Sunday, April 1, 2018

The many tendrils of a Sonic 3 level, part 3: hiding in plain sight

Have you ever been on an egg hunt, and ended up walking past the damn things multiple times without actually seeing them?

Shortly after the code for the animal object, at $2CA7C we run across the code for the title card object. The first thing it does in its init routine is check if the current zone is one of the Competition levels, and if so set byte $44 of its own SST.
        cmpi.b  #$E,(Current_zone).w
        bcs.s   loc_2CA96
        cmpi.b  #$12,(Current_zone).w
        bhi.s   loc_2CA96
        st      $44(a0)
        jmp     (Delete_Current_Sprite).l
When this flag is set, various aspects of the object's behavior are changed in order to display a unique set of title cards in Competition mode. However, these are ultimately never shown, because the object calls the Delete_Current_Sprite function immediately after setting the flag.
        lea     TitleCard_LevelGfx,a1
        moveq   #0,d0
        move.b  (Apparent_zone).w,d0
        lsl.w   #2,d0
        movea.l (a1,d0.w),a1
        move.w  #$A9A0,d2
        jsr     (Queue_Kos_Module).l
Shortly after, we find the code which loads the title card graphics. It uses the value of the apparent zone as an index to the TitleCard_LevelGfx array, which is a list of pointers to KosM-compressed archives containing the letters that spell out each zone's name:
TitleCard_LevelGfx:     dc.l ArtKosM_AIZTitleCard
                        dc.l ArtKosM_HCZTitleCard
                        dc.l ArtKosM_MGZTitleCard
                        dc.l ArtKosM_CNZTitleCard
                        dc.l ArtKosM_FBZTitleCard
                        dc.l ArtKosM_ICZTitleCard
                        dc.l ArtKosM_LBZTitleCard
                        dc.l ArtKosM_AIZTitleCard       ; MHZ
                        dc.l ArtKosM_AIZTitleCard       ; SOZ
                        dc.l ArtKosM_AIZTitleCard       ; LRZ
                        dc.l ArtKosM_AIZTitleCard       ; SSZ
                        dc.l ArtKosM_AIZTitleCard       ; DEZ
                        dc.l ArtKosM_AIZTitleCard       ; DDZ
                        dc.l ArtKosM_AIZTitleCard       ; HPZ
                        dc.l ArtKosM_ALZTitleCard
                        dc.l ArtKosM_BPZTitleCard
                        dc.l ArtKosM_DPZTitleCard
                        dc.l ArtKosM_CGZTitleCard
                        dc.l ArtKosM_EMZTitleCard
                        dc.l ArtKosM_BonusTitleCard
                        dc.l ArtKosM_BonusTitleCard
                        dc.l ArtKosM_BonusTitleCard
Then, at $2CC62, the apparent zone is used again, this time to inform the mapping frame used by the "name" portion of the title card:
        move.b  (Apparent_zone).w,d0
        add.b   d0,$22(a0)
The sprite mappings used by the title card object can be found at $2D90C. These mappings have the same layout as their Sonic & Knuckles counterparts, except the S&K stages all point at a blank mapping frame, and there's a mapping frame for the Competition mode title cards which was removed in Sonic & Knuckles.
Map_TitleCard:  dc.w Map_TitleCard_Blank-Map_TitleCard
                dc.w Map_TitleCard_Banner-Map_TitleCard
                dc.w Map_TitleCard_Act-Map_TitleCard
                dc.w Map_TitleCard_Zone-Map_TitleCard
                dc.w Map_TitleCard_AIZ-Map_TitleCard
                dc.w Map_TitleCard_HCZ-Map_TitleCard
                dc.w Map_TitleCard_MGZ-Map_TitleCard
                dc.w Map_TitleCard_CNZ-Map_TitleCard
                dc.w Map_TitleCard_FBZ-Map_TitleCard
                dc.w Map_TitleCard_ICZ-Map_TitleCard
                dc.w Map_TitleCard_LBZ-Map_TitleCard
                dc.w Map_TitleCard_Blank-Map_TitleCard  ; MHZ
                dc.w Map_TitleCard_Blank-Map_TitleCard  ; SOZ
                dc.w Map_TitleCard_Blank-Map_TitleCard  ; LRZ
                dc.w Map_TitleCard_Blank-Map_TitleCard  ; SSZ
                dc.w Map_TitleCard_Blank-Map_TitleCard  ; DEZ
                dc.w Map_TitleCard_Blank-Map_TitleCard  ; DDZ
                dc.w Map_TitleCard_Blank-Map_TitleCard  ; HPZ
                dc.w Map_TitleCard_2PMode-Map_TitleCard
                dc.w Map_TitleCard_Bonus-Map_TitleCard
                dc.w Map_TitleCard_Stage-Map_TitleCard
Note that both TitleCard_LevelGfx and Map_TitleCard contain valid entries for Flying Battery Zone, which is why we can get as far as displaying its title card in Sonic 3.

That's not why we're here, though. At $2CC08, right before being dismissed, the title card is responsible for loading the current level's KosM PLCs. It does this by calling the rather appropriately named LoadEnemyArt function:
        lea     off_2DF60,a6
        move.w  (Apparent_zone_and_act).w,d0
        ror.b   #1,d0
        lsr.w   #6,d0
        adda.w  (a6,d0.w),a6
        move.w  (a6)+,d6
        bmi.s   locret_2DF5E

        movea.l (a6)+,a1
        move.w  (a6)+,d2
        jsr     (Queue_Kos_Module).l
        dbf     d6,loc_2DF50

Once again, it uses the value of the apparent zone (and act) as an index to a pointer array, this time containing pointers to the KosM PLCs for each act in the game.

The thing is, much like TitleCard_LevelGfx and Map_TitleCard before it, this pointer array also contains valid entries for Flying Battery Zone:
off_2DF60:      dc.w PLCKosM_AIZ-off_2DF60
                dc.w PLCKosM_AIZ-off_2DF60
                dc.w PLCKosM_HCZ1-off_2DF60
                dc.w PLCKosM_HCZ2-off_2DF60
                dc.w PLCKosM_MGZ1-off_2DF60
                dc.w PLCKosM_MGZ2-off_2DF60
                dc.w PLCKosM_CNZ-off_2DF60
                dc.w PLCKosM_CNZ-off_2DF60
                dc.w PLCKosM_FBZ-off_2DF60
                dc.w PLCKosM_FBZ-off_2DF60
                dc.w PLCKosM_ICZ-off_2DF60
                dc.w PLCKosM_ICZ-off_2DF60
                dc.w PLCKosM_LBZ-off_2DF60
                dc.w PLCKosM_LBZ-off_2DF60
                dc.w PLCKosM_LBZ-off_2DF60
                dc.w PLCKosM_LBZ-off_2DF60
                dc.w PLCKosM_LBZ-off_2DF60
                dc.w PLCKosM_LBZ-off_2DF60
                dc.w PLCKosM_LBZ-off_2DF60
                dc.w PLCKosM_LBZ-off_2DF60
                dc.w PLCKosM_LBZ-off_2DF60
                dc.w PLCKosM_LBZ-off_2DF60
                dc.w PLCKosM_LBZ-off_2DF60
                dc.w PLCKosM_LBZ-off_2DF60
                dc.w PLCKosM_LBZ-off_2DF60
                dc.w PLCKosM_LBZ-off_2DF60
Remember when I said the graphics for Flying Battery's enemies went out the door with the rest of the level load block?
PLCKosM_FBZ:    dc.w 1
                dc.l ArtKosM_Blaster
                dc.w $A000
                dc.l ArtKosM_Technosqueek
                dc.w $A500
Uh... April Fools!

The PAR code 02DF46:7010 will force every level to load Flying Battery Zone's KosM PLC. You can use this alongside the codes 05B58C:7010 and 05B5C2:7010 from my previous post in order to place the FBZ enemies in any level using debug mode. FBZ's palette is long gone (I checked), but luckily Carnival Night Zone's is a suitable replacement.


  1. I saw that video in your playlist some time ago, and wondered whether the graphics were there all along or you added them in. No wonder why TCRF mentioned ripping the sprites.

    Happy Easter/April Fools' Day!

  2. xkeeper here. so good to have another update.

    i never noticed they eject a shell, too, neato

  3. On the topic of title cards, forcing yourself into Sky Sanctuary zone or The Doomsday level slot reveals that while the title card is long gone, both of them lack act numbers like in the final version. Does this mean that they had the title cards for all the S&K zones completed at one point?

    Also, Happy Easter!

    1. Indeed, the "act" portion of the title card kills itself when the apparent zone is either Sky Sanctuary Zone or The Doomsday Zone. They most definitely had that stuff planned out in advance.

    2. Do you know why the S3C 0408 prototype uses act numbers for Sky Sanctuary Zone?

    3. Do I know? Of course not. But what's clear to me is that Hidden Palace was a late addition, so they were probably still figuring out what to call it, and one of the things they tried was making it act 1 of Sky Sanctuary Zone.

      Evidently, they later changed their minds and decided to name it after the lost Sonic 2 level instead.

    4. Now this is unrelated, but the game freezes whenever you try to spawn in a Knuckles cut-scene object from Sonic & Knuckles. Subtypes that aren't defined never crash the game so why is that not the case here?

      Setting the subtype to 30$ (MHZ intro cut-scene that was added in S3C 0517)and anything higher does not crash the game.

  4. I have a question, you know the Marble Garden Robotnik? When he is zooming out, his face gets this weird color that has a lot of black splotches. Are multiple color entries in the graphic data for that sprite getting rewritten with black?

  5. Today is the anniversary of this blog, and yet there's no post at least acknowledging this. Disappointing.

  6. looks like the blog's dead?

  7. Is the blog done? I hope not. If it is I'll understand but is it done? I really enjoyed reading these and it'd be a shame if it got discontinued.

    1. I mean, there will come a time when I just stop writing it, and there probably won't be any fanfare about it, there simply won't be any new posts from then on.

      In that sense, every post is the last post until it isn't, so... edge of your seats, everybody! (There is a slightly smaller chance that a given post will be the last if it ends with "next time" or some such.)