Sonic Advance 2
Dimps and Sonic Team's 2002 Game Boy Advance platformer, the second Sonic Advance, read through the SAT-R decompilation.
On this page
The atlas reads Sonic Advance 2 through SAT-R’s decompilation, C that builds the US release by default and shares one player file with Sonic Advance. Names quoted here are the decompilers’. Nothing has been measured in the ROM yet.
The jump is Sonic 1’s scaled down. The take-off of 4.875 px/frame1,1, the gravity2, and the release cap3 are each three quarters of Sonic 1’s value. The top speed isn’t scaled: holding forward builds to 6 px/frame, as in Sonic 14. The acceleration toward it rises with the rings held, from 8 subpixels a frame with 10 or fewer to 18 with 150, and letting go slows the run by a fixed 85.
Boost mode works like a hidden P-meter on the running speed. Holding forward at the top speed adds the acceleration to a counter, in the branch where Sonic 2 keeps a speed above the top. A full counter starts boost mode, after 256 frames with 10 rings or fewer and 60 with 1506. Boosting raises the top speed to 12 px/frame. Among the things that end it are a hit, the start of Tails’s or Cream’s flight, and a ground speed below 4.57.
The spin dash is fully charged from the first press. Every press raises the charge to at least the full mark, so the press that starts it already gives the top release speed, 9 px/frame. Sonic 2’s starting press only clears the counter, and its full charge, 12 px/frame, takes five presses in all8.
Mechanics
| Mechanic | Kind | How |
|---|---|---|
| Running | P-meter | holding forward at the 6 px/frame top speed fills a hidden counter, faster with more rings, and boost mode then raises the top speed to 12 |
| Jump | One speed | 4.875 px/frame along the ground's normal, three quarters of Sonic 1's |
| Gravity | One gravity | 42/256 px/frame² in a jump, held or not, three quarters of Sonic 1's |
| Variable jump height | Release cap | a release cuts a rise faster than 3 px/frame to 3, three quarters of Sonic 1's cap |
| Spin dash | One press | one press is a full charge, 9 px/frame on release, which leaks by a 32nd each frame |
Every claim about this game, by concept (8)
References
- ^ ab
Player_InitJump, which doesn't test the character, addsPLAYER_JUMP_HEIGHT, 4.875 px/frame (2.625 in water), to the speed along the ground's normal (rotation - 64). Sonic 1'sSonic_Jumpaddsson_jumpspeed,$680(6.5 px/frame), along the normal the same way, so Sonic Advance 2's take-off is three quarters of it (computed).CodeSAT-R/sa2 · sonicretro/s1disasmHow we know
23#define PLAYER_JUMP_HEIGHT (4.875) 24#define PLAYER_JUMP_HEIGHT_UNDER_WATER (2.625)
include/game/shared/parameters/characters.h lines 23–24 at 393357f · upstream ↗ 6939void Player_InitJump(Player *p) ⋮ 6980 jumpHeight = (p->moveState & MOVESTATE_IN_WATER) ? Q(PLAYER_JUMP_HEIGHT_UNDER_WATER) : Q(PLAYER_JUMP_HEIGHT); ⋮ 6985 rot = p->rotation - 64; ⋮ 6987 accelX = I(COS_24_8(rot * 4) * jumpHeight); 6988 p->qSpeedAirX += accelX; ⋮ 6990 accelY = I(SIN_24_8(rot * 4) * jumpHeight); 6991 p->qSpeedAirY += accelY;
src/game/shared/stage/player.c lines 6939–7004 at 393357f · upstream ↗ 8son_jumpspeed: equ $680 ; Sonic's jump speed_incObj/01 Sonic.asm line 8 at 15dfa3a · upstream ↗ 1218 move.w #son_jumpspeed,d2 ; set initial jump force_incObj/01 Sonic.asm line 1218 at 15dfa3a · upstream ↗ 1226 subi.b #$40,d0 ; rotate it perpendicularly for jump trajectory 1227 jsr (CalcSine).l ; find the direction Sonic should jump
_incObj/01 Sonic.asm lines 1224–1233 at 15dfa3a · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-22.
Checked by a second AI model, 2026-10-22.
- ^Each frame of a jump, button held or not,
Player_JumpingaddsPLAYER_GRAVITY, 42/256 px/frame² (12/256 in water), to the vertical speed throughPLAYERFN_UPDATE_AIR_FALL_SPEED, except on a frame where Sonic's forward thrust or a mid-air move starts (Player_Sonic_TryForwardThrust,Player_TryMidAirAction):Player_Jumpingreturns before the gravity then. That is three quarters of the gravity Sonic 1's airborne-rolling mode,Sonic_MdJump2, adds throughObjectFall,$38(56/256) a frame (computed).CodeSAT-R/sa2 · sonicretro/s1disasmrevisedHow we know
18#define PLAYER_GRAVITY (42.0 / 256.0) 19#define PLAYER_GRAVITY_UNDER_WATER (12.0 / 256.0)
include/game/shared/parameters/characters.h lines 18–19 at 393357f · upstream ↗ 200#elif (GAME == GAME_SA2) 201#ifndef COLLECT_RINGS_ROM 202#define PLAYERFN_UPDATE_AIR_FALL_SPEED(player) \ 203 if (player->moveState & MOVESTATE_IN_WATER) { \ 204 player->qSpeedAirY += Q(PLAYER_GRAVITY_UNDER_WATER); \ 205 } else { \ 206 player->qSpeedAirY += Q(PLAYER_GRAVITY); \
src/game/shared/stage/player.c lines 200–207 at 393357f · upstream ↗ 7017 if (p->moveState & MOVESTATE_100) { 7018#ifndef COLLECT_RINGS_ROM 7019 if (gGameMode != GAME_MODE_MULTI_PLAYER_COLLECT_RINGS) 7020 if (Player_Sonic_TryForwardThrust(p) || Player_TryMidAirAction(p)) 7021 return;
src/game/shared/stage/player.c lines 7017–7021 at 393357f · upstream ↗ 7039 SA2_LABEL(sub_80232D0)(p); ⋮ 7041 PLAYERFN_UPDATE_AIR_FALL_SPEED(p); 7042 PLAYERFN_UPDATE_POSITION(p);
src/game/shared/stage/player.c lines 7039–7042 at 393357f · upstream ↗ 5gravity: equ $38 ; gravity constant used by many objects_incObj/sub ObjectFall & SpeedToPos.asm line 5 at 15dfa3a · upstream ↗ 359Sonic_MdJump2: ; While Sonic is in the air and rolling (usually, but not limited to, jumping) ⋮ 363 jsr (ObjectFall).l ; apply gravity and update Sonic's position based on his current velocities
_incObj/01 Sonic.asm lines 359–363 at 15dfa3a · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-22.
Revised after a check by a second AI model, 2026-10-22.
- ^In a jump (
MOVESTATE_100, whichPlayer_InitJumpsets), on any frame the jump button isn't held,Player_Jumpingcuts a rise faster than 3 px/frame to exactly −3 (PLAYER_MAX_NOT_HELD_JUMP_FORCE; −1.5 in water). Outside the Collect Rings multiplayer mode it first tries Sonic's forward thrust and the mid-air moves (Player_Sonic_TryForwardThrust,Player_TryMidAirAction), and a frame on which one starts skips the cap. Sonic 1'sSonic_JumpHeightcuts to 4 px/frame (-$400), so this cap too is three quarters of Sonic 1's (computed).CodeSAT-R/sa2 · sonicretro/s1disasmHow we know
28#define PLAYER_MAX_NOT_HELD_JUMP_FORCE (3.0) 29#define PLAYER_MAX_NOT_HELD_JUMP_FORCE_UNDER_WATER (1.5)
include/game/shared/parameters/characters.h lines 28–29 at 393357f · upstream ↗ 6965 p->moveState |= (MOVESTATE_100 | MOVESTATE_IN_AIR);src/game/shared/stage/player.c line 6965 at 393357f · upstream ↗ 7006void Player_Jumping(Player *p) ⋮ 7009 s16 maxJumpSpeed = -Q(PLAYER_MAX_NOT_HELD_JUMP_FORCE); ⋮ 7012 if (p->moveState & MOVESTATE_IN_WATER) { 7013 maxJumpSpeed = -Q(PLAYER_MAX_NOT_HELD_JUMP_FORCE_UNDER_WATER); ⋮ 7017 if (p->moveState & MOVESTATE_100) { ⋮ 7019 if (gGameMode != GAME_MODE_MULTI_PLAYER_COLLECT_RINGS) 7020 if (Player_Sonic_TryForwardThrust(p) || Player_TryMidAirAction(p)) 7021 return; ⋮ 7024 // Caps the jump force if the player lets go of the jump button 7025 if (p->qSpeedAirY < maxJumpSpeed && !(p->heldInput & gPlayerControls.jump)) { 7026 p->qSpeedAirY = maxJumpSpeed;
src/game/shared/stage/player.c lines 7006–7027 at 393357f · upstream ↗ 1273Sonic_JumpHeight: ⋮ 1276 move.w #-$400,d1 ; set max jump height ⋮ 1283 cmp.w obVelY(a0),d1 ; get current y speed 1284 ble.s .return ; is Sonic moving up slower than the cap speed? if yes, branch 1285 move.b (v_jpadhold2).w,d0 ; get currently held buttons 1286 andi.b #btnABC,d0 ; is A, B or C still held after jumping? 1287 bne.s .return ; if yes, branch 1288 move.w d1,obVelY(a0) ; otherwise, cap Sonic's maximum jump height
_incObj/01 Sonic.asm lines 1273–1288 at 15dfa3a · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-22.
Checked by a second AI model, 2026-10-22.
- ^Outside boost mode
Player_ApplyBoostPhysicssetstopSpeed, the speed holding forward builds to inPlayer_HandleGroundMovement, to 6 px/frame (Q(6.0)), Sonic 1'sson_maxspeed($600). A faster speed, from a slope, is cut only atmaxSpeed, bysub_8023260, which the ground state (Player_Idle) calls after adding the slope term: 9 px/frame, or 15 whileMOVESTATE_SPIN_ATTACKis set, as a spin dash sets it.CodeSAT-R/sa2 · sonicretro/s1disasmHow we know
9912void Player_ApplyBoostPhysics(Player *p) ⋮ 9914 u32 boostThreshold = p->boostThreshold; 9915 if (p->isBoosting) { 9916 p->topSpeed = Q(12.0); 9917 p->maxSpeed = Q(15.0); 9918 } else if (p->moveState & MOVESTATE_SPIN_ATTACK) { 9919 p->topSpeed = Q(6.0); 9920 p->maxSpeed = Q(15.0); 9921 } else { 9922 p->topSpeed = Q(6.0); 9923 p->maxSpeed = Q(9.0);
src/game/shared/stage/player.c lines 9912–9924 at 393357f · upstream ↗ 8974 if (p->qSpeedGround < p->topSpeed) { 8975 p->qSpeedGround += acc; ⋮ 8977 if (p->qSpeedGround > p->topSpeed) { 8978 p->qSpeedGround = p->topSpeed;
src/game/shared/stage/player.c lines 8974–8979 at 393357f · upstream ↗ 6658 Player_HandleGroundMovement(p); ⋮ 6660 if (((p->rotation + Q(0.375)) & 0xFF) < 0xC0) { 6661 s32 acceleration = GET_ROTATED_ACCEL(p->rotation); ⋮ 6664 p->qSpeedGround += acceleration; ⋮ 6669 SA2_LABEL(sub_8023260)(p);
src/game/shared/stage/player.c lines 6658–6669 at 393357f · upstream ↗ 3383void SA2_LABEL(sub_8023260)(Player *p) ⋮ 3385 s32 maxSpeed = p->maxSpeed; ⋮ 3387 if (p->qSpeedGround > (s16)maxSpeed) { 3388 p->qSpeedGround = +maxSpeed;
src/game/shared/stage/player.c lines 3383–3388 at 393357f · upstream ↗ 7190 p->moveState |= (MOVESTATE_SPINDASH | MOVESTATE_SPIN_ATTACK);src/game/shared/stage/player.c line 7190 at 393357f · upstream ↗ 5son_maxspeed: equ $600 ; Sonic's max speed_incObj/01 Sonic.asm line 5 at 15dfa3a · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-22.
Checked by a second AI model, 2026-10-22.
- ^In Sonic Advance 2 the ground acceleration rises with the ring count. At the start of each frame
Player_HandleBoostThresholdpicks a bracket from the rings andPlayer_ApplyBoostPhysicssets the acceleration fromplayerBoostPhysicsTable: 8 subpixels (1/256 px/frame) a frame with 10 rings or fewer, 12 with 11 to 49, 14 with 50 to 99, 16 with 100 to 149, and 18 with 150 or more. The deceleration, taken when the d-pad is held against the motion, is 64 in every bracket, and the speed-up item doubles both. With neither Left nor Right held, or whileunk2Acounts down (a 30-frame timer set when the player slips off a steep slope, in the atlas's reading),Player_HandleGroundMovementtakes a fixed 8 a frame off the speed toward 0. Sonic 1's rates are$C(12) to accelerate, the same$Cfor the friction, and$80(128) against the motion.CodeSAT-R/sa2 · sonicretro/s1disasmHow we know
4899#if (GAME == GAME_SA2) 4900 Player_HandleBoostThreshold(p); 4901 Player_HandleBoostState(p); 4902 Player_ApplyBoostPhysics(p);
src/game/shared/stage/player.c lines 4899–4902 at 393357f · upstream ↗ 9107void Player_HandleBoostThreshold(Player *p) ⋮ 9109 if (gRingCount >= 150) { 9110 p->boostThreshold = 4; 9111 } else if (gRingCount >= 100) { 9112 p->boostThreshold = 3; 9113 } else if (gRingCount >= 50) { 9114 p->boostThreshold = 2; 9115 } else if (gRingCount > 10) { 9116 p->boostThreshold = 1; 9117 } else { 9118 p->boostThreshold = 0;
src/game/shared/stage/player.c lines 9107–9120 at 393357f · upstream ↗ 552static const s16 playerBoostPhysicsTable[5][2] = { 553 { 8, 64 }, { 12, 64 }, { 14, 64 }, { 16, 64 }, { 18, 64 },
src/game/shared/stage/player.c lines 552–553 at 393357f · upstream ↗ 9926 p->acceleration = playerBoostPhysicsTable[boostThreshold][0]; 9927 p->deceleration = playerBoostPhysicsTable[boostThreshold][1];
src/game/shared/stage/player.c lines 9926–9927 at 393357f · upstream ↗ 4907 Player_HandleInputs(p); ⋮ 4910 Player_HandleWater(p);
src/game/shared/stage/player.c lines 4907–4910 at 393357f · upstream ↗ 4429 if (p->itemEffect & PLAYER_ITEM_EFFECT__SPEED_UP) { ⋮ 4432 p->acceleration = p->acceleration * 2; 4433 p->deceleration = p->deceleration * 2; 4434#else 4435 p->acceleration = p->acceleration * 2; 4436 p->deceleration = p->deceleration * 2;
src/game/shared/stage/player.c lines 4429–4437 at 393357f · upstream ↗ 6652 if (!Player_TryTaunt(p) && !Player_TryCrouchOrSpinAttack(p) && !Player_TryJump(p) ⋮ 6658 Player_HandleGroundMovement(p);
src/game/shared/stage/player.c lines 6652–6658 at 393357f · upstream ↗ 8965void Player_HandleGroundMovement(Player *p) ⋮ 8967 s8 charState = CHARSTATE_INVALID; 8968 s32 acc = p->acceleration; 8969 s32 dec = p->deceleration; ⋮ 8971 if ((p->SA2_LABEL(unk2A) == 0) && p->heldInput & (DPAD_LEFT | DPAD_RIGHT)) { 8972 if (p->qSpeedGround > 0) { 8973 if (p->heldInput & DPAD_RIGHT) { 8974 if (p->qSpeedGround < p->topSpeed) { 8975 p->qSpeedGround += acc;
src/game/shared/stage/player.c lines 8965–8975 at 393357f · upstream ↗ 8984 } else if (p->qSpeedGround >= Q(2.0)) { ⋮ 8994 p->qSpeedGround -= dec;
src/game/shared/stage/player.c lines 8984–8994 at 393357f · upstream ↗ 9063 } else { 9064 s32 grndSpeed = p->qSpeedGround; 9065 if (grndSpeed > 0) { 9066 s16 val = grndSpeed - Q(8.0 / 256.0); 9067 if (val <= 0) { 9068 val = 0; ⋮ 9070 } else {
src/game/shared/stage/player.c lines 9063–9074 at 393357f · upstream ↗ 118#define PLAYERFN_UPDATE_UNK2A(player) \ 119 { \ 120 if (player->SA2_LABEL(unk2A) != 0) { \ 121 player->SA2_LABEL(unk2A) -= 1; \ 122 } else if ((player->rotation + Q(0.125)) & 0xC0) { \ 123 if (ABS(player->qSpeedGround) < Q(1.875)) { \ ⋮ 127 player->SA2_LABEL(unk2A) = GBA_FRAMES_PER_SECOND / 2; \
src/game/shared/stage/player.c lines 118–127 at 393357f · upstream ↗ 5son_maxspeed: equ $600 ; Sonic's max speed 6son_acceleration: equ $C ; Sonic's acceleration 7son_deceleration: equ $80 ; Sonic's deceleration
_incObj/01 Sonic.asm lines 5–7 at 15dfa3a · upstream ↗ 499Sonic_CheckDpadLetGo: ⋮ 501 andi.b #btnL+btnR,d0 ; is left or right held? ⋮ 506 sub.w d5,d0 ; reduce current rightward speed by acceleration
_incObj/01 Sonic.asm lines 499–506 at 15dfa3a · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-22.
Checked by a second AI model, 2026-10-22.
- ^Boost mode starts from a run held at the top speed. Holding forward on the ground at 6 px/frame or faster,
Player_HandleGroundMovementleaves the speed as it is and adds the frame's acceleration toboostSpeed, in the branch where Sonic 2'sSonic_MoveRightkeeps a speed above the top and Sonic 1's cuts it back to the top. At the start of each framePlayer_HandleBoostStateturns boost mode on onceboostSpeedreaches the ring bracket's entry inplayerBoostThresholdTable:Q(8)(2048) with 10 rings or fewer, 2040 with 11 to 49, 1680 with 50 to 99, 1440 with 100 to 149, and 1080 with 150 or more. In the air or below the top speed it setsboostSpeedback to 0. With each bracket's acceleration that is 256, 170, 120, 90, and 60 frames of holding forward at the top speed, and half as many with the speed-up item (computed).CodeSAT-R/sa2 · sonicretro/s2disasm · sonicretro/s1disasmHow we know
8972 if (p->qSpeedGround > 0) { 8973 if (p->heldInput & DPAD_RIGHT) { 8974 if (p->qSpeedGround < p->topSpeed) { ⋮ 8980 } else { 8981 p->boostSpeed += acc;
src/game/shared/stage/player.c lines 8972–8982 at 393357f · upstream ↗ 9009 } else if (p->qSpeedGround < 0) { 9010 if (p->heldInput & DPAD_LEFT) { 9011 if (p->qSpeedGround > -p->topSpeed) { ⋮ 9017 } else { 9018 p->boostSpeed += acc;
src/game/shared/stage/player.c lines 9009–9019 at 393357f · upstream ↗ 556static const s16 playerBoostThresholdTable[5] = { Q(8), Q(7.96875), Q(6.5625), Q(5.625), Q(4.21875) };src/game/shared/stage/player.c line 556 at 393357f · upstream ↗ 4899#if (GAME == GAME_SA2) 4900 Player_HandleBoostThreshold(p); 4901 Player_HandleBoostState(p); 4902 Player_ApplyBoostPhysics(p);
src/game/shared/stage/player.c lines 4899–4902 at 393357f · upstream ↗ 9122void Player_HandleBoostState(Player *p) ⋮ 9124 bool32 isBoostActive = p->isBoosting; 9125 if (isBoostActive) { ⋮ 9134 } else { 9135 if ((!(p->moveState & MOVESTATE_IN_AIR)) && ((ABS(p->qSpeedGround) >= p->topSpeed))) { 9136 if (p->boostSpeed >= playerBoostThresholdTable[p->boostThreshold]) { 9137 p->isBoosting = TRUE; ⋮ 9144 } else { 9145#ifndef NON_MATCHING 9146 p->boostSpeed = isBoostActive;
src/game/shared/stage/player.c lines 9122–9150 at 393357f · upstream ↗ 552static const s16 playerBoostPhysicsTable[5][2] = { 553 { 8, 64 }, { 12, 64 }, { 14, 64 }, { 16, 64 }, { 18, 64 },
src/game/shared/stage/player.c lines 552–553 at 393357f · upstream ↗ 4429 if (p->itemEffect & PLAYER_ITEM_EFFECT__SPEED_UP) { ⋮ 4432 p->acceleration = p->acceleration * 2; ⋮ 4435 p->acceleration = p->acceleration * 2;
src/game/shared/stage/player.c lines 4429–4437 at 393357f · upstream ↗ 36998 add.w d5,d0 ; add acceleration to the right 36999 cmp.w d6,d0 ; compare new speed with top speed 37000 blt.s + ; if new speed is less than the maximum, branch 37001 sub.w d5,d0 ; remove this frame's acceleration change 37002 cmp.w d6,d0 ; compare speed with top speed 37003 bge.s + ; if speed was already greater than the maximum, branch 37004 move.w d6,d0 ; limit speed on ground going right
s2.asm lines 36998–37004 at 4ef457c · upstream ↗ 716 add.w d5,d0 ; add acceleration to current ground speed 717 cmp.w d6,d0 ; is new speed above max speed? 718 blt.s .nocap ; if not, branch 719 move.w d6,d0 ; cap Sonic's ground speed
_incObj/01 Sonic.asm lines 716–719 at 15dfa3a · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-22.
Checked by a second AI model, 2026-10-22.
- ^In boost mode
Player_ApplyBoostPhysicsraisestopSpeed, the speed holding forward builds to, from 6 to 12 px/frame, twice Sonic 1's single top speed (son_maxspeed,$600; computed), andmaxSpeed, the ground cap, to 15; the acceleration still follows the ring count.Player_HandleBoostStatelooks for the end of boost mode only on the ground, where a ground speed below 4.5 px/frame ends it. Among the other ends are a hit (Player_InitHurt), the start of Tails's or Cream's flight, a multiplayer finish, the special stage transition, andInitializePlayer.CodeSAT-R/sa2 · sonicretro/s1disasmrevisedHow we know
9912void Player_ApplyBoostPhysics(Player *p) ⋮ 9914 u32 boostThreshold = p->boostThreshold; 9915 if (p->isBoosting) { 9916 p->topSpeed = Q(12.0); 9917 p->maxSpeed = Q(15.0); ⋮ 9920 p->maxSpeed = Q(15.0); ⋮ 9926 p->acceleration = playerBoostPhysicsTable[boostThreshold][0];
src/game/shared/stage/player.c lines 9912–9927 at 393357f · upstream ↗ 9122void Player_HandleBoostState(Player *p) ⋮ 9124 bool32 isBoostActive = p->isBoosting; 9125 if (isBoostActive) { 9126 if (!(p->moveState & MOVESTATE_IN_AIR)) { 9127 p->boostSpeed = playerBoostThresholdTable[p->boostThreshold]; ⋮ 9129 if (ABS(p->qSpeedGround) < Q(4.5)) { 9130 p->isBoosting = FALSE; 9131 p->boostSpeed = 0;
src/game/shared/stage/player.c lines 9122–9133 at 393357f · upstream ↗ 7856void Player_InitHurt(Player *p) ⋮ 7858 p->timerInvulnerability = 0x78; 7859 p->isBoosting = FALSE;
src/game/shared/stage/player.c lines 7856–7859 at 393357f · upstream ↗ 893void Player_Tails_InitFlying(Player *p) ⋮ 905 p->isBoosting = FALSE;
src/game/sa2/stage/player_callbacks.c lines 893–905 at 393357f · upstream ↗ 632void Player_Cream_InitFlying(Player *p) ⋮ 644 p->isBoosting = FALSE;
src/game/sa2/stage/player_callbacks.c lines 632–644 at 393357f · upstream ↗ 8193void Player_HandleMultiplayerFinish(Player *p) ⋮ 8230 p->isBoosting = FALSE;
src/game/shared/stage/player.c lines 8193–8230 at 393357f · upstream ↗ 10050void Player_InitSpecialStageTransition(Player *p) ⋮ 10057 p->isBoosting = FALSE;
src/game/shared/stage/player.c lines 10050–10057 at 393357f · upstream ↗ 976void InitializePlayer(Player *p) ⋮ 987 if ( ⋮ 999#if (GAME == GAME_SA2) ⋮ 1059#if (GAME == GAME_SA2) 1060 p->transition = 0; 1061 p->unk6E = 0; 1062 p->prevTransition = 0; 1063 p->isBoosting = FALSE;
src/game/shared/stage/player.c lines 976–1063 at 393357f · upstream ↗ 5son_maxspeed: equ $600 ; Sonic's max speed_incObj/01 Sonic.asm line 5 at 15dfa3a · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-22.
Revised after a check by a second AI model, 2026-10-22.
- ^While the spin dash charges, each jump press adds
Q(2.0)(512) toqSpindashAcceland then raises it to at leastQ(8.0)(2048) withMAX, so one press is a full charge, the starting press included:Player_TryInitSpindashrunsPlayer_InitSpindashand thenPlayer_Spindashin the same frame. Each frame Down stays held first takes 1/32 off the charge. Releasing Down sets the ground speed fromsSpinDashSpeedsat the charge's whole part, capped at 8: 6 px/frame plus 3/8 a step, up to 9, so a release on the frame after a press always gives 9 px/frame (computed). In Sonic 2 the starting press setsspindash_counterto 0, and each later press adds$200up to a cap of$800, so a full charge takes five presses in all (computed); its release speeds run from 8 to 12 px/frame.CodeSAT-R/sa2 · sonicretro/s2disasmrevisedHow we know
9898bool32 Player_TryInitSpindash(Player *p) ⋮ 9900 u32 r6 = 0; 9901 if (p->charState == CHARSTATE_CROUCH) { 9902 if (p->frameInput & gPlayerControls.jump) { 9903 PLAYERFN_SET_AND_CALL(Player_InitSpindash, p);
src/game/shared/stage/player.c lines 9898–9904 at 393357f · upstream ↗ 7186void Player_InitSpindash(Player *p) ⋮ 7195 p->qSpindashAccel = 0; ⋮ 7203 PLAYERFN_SET_AND_CALL(Player_Spindash, p);
src/game/shared/stage/player.c lines 7186–7203 at 393357f · upstream ↗ 7212 if (!(p->heldInput & DPAD_DOWN)) { ⋮ 7217 index = I(p->qSpindashAccel); 7218 if (index > 8) 7219 index = 8; ⋮ 7221 speed = sSpinDashSpeeds[index]; ⋮ 7225 p->qSpeedGround = speed;
src/game/shared/stage/player.c lines 7212–7225 at 393357f · upstream ↗ 7230 } else { 7231 s16 pitch = p->qSpindashAccel; ⋮ 7233 s16 pitch2 = pitch; 7234 if (pitch2 != 0) { 7235 pitch = pitch2 - (I(pitch << 3)); ⋮ 7240 if (p->frameInput & gPlayerControls.jump) { ⋮ 7248 pitch += Q(2.0); 7249 pitch = MAX(pitch, Q(8.0));
src/game/shared/stage/player.c lines 7230–7249 at 393357f · upstream ↗ 558static const s16 sSpinDashSpeeds[9] = { 559 Q_8_8(6.000 + 0 * (3. / 8.)), // ⋮ 567 Q_8_8(6.000 + 8 * (3. / 8.)), //
src/game/shared/stage/player.c lines 558–567 at 393357f · upstream ↗ 37605 move.b (Ctrl_1_Press_Logical).w,d0 37606 andi.b #button_B_mask|button_C_mask|button_A_mask,d0 37607 beq.w return_1AC8C ⋮ 37612 move.b #1,spindash_flag(a0) 37613 move.w #0,spindash_counter(a0)
s2.asm lines 37605–37613 at 4ef457c · upstream ↗ 37726 addi.w #$200,spindash_counter(a0) 37727 cmpi.w #$800,spindash_counter(a0) 37728 blo.s Obj01_Spindash_ResetScr 37729 move.w #$800,spindash_counter(a0)
s2.asm lines 37726–37729 at 4ef457c · upstream ↗ 37688SpindashSpeeds: 37689 dc.w $800 ; 0 ⋮ 37697 dc.w $C00 ; 8
s2.asm lines 37688–37697 at 4ef457c · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-22.
Revised after a check by a second AI model, 2026-10-22.
Sources
Code
- SAT-R contributors. 2026. “sa2: a decompilation of Sonic Advance 1 and 2 (SAT-R/sa2).” Commit 393357f. SAT-R/sa2 @ 393357f · licence, all uses
- Sonic Retro contributors. 2026. “s1disasm: Sonic the Hedgehog (16-bit) disassembly (sonicretro/s1disasm).” Commit 15dfa3a. sonicretro/s1disasm @ 15dfa3a · licence, all uses
- Sonic Retro contributors. 2026. “s2disasm: Sonic the Hedgehog 2 (16-bit) disassembly (sonicretro/s2disasm).” Commit 4ef457c. sonicretro/s2disasm @ 4ef457c · licence, all uses