Thanks for the detailed explanation, Devon. The ported C code's version doesn't just calculate the ID, though, which is why I was confused. I think it was ported incorrectly: Code (C): if (!((systemtimer.b.b4 + ((pActwk - actwk) / 68U)) & 3)) { This should be: Code (C): if (!((systemtimer.b.b4 + (pActwk - actwk)) & 3)) { EDIT: Or even: Code (C): if (!((systemtimer.b.b4 + (pActwk - actwk - 1)) & 3)) {
The division is there to convert its offset from the start of the object pool to an index value, which is comparable to using the object update loop counter from register d7. Though, this is assuming the subtraction treats both values as plain raw addresses, so if that's NOT the case (i.e. the compiler doing the division automatically), then that's a bug, yes. Also, if you wanted it to be more accurate, you'd wanna subtract the object's slot address from the end of the object pool, divide that into an index value, then subtract 1, since register d7 in the original code counted down as it went from the start to the end of the object pool.
The compiler indeed does the division automatically. This is the equivalent MIPS ASM of Sonic CD's if statement: Code (ASM): lw v1,32(sp) lui v0,hi(actwk+260) addiu v0,v0,lo(actwk+18032) subu v1,v1,v0 li v0,68 div v1,v0 bne v0,$0,*+12 nop break 7 mflo v1 li v0,68 divu v1,v0 bne v0,$0,*+12 nop break 7 mflo v1 lui v0,hi(systemtimer+261) lb v0,lo(systemtimer)(v0) dsll32 v0,v0,24 dsra32 v0,v0,24 addu v0,v0,v1 andi v0,v0,0x3 bne v0,$0,*+148 For accuracy, would it be enough to have my own version of Process_Sprites count backwards just like the original? Then I don't need to change the code of flyringmove.
Yup, that's a bug alright. And just as long as you still go from the start of the object pool to the end still and have the counter passed onto the object functions themselves, then yeah.
At the tail end of Sonic 3's code for the animal capsule, I found a reference to the act results code. As a result, that's the next chunk I've been investigating. A chunk that I don't have labels for, so I have to name stuff myself (eek!). In Sonic 1 and Sonic CD, the code for the end of the level is spread between the goal and clear function sets. goal calculates the bonus scores, while clear adds them in small chunks to the player's score. Come Sonic 2, goal doesn't seem to exist anymore. When searching for "clear", I find masinclear. masin is Sonic 2's term for the animal capsules. I don't know how it's done in Sonic 2, but in Sonic 3 the functionality of goal and clear seems to have been combined into one object. As a result, I have no good idea for what to name it. There's also another object that is responsible for the numbers, and takes control of the player characters.
In Sonic 1, the capsule or signpost object, the results object, and the HUD update function ( scoreset) called by the scene's main loop work together to handle the end of level sequence. It starts with the signpost/capsule setting a flag to stop the timer, which is generally also to indicate we are in the end of level sequence, it then locks the player's control and forces them to run to the right, all done within either the capsule or the signpost's code. Then they both call a function in disasms called GotThroughAct. Which sets the left side boundary to the same as the right side boundary, locking the screen. Then loads a results card object. And then calculates the time and ring bonuses. And finally plays the end of level music. After the results card initializes and moves into place, it sets the flag which tells scoreset to update the bonus counter number tiles, and works away at ticking down the time bonus, followed by the ring bonus while they get added to the player's current score. Once it's done with that, the title card will get the next level ID the game should go to from LevelOrder, if the next level for whatever reason should be GHZ1 (You can't go back to the first level, of course), then it'll make the game reset, likely for demo builds.
So it's pretty well known by now that sprite mappings were done by hand, but let's take a look at some sprites that make this clear. Both the press start button text and GHZ's spike helix have a clever method of saving a single byte for frames that are intended to be blank. What they do is point to the actual sprite data, rather than the start of the sprite list, where a 0 is placed to indicate there are 0 sprites for the entry. Code (Text): dc.w TtlSprtMaps_PSB+1-TitleSprite_Maps ; Points to the 0 AFTER the sprite count, effectively displaying 0 sprites, or a blank frame. dc.w TtlSprtMaps_PSB-TitleSprite_Maps dc.w TtlSprtMaps_Filler-TitleSprite_Maps dc.w TtlSprtMaps_TM-TitleSprite_Maps TtlSprtMaps_PSB: dc.b 6 dc.b 0, $C, 0, $F0, 0 dc.b 0, 0, 0, $F3, $20 dc.b 0, 0, 0, $F3, $30 dc.b 0, $C, 0, $F4, $38 dc.b 0, 8, 0, $F8, $60 dc.b 0, 8, 0, $FB, $78 Code (Text): dc.w HelixMaps_Frame1-SpikeHelix_Maps, HelixMaps_Frame2-SpikeHelix_Maps dc.w HelixMaps_Frame3-SpikeHelix_Maps, HelixMaps_Frame4-SpikeHelix_Maps dc.w HelixMaps_Frame5-SpikeHelix_Maps, HelixMaps_Frame6-SpikeHelix_Maps dc.w HelixMaps_Frame7+2-SpikeHelix_Maps ; Similarily, points to the 0 dc.w HelixMaps_Frame7-SpikeHelix_Maps HelixMaps_Frame1: dc.b 1 dc.b $F0, 1, 0, 0, $FC HelixMaps_Frame2: dc.b 1 dc.b $F5, 5, 0, 2, $F8 HelixMaps_Frame3: dc.b 1 dc.b $F8, 5, 0, 6, $F8 HelixMaps_Frame4: dc.b 1 dc.b $FB, 5, 0, $A, $F8 HelixMaps_Frame5: dc.b 1 dc.b 0, 1, 0, $E, $FC HelixMaps_Frame6: dc.b 1 dc.b 4, 0, 0, $10, $FD HelixMaps_Frame7: dc.b 1 dc.b $F4, 0, 0, $11, $FD even This was something I came to realize while converting the mapping files in my project to binaries and had this happen when I got to the spike helix. Because as it turns out, Flex 2 can't properly handle strange situations like this, not that I expect it to, since this is something that could only have happened by hand. The shield and invincibility stars also do something similar. None of this is news, but I thought it was cool to share evidence about something some may still have had doubts about.
I would also say that the graphical glitches with Sonic in the v0.02 prototype of Sonic CD point to this as well.
In the v0.02 prototype, a number of Sonic's frames are glitched out, particularly the 0 degree walking sprites, the 45 degree angle running sprites, the "I'm outta here" sprites, and the spinning disc sprites, all due to problems in the mappings/DPLC data. You can see them in this sheet. To me, they can definitely be the result of human error, especially when combined with the existing evidence that the data was made by hand.
When I get the chance, I can take a look. I've been out all day for a concert out of state and I need time to rest first.
So, upon inspection, I noticed that all the of glitches stem from bad DPLC data. Let's take a look, shall we? Case 1: "I'm outta here" last frame The sprite mappings and DPLC data is actually fine here. It's just that the maximum amount of tiles allowed to be loaded for a sprite is 0x17, which this sprite exceeds, particularly due to the fact that the tiles get loaded into work RAM first before being DMA'd into VRAM, in which there isn't a whole lot of space to work with there. Not really much we can do outside of refactoring how the tiles are loaded. Case 2: Spinning disc (front) Here, the first 2 DPLC entries erroneously load an extra tile than it should, which throws off the sprite mappings. Code (ASM): byte_22E08A: dc.b 4 dc.w $2179 ; Should be $1179 dc.w $217B ; Should be $117B dc.w $217D dc.w $8180 Fix those values, and we get this: Case 3: Spinning disc (back) Similar issue as before. The first DPLC entry loads 3 more tiles than it should. Code (ASM): byte_22E09C: dc.b 3 dc.w $8189 ; Should be $5189 dc.w $218F dc.w $8192 Fix that value, and we get this: Case 4: Spinning disc (front left) This one isn't really visible, but there's a few errors with its DPLC entries. First off, the last entry loads an extra tile. Since it's the last entry though, this doesn't really affect anything. However, there's something curious about all of the entries. You see, this particular sprite is a horizontally flipped version of another sprite. What's interesting is that it seems they tried to apply the sprite X flip flag to the DPLC entries, because the graphics tile offset are all offset by 0x800, which is the same offset used for sprite tile IDs to make them flipped. Code (ASM): byte_22E094: dc.b 3 dc.w $B966 ; Should be $B166 dc.w $5972 ; Should be $5172 dc.w $1978 ; Should be $178 Now, the offset of 0x800 here doesn't really make any visible impact, because it gets shifted out when it's converted into a byte offset. You see, when multiplying, it doesn't actually get expanded into a longword, it remains a regular word, which is why the bit is shifted out. Code (ASM): moveq #0,d2 ; Get first byte of entry move.b (a2)+,d2 move.w d2,d0 ; Get number of tiles lsr.b #4,d0 lsl.w #8,d2 ; Get tile offset in graphics data move.b (a2)+,d2 lsl.w #5,d2 ; Multiply by 0x20 (as a word) lea Art_Sonic,a1 ; Add to base graphics data address adda.l d2,a1 Case 5: Spinning disc (back left) The exact same issue as the previous one, except that it doesn't load extra tiles. Code (ASM): byte_22E0AA: dc.b 2 dc.w $F99B ; Should be $F19B dc.w $39AB ; Should be $31AB Case 6: Walking sprite With this one, the last DPLC entry actually loads 1 LESS tile that it should. The glitched tile is a leftover from the previous walking sprite displayed. Code (ASM): byte_22E12A: dc.b 3 dc.w $2090 dc.w $B2FD dc.w $1309 ; Should be $2309 Fix that value, and we get this: Case 7: Angled walking sprite Another case of extra tiles being loaded and throwing off the sprite mappings. This time 4 extra tiles are loaded in one of the DPLC entries. Code (ASM): byte_22E156: dc.b 6 dc.w $333A dc.w $133E dc.w $B340 ; Should be $7340 dc.w $348 dc.w $349 dc.w $234A Fix that value and we get this: Case 8: Angled running sprite #1 Another case where a DPLC entry loads 1 less tile that is should. Code (ASM): byte_22E27E: dc.b 4 dc.w $34EF dc.w $4F3 dc.w $14F4 ; Should be $24F4 dc.w $B4F7 Fix that value, and we get this: Case 9: Angled running sprite #2 Basically the exact same issue as the last one. Seems to have been copied and pasted and then modified. Code (ASM): byte_22E296: dc.b 4 dc.w $34EF dc.w $4F3 dc.w $14F4 ; Should be $24F4 dc.w $B50F Fix that value, and we get this: Bonus case: Shield sprite in the final game This one isn't really an error, but it's definitely a clear sign that the sprite mappings were done by hand. Basically, like some objects in Sonic 1, for the blank sprite, it uses a 0 that's found inside another sprite. Code (ASM): byte_23EEB6: dc.b 4 dc.b $E8, $A, 0, 0, $E8 dc.b $E8, $A, 0, 9, 0 byte_23EEC1: dc.b 0, $A, $10, 0, $E8 ; Blank sprite uses this "0" from this sprite for the piece count dc.b 0, $A, $10, 9, 0 Conclusion So, those are some extra tidbits from Sonic CD that I believe help point to the fact that the sprites were done by hand, particularly since only a select set of frames have correct sprite mappings, but broken DPLC entries. There's just not enough consistency to believe that a tool did all this, either.
This is probably nothing, but I wonder if anyone's got an answer. The RNG routine will make a seed if the input value is 0. The seed it uses in particular is 0x2A6D365A. I've tried throwing it around in different formats, in decimal it's 711,800,410 which almost seems like a messed up phone number? Or a birthday? In ASCII it's *m6Z, which is... something. I could also be overthinking it, it is meant to be random after all. But this seems like the kind of thing that a developer would stick an easter egg into. Also worth keeping in mind is that this is most likely a library that multiple games used so it may not even be relevant to Sonic in particular.
I don't think it has any meaning. Just a random set of digits. Also yeah, other Genesis games have the exact same function.