i wish i knew about this when i still made rom hacks....... it was so so annoying how much ihad to worry about getting suddenly stopped on slopes, i never knew why it happened and it made designing levels so so annoying... anyways this fix is cool as hell LOL !
There is an oversight with invisible solid block collision. At the top of its main routine, it checks if it's offscreen: Code (Text): Invis_Solid: ; Routine 2 bsr.w ChkObjectVisible bne.s .chkdel There is a problem though. If we go to the routine: Code (Text): ; --------------------------------------------------------------------------- ; Subroutine to check if an object is off screen ; output: ; d0 = flag set if object is off screen ; --------------------------------------------------------------------------- ; ||||||||||||||| S U B R O U T I N E ||||||||||||||||||||||||||||||||||||||| ChkObjectVisible: move.w obX(a0),d0 ; get object x-position sub.w (v_screenposx).w,d0 ; subtract screen x-position bmi.s .offscreen cmpi.w #320,d0 ; is object on the screen? bge.s .offscreen ; if not, branch move.w obY(a0),d1 ; get object y-position sub.w (v_screenposy).w,d1 ; subtract screen y-position bmi.s .offscreen cmpi.w #224,d1 ; is object on the screen? bge.s .offscreen ; if not, branch moveq #0,d0 ; set flag to 0 rts .offscreen: moveq #1,d0 ; set flag to 1 rts ; End of function ChkObjectVisible It doesn't take into account the size of the hitbox, just the position. Now, it's not very common at all to run into this issue, but you can easily see the effect if you duck the camera down in this area: To fix this, add this routine that accounts for the hitbox: Code (Text): ; --------------------------------------------------------------------------- ; Subroutine to check if an object is off screen ; Takes both width and height into account ; output: ; d0 = flag set if object is off screen ; --------------------------------------------------------------------------- ; ||||||||||||||| S U B R O U T I N E ||||||||||||||||||||||||||||||||||||||| ChkSizedObjVisible: moveq #0,d1 ; Get object's width move.b obActWid(a0),d1 move.w obX(a0),d0 ; Get object's X position sub.w (v_screenposx).w,d0 ; Get object's X position on screen add.w d1,d0 ; Is the right side of the object on screen? bmi.s .offscreen2 ; If not, branch add.w d1,d1 ; Is the left side of the object on screen? sub.w d1,d0 cmpi.w #320,d0 bge.s .offscreen2 ; If not, branch moveq #0,d1 ; Get object's height move.b obHeight(a0),d1 move.w obY(a0),d0 ; Get object's Y position sub.w (v_screenposy).w,d0 ; Get object's Y position on screen add.w d1,d0 ; Is the bottom side of the object on screen? bmi.s .offscreen2 ; If not, branch add.w d1,d1 ; Is the top side of the object on screen? sub.w d1,d0 cmpi.w #224,d1 bge.s .offscreen2 ; If not, branch moveq #0,d0 ; Visible rts .offscreen2: moveq #1,d0 ; Not visible rts And change the call to ChkObjectVisible to ChkSizedObjVisible instead, and tada!
Small bug that's probably really insignificant in the grand scheme of things, but I thought it would be worth documenting. The plasma balls in Final Zone's movement code when it first gets spawned has a small bug in it. If we go to loc_1A9C0 in "_incObj/86 FZ Plasma Ball Launcher.asm", we can see this: Code (Text): move.w obX(a0),d0 sub.w $30(a0),d0 bcc.s loc_1A9E6 clr.w obVelX(a0) add.w d0,obX(a0) ; <-- BUG The first 2 lines get the distance between the plasma ball's current X position and its target X position. If it has moved past that target X position, that distance value should be negative. The branch after checks if the result underflowed (has gone negative), and if not, it skips over the rest. If it HAS, then it stops its movement and attempts to align it to the target X position by nudging it by the amount that it has moved past. The bug in question is that it uses an addition to do that alignment. When you add a negative number, it's a subtraction. In this case, it's basically moving the plasma ball MORE to the left, instead of pushing it towards the right to its actual target X position. To fix that, just change the add to a sub. Now, if you're okay with the balls spreading out a bit less like this, you can stop here. In fact, this is the same behavior as the 2013 Taxman remake, because it doesn't have that bug in it. But, if you want them to actually spread out further, then go to Obj86_Loop and change Code (Text): muls.w #-$4F,d1 to Code (Text): muls.w #-$59,d1 and you'll get this again. Spoiler: Bonus 2013 Taxman remake fix If you want to have the plasma balls spread out further without adding this bug, then in "SBZ/FZEggman.txt", in the FZEGGMAN_SETUP_PLASMAATTACK case, change the subtraction of 0x4F0000 to 0x590000.
The trap doors in Scrap Brain Zone have a small bug where if you are off screen, but not enough to despawn them, their sprite will wrap over and appear at the edge of the screen. For example, here's this trap door And if I move a bit to the left, but not have it despawned yet... The reason for this is that it has a pretty ridiculously high sprite width set on initialization. Code (Text): move.b #$80,obActWid(a0) The trap door sprite is $80 pixels wide, but the value set here is supposed to be half of that. The fix is simple: change the $80 to a $40. It should be noted that the sound for when it moves is only ever played when the object is on screen. Due to the larger width, the sound could play even with it was just slightly off screen, but with this change, it will no longer play if the object is not off screen at all. If you want to retain that behavior, then you'll have to manually check the camera's position.
So... wonder why you can't die at all in debug mode? It turns out the culprit lies in the line of code pointed with the arrow: Code (Text): HIVEBRAIN 2005: HurtSonic: tst.b ($FFFFFE2C).w ; does Sonic have a shield? bne.s Hurt_Shield ; if yes, branch tst.w ($FFFFFE20).w ; does Sonic have any rings? beq.w Hurt_NoRings ; if not, branch <--- This line jsr SingleObjLoad bne.s Hurt_Shield move.b #$37,0(a1) ; load bouncing multi rings object move.w 8(a0),8(a1) move.w $C(a0),$C(a1) Code (Text): GITHUB: HurtSonic: tst.b (v_shield).w ; does Sonic have a shield? bne.s @hasshield ; if yes, branch tst.w (v_rings).w ; does Sonic have any rings? beq.w @norings ; if not, branch <--- This line The code for that particular routine just has a check for debug mode and branching to the shield injury routine: Code (Text): HIVEBRAIN 2005: Hurt_NoRings: tst.w ($FFFFFFFA).w ; is debug mode cheat on? bne.w Hurt_Shield ; if yes, branch ; End of function HurtSonic Code (Text): GITHUB: @norings: tst.w (f_debugmode).w ; is debug mode cheat on? bne.w @hasshield ; if yes, branch So in HurtSonic, if you replace the pointed branch to Hurt_NoRings/@norings with KillSonic, the lack of rings properly kills Sonic regardless of being in debug mode or not. It's odd that no revision of Sonic 1 ever noticed this, much less any rom hacks of the game.
While this is fine and dandy, I don't think it's a bug. Having the debug mode disable dying makes sense, at least to me it does.
Same thing in Sonic CD. The function playdieset() is responsible for setting the necessary values if Sonic has to die. The first few lines check if edit mode is on, and if so, it immediately bails: Code (Text): if (editmode.w) return -1; But that's not all. The PC port also has an actual debug mode. In playdamage(), if you have no rings, you get damaged instead of dying if debug mode is on: Code (Text): if (debugflag.w) { playdamagechk(pActwk, pColliAct); return -1; } return playdieset(pActwk);
Yeah I'm guessing it'd be more of a consistency thing for how S2 and S3K handle deaths while in debug mode, ain't worth losin' sleep over it for sure
As I recall, it is a cheat on its own. In the Japanese version, you can enable four different cheats with four different combinations of U D L R (press C zero times, two times, four times, and six times). In the American version, it just enables everything. But I do think it's be nice if he could actually die if for no other reason than to restart the level, which is a legitimate reason to *not* have invulnerability. I like how they handled cheating death in the later games.
I've noticed that with this code tweak applied, the player can't go into debug mode to recover from a death, which definitely adds to their idea of cheating death in said mode (although not exact but oh well).