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. Spoiler: IMAGE: Loop demonstration, separated Spoiler: IMAGE: Loop demonstration, overlapped 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. Spoiler: IMAGE: Offset demonstration -------------------------------------------------------------------------------------------------------------------------------------------- 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. Spoiler: IMAGE: Overlap demonstration 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. Spoiler: IMAGE: Sprites and background usage clarification 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: Spoiler: IMAGE: Replica background plane demonstration [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.
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.
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.
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): tst.b (a2) beq.s locret_6AD6 bclr #2,(a2) beq.s loc_6AAC move.w #$D0,d4 move.w 4(a3),d1 andi.w #-$10,d1 sub.w d1,d4 move.w d4,-(sp) moveq #-$10,d5 bsr.w sub_6C3C move.w (sp)+,d4 moveq #-$10,d5 moveq #2,d6 bsr.w sub_6B06 loc_6AAC: bclr #3,(a2) beq.s locret_6AD6 move.w #$D0,d4 move.w 4(a3),d1 andi.w #-$10,d1 sub.w d1,d4 move.w d4,-(sp) move.w #$140,d5 bsr.w sub_6C3C move.w (sp)+,d4 move.w #$140,d5 moveq #2,d6 bsr.w sub_6B06 locret_6AD6: rts
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) 0x47D8: Code (Text): ??DrawChunksUnk: lea scrollBG3PosX.w,a3 move.w #$6000,d2 move.w #176,d4 moveq #2,d6 .Loop: movem.l d4-d6,-(sp) moveq #0,d5 move.w d4,d1 bsr.w .CalcBlocksVRAMUnk move.w d1,d4 moveq #0,d5 moveq #32-1,d6 bsr.w DrawLeftToRight2 movem.l (sp)+,d4-d6 addi.w #16,d4 dbf d6,.Loop 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): GetObjects_Init: addq.b #2,objectLoadRoutine.w move.w zone.w,d0 lsl.b #6,d0 lsr.w #4,d0 lea ObjPosIndex.l,a0 movea.l a0,a1 adda.w (a0,d0.w),a0 move.l a0,objectChunkRight.w move.l a0,objectChunkLeft.w adda.w 2(a1,d0.w),a1 move.l a1,objunkChunkRight.w move.l a1,objunkChunkLeft.w ... A bit later on, there's some unreferenced code for actually creating the objects within it(?), at 0x8B00: Code (Text): ??_getobjectsUnknown: movea.l objunkChunkRight.w,a0 move.w scrollBG3PosX.w,d0 addi.w #$200,d0 andi.w #$FF80,d0 cmp.w (a0),d0 bcs.s .Exit bsr.w .LoadEntry move.l a0,objunkChunkRight.w bra.w ??_getobjectsUnknown .Exit: 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.
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 )
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): ??DrawBGScrollBlockUnk: tst.b (a2) beq.s .Exit bclr #2,(a2) beq.s .AlreadyDrawn move.w #208,d4 move.w 4(a3),d1 andi.w #$FFF0,d1 sub.w d1,d4 move.w d4,-(sp) moveq #-16,d5 bsr.w ??CalcBlocksVRAMUnk move.w (sp)+,d4 moveq #-16,d5 moveq #2,d6 bsr.w DrawTopToBottom2 .AlreadyDrawn: bclr #3,(a2) beq.s .Exit move.w #208,d4 move.w 4(a3),d1 andi.w #$FFF0,d1 sub.w d1,d4 move.w d4,-(sp) move.w #320,d5 bsr.w ??CalcBlocksVRAMUnk move.w (sp)+,d4 move.w #320,d5 moveq #2,d6 bsr.w DrawTopToBottom2 .Exit: rts Code (Text): ??CalcBlocksVRAMUnk: add.w 4(a3),d4 ; Y input add.w (a3),d5 ; X input andi.w #$F0,d4 andi.w #$1F0,d5 lsl.w #4,d4 lsr.w #2,d5 add.w d5,d4 moveq #2,d0 swap d0 move.w d4,d0 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
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?
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): loc_347A: subq.b #1,(unk_FFFEC4).w bpl.s loc_3498 move.b #7,(unk_FFFEC4).w addq.b #1,(unk_FFFEC5).w cmpi.b #6,(unk_FFFEC5).w bcs.s loc_3498 move.b #0,(unk_FFFEC5).w loc_3498: In the prototype, this is (part of) the code that handles Marble Zone's UFO animations: Code (Text): loc_11412: subq.b #1,(unk_FFF7B5).w bpl.w locret_11480 move.b #7,(unk_FFF7B5).w lea (byte_6C398).l,a1 moveq #0,d0 move.b (unk_FFF7B4).w,d0 addq.b #1,d0 cmpi.b #5,d0 bne.s loc_11436 moveq #0,d0 loc_11436: I wonder why they look so similar...
...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.
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.