Hey, did you know that checkpoints don't exist or even function at all in the past or future time zones? In fact, the game even has code that forces Sonic to spawn at the beginning of the stage if you're not in the present when he dies.
I genuinely never noticed this and it didn't seem right to me, but I went and checked and it's absolutely true - the present time zones have checkpoints pretty liberally placed all over, but they're completely absent in every other time zone. Incredible that I never noticed it before.
Since checkpoints have an internal order (most noticeable in Emerald Hill Zone), I guess they just didn't know how to handle multiple checkpoints at potentially different locations in different timezones, and they didn't feel like using two more bytes of RAM. But it's only a guess.
Here's a bonus fun fact. In Palmtree Panic act 1, in the present, the checkpoint object is set to ID 0x13, but in the other time zones, they are set to ID 0x2B. The checkpoints in the present for that stage wouldn't have even shown up for the other time zones because of this!
I always noticed you'd restart at the beginning if you died or restarted in the past, but it'd never occurred to me that was why. Interesting!
I think I found the relevant code for the C port in PLAYER.C, function play00erase. A "checkpoint" is apparently known as a "marker" there: Code (Text): if (markerno == 0 && time_flag == 1) play_start &= 253; if (pl_suu != 0) { if (time_flag == 1) { if (markerno != 0) plflag = 1; } else { plflag = 0; } } sub_sync(14);
I knew about the restarting thing for a while now... maybe I've just played the 2011 version quite a bit. If I had to guess, I suspect that it would be a problem if, after time-traveling, respawning at a checkpoint's coordinates would lead to you being stuck inside a wall or something.
There's an inconsistency in the code for the first boss in the C version. I wonder if it's also in the Mega CD original. In file BOSS_1.C, function egg1leg1_11, lines 2961 and 2962, it retrieves the ID of a sub-object: Code (Text): subact = pActwk->actfree[6]; subact = actwk[subact].actfree[6]; What's wrong with it? Usually these IDs are retrieved as a short, not as a char, like this: Code (Text): subact = ((short*)pActwk)[26]; The maximum ID is 128, which fits in one byte, so at first glance this isn't an issue. However, on a big-endian system (like the GameCube), this would retrieve the high byte, which would be zero.
That looks like a bug they made while translating the code. The original game treats it like the latter. Code (Text): ROM:0020D0EE egg1leg1_11: ROM:0020D0EE btst #0,actfree+2(a0) ROM:0020D0F4 bne.s loc_20D12E ROM:0020D0F6 movea.w actfree+6(a0),a1 ROM:0020D0FA movea.w actfree+6(a1),a1 (Here, the values are direct 16-bit addresses in work RAM rather than offsets in the "actwk" pool, since they get sign extended, but it's basically the same logic)
I haven't seen this documented anywhere, so here goes: Sonic CD's Time Attack menu replaces any high score name that consists of "AAA" by "YOU".
Adapting the Sub CPU's command manager in the SP Extension for use in my Mode 1 error handler's test ROM revealed a small but destructive oversight within it: Code (ASM): .WaitCommand: move.w GACOMCMD0.w,d0 ; Get command ID from Main CPU beq.s .WaitCommand cmp.w GACOMCMD0.w,d0 bne.s .WaitCommand cmpi.w #(.SPCmdsEnd-.SPCmds)/2+1,d0 ; Note: that "+1" shouldn't be there bcc.w SPCmdFinish ; If it's an invalid ID, crash the sub CPU! move.w d0,d1 ; Execute command add.w d0,d0 move.w .SPCmds(pc,d0.w),d0 jsr .SPCmds(pc,d0.w) The intent of the cmpi.w and bcc.w is to allow the system to gracefully handle the possibility of the main CPU passing an invalid command ID by acknowledging the main CPU using the invalid ID. However, they bungled it completely since SPCmdFinish is meant to be one level higher on the call stack (specifically, branched to by command handlers that have been called via the jsr). The sub CPU's stack pointer is at its initial value ($10000) while in the command wait loop, so as it stands, passing an invalid command from the main CPU leads to a stack underflow, with the first four bytes of whatever sub CPU code file was previously run loaded into the PC as a return address. Granted, this could have been a means for the developers to detect invalid commands, but the v0.02 proto's version of this routine doesn't perform any validation of commands, and I can't find any disassembles of any other protos offhand to see what they did, so I only assume this was unintentional. (The +1 in the cmpi.w is also a mistake, it was meant to produce the ID of the first invalid command, but is not necessary here due to the presence of a 'dc.w 0' dummy entry at the start of the command table.) Anyhow, to fix this bug, we need to rework the routine so that SPCmdFinish is called rather than branched to for invalid commands. The following is how I fixed it in my test ROM (which also changes the validation check slightly and eliminates the need for a dummy entry at the start of the command table): Code (ASM): MainLoop: move.w (mcd_maincom_0).w,d0 ; get command ID from main CPU beq.s MainLoop ; wait if not set cmp.w (mcd_maincom_0).w,d0 ; why do this (a safeguard against spurious writes)? bne.s MainLoop cmpi.w #sizeof_SubCPUCmd_Index/2,d0 ; is it a valid command? bhi.s .invalid ; branch if not ; move.w d0,d1 ; required in the original code, since the id is used by the LoadLevel command add.w d0,d0 move.w SubCPUCmd_Index-2(pc,d0.w),d0 ; minus 2 since IDs start at 1; eliminates the need for a 'dc.w 0' dummy entry jsr SubCPUCmd_Index(pc,d0.w) ; run the command bra.s MainLoop ; =========================================================================== .invalid: bsr.s CmdFinish ; call the command finish routine instead of branching bra.s MainLoop
That "+1" was a mistake, because "(.SPCmdsEnd-.SPCmds)/2" ends refers to whatever data *after* the table, because ".SPCmdsEnd" is placed after the last entry. Even then, because the original command table does include a dummy entry for command ID 0, so there's no need for that offset to begin with. You also need that "move.w d0,d1", because the level loading function relies on it.
You're right, I made a math mistake there (this is what happens when you're tired). It's been fixed. I removed the move.w d0,d1 since it wasn't needed for what I was doing, but I've added a clarification.
In Collision Chaos/R3, there is an object called "Miracle", and the only thing it does is disappear (calls frameout). Anyone have any idea what it is?
Might be a good idea to share the object code so those of us (who are unable to actively acquire the source at this current moment to investigate) can help d=
I *think* it might be this object that's found in the 510 prototype. Its code was completely dummied out in the final. Does it happen to be ID 0x2B?
There isn't anything more to share, unfortunately. This is literally all there is in the file: Code (Text): void miracle(sprite_status* actionwk) { frameout(actionwk); }