don't click here

An investigation on the Tokyo Toy Show demo's parallax

Discussion in 'Engineering & Reverse Engineering' started by MDTravis, May 17, 2024.

  1. In June 1990, Sonic the Hedgehog debuted at the ‘90 Tokyo Toy Show, with a radically different appearance to what it became at its release a year later. In its legacy, this parallax demo became a famous piece of Sonic lost media. Especially as of the 2020s, many people including myself have attempted to recreate the parallax demo, but doing such a task would require lots of research. That’s what I’m doing here today, to write about how I believe the foreground parallax works in this demo.

    The foreground parallax I’ll be referring to here involves things that display above the level, such as the moving trees, rocks, and clouds. I won’t be talking about the clouds in this one though, as that is its own (still unsolved) mystery.


    For many years, people have assumed this was achieved entirely based on sprites. It was found out that the tree in the final version of Green Hill Zone was constructed to work like a sprite, further supporting the idea. Unfortunately, all attempts at replicating the foreground using sprites resulted in the scanline sprite limit being reached.

    Later on, a pattern was found in these sprites, where they loop every 1024 pixels. This is equal to every 512 pixels of Sonic movement as these sprites move at double the speed of the camera.

    A later discovery has shown that these sprites were placed on a 16x16 grid, equal to two tiles, or one level block. The below image is an example.


    Being aligned in such a way seems odd for seemingly free-form sprites. That gave me the idea, what if these aren’t even sprites at all?
    You may be able to notice that the bottom of the background isn’t visible in any of the currently available screenshots, as the level terrain overlaps it. You may also notice that in one case, the tree overlaps another tree above the ground, but nothing overlaps in front of the ground.

    What’s my idea here?
    These are high priority background tiles.

    Now, this doesn’t completely rule out sprites being used, but just only when they're above the ground. Below the ground, they are background tiles.

    The sprites and background tiles would move in sync with each other, making it look like there was a seamless third plane scrolling along with everything else. Due to that, the background plane would look something like this:
    [assets from Project Tokyo Debut 1990 by Laffy Taffy]

    A similar method of parallax was used in Sonic the Hedgehog 2 and Sonic the Hedgehog CD, in Aquatic Ruin and Quartz Quadrant respectively.


    Putting it all together, the foreground parallax is constructed of both sprites that go above the ground, and high priority background tiles that overlay the ground. This is supported by things only overlapping above the ground, and how each object is on a 16x16 grid.
    Last edited: May 17, 2024
    • Like Like x 9
    • Agree Agree x 1
    • List
  2. MarkeyJester


    Original, No substitute Resident Jester
    I assumed the same, but for different reasons; the level never seems to scroll vertically, you see frequently Sonic near the bottom and top of the screen in muliple shots, but the terrain always appears static on Y.

    Code wise, the VSRAM write during V-blank exists near the immediate start, rather than in the subroutines along with CRAM write, and other name table/H-scroll/sprite transfers. This is strange unless you realise the Tokyo Toy show ROM never had V-scroll writing, and VSRAM write would be novicely tacked on later without proper structure.

    This lack of potential V-scroll coincides with your H-scroll plane methodology.
    • Agree Agree x 3
    • Like Like x 2
    • List
  3. It has been confirmed in a now deleted Twitter post that the demo lacked vertical scrolling. I believe the addition of vertical scrolling was why the foreground parallax was removed.
    • Informative Informative x 7
    • List
  4. MarkeyJester


    Original, No substitute Resident Jester
    Ah okay, well that certainly helps prove it lol

    By the way, I believe offset 6A82 "might" be the special subroutine for drawing your lower 3 blocks of BG high plane tiles, it's unused in the final.

    Code (Text):
    2.         tst.b   (a2)
    3.         beq.s   locret_6AD6
    4.         bclr    #2,(a2)
    5.         beq.s   loc_6AAC
    6.         move.w  #$D0,d4
    7.         move.w  4(a3),d1
    8.         andi.w  #-$10,d1
    9.         sub.w   d1,d4
    10.         move.w  d4,-(sp)
    11.         moveq   #-$10,d5
    12.         bsr.w   sub_6C3C
    13.         move.w  (sp)+,d4
    14.         moveq   #-$10,d5
    15.         moveq   #2,d6
    16.         bsr.w   sub_6B06
    18. loc_6AAC:
    19.         bclr    #3,(a2)
    20.         beq.s   locret_6AD6
    21.         move.w  #$D0,d4
    22.         move.w  4(a3),d1
    23.         andi.w  #-$10,d1
    24.         sub.w   d1,d4
    25.         move.w  d4,-(sp)
    26.         move.w  #$140,d5
    27.         bsr.w   sub_6C3C
    28.         move.w  (sp)+,d4
    29.         move.w  #$140,d5
    30.         moveq   #2,d6
    31.         bsr.w   sub_6B06
    33. locret_6AD6:
    34.         rts
    • Informative Informative x 2
    • Like Like x 1
    • Agree Agree x 1
    • Useful Useful x 1
    • List
  5. JayKuriN


    Defeated Dreamer Member
    We know that for the tops of the foreground elements they would have had to be objects, with the lower redraw dampening the sprite limitations.
    There's some remnants that point to the redraw and object layout parser routines communicating with each other through variable 0xFFF718 (I have this labelled as scrollBG3PosX in my disassembly.)

    (ROM Addresses for the prototype)

    Code (Text):
    1. ??DrawChunksUnk:
    2.                 lea     scrollBG3PosX.w,a3
    3.                 move.w  #$6000,d2
    4.                 move.w  #176,d4
    5.                 moveq   #2,d6
    7. .Loop:
    8.                 movem.l d4-d6,-(sp)
    9.                 moveq   #0,d5
    10.                 move.w  d4,d1
    11.                 bsr.w   .CalcBlocksVRAMUnk
    12.                 move.w  d1,d4
    13.                 moveq   #0,d5
    14.                 moveq   #32-1,d6
    15.                 bsr.w   DrawLeftToRight2
    16.                 movem.l (sp)+,d4-d6
    17.                 addi.w  #16,d4
    18.                 dbf     d6,.Loop
    19.                 rts

    The parser grabs the unused second object layout pointer for each zone too, initializing some values with their null data at 0x8A00:

    Code (Text):
    1. GetObjects_Init:                  
    2.                 addq.b  #2,objectLoadRoutine.w
    3.                 move.w  zone.w,d0
    4.                 lsl.b   #6,d0
    5.                 lsr.w   #4,d0
    6.                 lea     ObjPosIndex.l,a0
    7.                 movea.l a0,a1
    8.                 adda.w  (a0,d0.w),a0
    9.                 move.l  a0,objectChunkRight.w
    10.                 move.l  a0,objectChunkLeft.w
    11.                 adda.w  2(a1,d0.w),a1
    12.                 move.l  a1,objunkChunkRight.w
    13.                 move.l  a1,objunkChunkLeft.w
    14.                 ...

    A bit later on, there's some unreferenced code for actually creating the objects within it(?), at 0x8B00:

    Code (Text):
    1. ??_getobjectsUnknown:
    2.                 movea.l objunkChunkRight.w,a0
    3.                 move.w  scrollBG3PosX.w,d0
    4.                 addi.w  #$200,d0
    5.                 andi.w  #$FF80,d0
    6.                 cmp.w   (a0),d0
    7.                 bcs.s   .Exit
    8.                 bsr.w   .LoadEntry
    9.                 move.l  a0,objunkChunkRight.w
    10.                 bra.w   ??_getobjectsUnknown
    11. .Exit:
    12.                 rts
    IIRC, this routine only redraws the bottom-most row of tiles into VRAM at $A000. It would definitely work for this purpose though.

    Some of this confused me going through it blind, and I originally thought some of the weird VRAM stuff was for the unused window plane HUD. There's a lot of weird oddities with the nametables and conflicting implementations of things here and there so it's hard to tell what's what.
    Last edited: May 17, 2024
  6. MarkeyJester


    Original, No substitute Resident Jester
    Now to your credit, given the screen position is at 0 relative to a plane at A000, this would mean only the bottom row of blocks of the screen would be redrawn, fair's fair. But it draws columns, not rows; 3 blocks of columns to the left or right, at D0, E0, and F0 on Y of A000.

    This routine has been given a Y position variable to draw relative against. With the original having no V-scroll, it's possible the Y variable was tacked on at some point during developement, but also the Y fixed position D0+ might have been diffferent, so the subroutine may have been heavily modified from its original purpose. The focus was primarily on it drawing 3 blocks worth, which coincides with the bottom drawing of the plane graphics MDTravis shared above.

    I won't beat around the bush of course, I'm not saying this is what the subroutine was used for, the 3 blocks could be just coincidence.

    I am interested though; given you've been disassembling the prototype, perhaps you've already uncovered this unused subroutine somewhere, would be great to have a look and compare (assume it does exist, its lack of existence might also put the theory to rest :eng101:)
  7. Brainulator


    Regular garden-variety member Member
    Is this disassembly of any use?
  8. JayKuriN


    Defeated Dreamer Member
    I'm an idiot. I was looking at the calculations with the 224 screen resolution in my head and NOT the plane resolution at 256. That's my bad. It fits this case almost perfectly

    This routine does exist at 0x45B2 in the prototype, alongside the routines to calculate the VRAM positions for it (0x476E), which are unique from the ones used by the other redrawing routines:

    Code (Text):
    1. ??DrawBGScrollBlockUnk:
    2.                 tst.b   (a2)
    3.                 beq.s   .Exit
    4.                 bclr    #2,(a2)
    5.                 beq.s   .AlreadyDrawn
    6.                 move.w  #208,d4
    7.                 move.w  4(a3),d1
    8.                 andi.w  #$FFF0,d1
    9.                 sub.w   d1,d4
    10.                 move.w  d4,-(sp)
    11.                 moveq   #-16,d5
    12.                 bsr.w   ??CalcBlocksVRAMUnk
    13.                 move.w  (sp)+,d4
    14.                 moveq   #-16,d5
    15.                 moveq   #2,d6
    16.                 bsr.w   DrawTopToBottom2
    18. .AlreadyDrawn:
    19.                 bclr    #3,(a2)
    20.                 beq.s   .Exit
    21.                 move.w  #208,d4
    22.                 move.w  4(a3),d1
    23.                 andi.w  #$FFF0,d1
    24.                 sub.w   d1,d4
    25.                 move.w  d4,-(sp)
    26.                 move.w  #320,d5
    27.                 bsr.w  ??CalcBlocksVRAMUnk
    28.                 move.w  (sp)+,d4
    29.                 move.w  #320,d5
    30.                 moveq   #2,d6
    31.                 bsr.w   DrawTopToBottom2
    33. .Exit:
    34.                 rts

    Code (Text):
    1. ??CalcBlocksVRAMUnk:
    2.                 add.w   4(a3),d4        ; Y input
    3.                 add.w   (a3),d5          ; X input
    4.                 andi.w  #$F0,d4
    5.                 andi.w  #$1F0,d5
    6.                 lsl.w   #4,d4
    7.                 lsr.w   #2,d5
    8.                 add.w   d5,d4
    9.                 moveq   #2,d0
    10.                 swap    d0
    11.                 move.w  d4,d0
    12.                 rts

    Considering some of the concept art, the reason why this is still here is because the concept for foreground parallax wasn't scrapped until a bit later on. Now why having them on different nametables? I have no idea. The VRAM calculation subroutine is only used by this singular piece of code and nowhere else. It's possible the VRAM layout started with Plane A at 0x8000 and Plane B at 0xA000, and they shifted this down later to 0xC000 and 0xE000. The calculations could just be a remnant of that implementation
  9. Kilo


    That inbetween sprite from S&K's title screen Tech Member
    S1 - Metal Sonic's Challenge, Sonic 1 Rev01 ASMX Disasm
    Hadn't seen this mentioned here yet, but bits 2 and 3 of the object render flags denote where the object should position itself relative to the world. In the final game, objects are only screen space relative, or foreground relative, 00 or 01. But there's also options to align it with the background's first position, 10, and it's 3rd position, 11, this lower area we've been talking about. So we can assume these foreground objects used the background 3 aligned positioning. Perhaps there were also objects that used the background 1 alignment? Like the clouds?
  10. Brainulator


    Regular garden-variety member Member
    The SCHG page for Sonic 1's RAM speculated that this may have been used for the Marble Zone UFOs, but I removed this claim since the prototype we have seemed to disprove this. However, I later noticed something interesting about the code that handles synchronized sprite animations (source):
    Code (Text):
    1. loc_347A:
    2.         subq.b    #1,(unk_FFFEC4).w
    3.         bpl.s    loc_3498
    4.         move.b    #7,(unk_FFFEC4).w
    5.         addq.b    #1,(unk_FFFEC5).w
    6.         cmpi.b    #6,(unk_FFFEC5).w
    7.         bcs.s    loc_3498
    8.         move.b    #0,(unk_FFFEC5).w
    10. loc_3498:
    In the prototype, this is (part of) the code that handles Marble Zone's UFO animations:
    Code (Text):
    1. loc_11412:
    2.         subq.b    #1,(unk_FFF7B5).w
    3.         bpl.w    locret_11480
    4.         move.b    #7,(unk_FFF7B5).w
    5.         lea    (byte_6C398).l,a1
    6.         moveq    #0,d0
    7.         move.b    (unk_FFF7B4).w,d0
    8.         addq.b    #1,d0
    9.         cmpi.b    #5,d0
    10.         bne.s    loc_11436
    11.         moveq    #0,d0
    13. loc_11436:
    I wonder why they look so similar...
  11. MarkeyJester


    Original, No substitute Resident Jester
    ...they look similar because anything involving an animation cycle was done in that style; and primarily to allow for non-power of 2 looping animations. You can find this type of counting style in the palette cycling, art animation cycling, and universal frame sych subroutines. Other than that, there's no connection, you're finding things that aren't there.
  12. Brainulator


    Regular garden-variety member Member
    It's not just the style of the code; they both animate every 8 frames and have 6 steps (the second snippet's cmpi #5,d0 should be a cmpi #6,d0). I suppose it still could be a coincidence, but... yeah. Also note that the UFOs seem to look different in an earlier build, though this may just be the palette.