Variable jump height
A jump whose height depends on how long the jump button is held.
On this page
Tap jump for a hop; hold it for a full jump. Super Mario Bros., Sonic the Hedgehog, and Celeste all do this, by three different mechanisms.
Mario falls under lighter gravity while the button is held1. Releasing it caps Sonic’s upward speed2. Celeste holds Madeline’s take-off speed on a timer, then halves gravity near the apex3,4.
#How it works
All three games run one loop per update (one frame): gravity reduces the upward speed, and the speed moves the position. The button changes a different term in each.
| Super Mario Bros. | Sonic the Hedgehog | Celeste | |
|---|---|---|---|
| Holding | lighter gravity, 0.117–0.156 px/frame² ($1E–$28)5 | nothing; one gravity, 0.21875 px/frame² ($38)6 | keeps the take-off speed for up to 0.2 s7; half gravity near the apex4 |
| Releasing | heavier gravity, 0.375–0.5625 px/frame² ($60–$90)8, for the rest of the jump1 | clamps upward speed to −4 px/frame (-$400)2 | ends the hold; the current speed carries on3 |
| Apex, held vs tapped | 66–83 px held, 24–29 px tapped9 | 100 px held, 39 px tapped10 | 27 px held, 7 px tapped11 |
Heights are native pixels at the character’s feet, from flat ground, simulated frame by frame in each game’s own number format and update order. An independent re-implementation reproduces every figure (probes/jump-physics/jump-heights.mjs).
#Implementations
#Super Mario Bros. (1985)
A jump starts on the frame A goes down while Mario is on the ground12. His horizontal speed picks a row of four small tables13. The rows give three distinct jumps, for standing, walking, and running14. Each sets a take-off speed of −4 px/frame (−5 running)15,16, a held gravity, and a falling gravity.
on the frame A goes down while standing: # CheckForJumping → InitJS
row = jump row for |x speed|
speed = TAKEOFF[row] # px/frame, fraction zero
gravity = HELD[row]
fallGrav = FALL[row]
every frame in the air: # JumpSwimSub
if speed >= 0: # at or past the apex
gravity = fallGrav
else if not (A held now and A held last frame) and risen >= HALT:
gravity = fallGrav # released; never switched back
position += speed # ImposeGravity, 8.8 fixed point
speed += gravity
if speed has reached MAX_FALL + 1/2: speed = MAX_FALL
The button never pushes Mario up; it picks only the gravity. Once JumpSwimSub swaps in the falling gravity, nothing swaps it back that jump1. The swap waits until Mario has risen 1 px17, which the press frame already covers18.
Held gravity is 0.117–0.156 px/frame² ($1E–$28)5 across the rows; falling gravity is 0.375–0.5625 px/frame² ($60–$90)8. For a standing jump, they are 0.125 px/frame²19 and 0.4375 px/frame²20.
Vertical speeds are 8.8 fixed point, with a third byte for the subpixel position21. Falling speed is capped at 4 px/frame22, but the clamp fires only once the speed’s fraction byte reaches $80, as at 4.5 px/frame23.
Jdaster64’s chart of SMB’s physics engine agrees on the take-off speeds and gravities15,5,8. It starts the fast jump at a higher horizontal speed than the code; the ledger follows the code13.
#Sonic the Hedgehog (1991)
Sonic jumps along the ground’s normal, so on flat ground all 6.5 px/frame ($680)24 goes upward25. The press frame doesn’t move him: Sonic_Jump drops its return address, skipping the rest of the grounded routine26. Curling into a ball lowers his center 5 px27, keeping his feet on the same row27.
on the frame a jump button goes down on the ground: # Sonic_Jump
ySpeed -= JUMP # along the ground normal
curl into a ball # height radius 19 → 14, y += 5
skip the rest of this frame # no movement yet
every frame in the air: # Sonic_MdJump2
if jumping and no jump button held and ySpeed < -CAP:
ySpeed = -CAP # Sonic_JumpHeight
y += ySpeed # ObjectFall: old speed first,
ySpeed += GRAVITY # then gravity
One gravity, 0.21875 px/frame² ($38)6, applies held or not. The release check runs every airborne frame, before the move28. With no jump button held, it clamps any faster rise to −4 px/frame (-$400)2. Gravity brings the take-off speed under the cap in 12 updates29, so later releases change nothing.
Speeds are 8.8 and positions 16.16 fixed point30, and nothing limits the fall31. The Sonic Retro community’s Sonic Physics Guide matches the disassembly, down to the release check2 and the motionless press frame26.
#Celeste (2018)
Celeste works in pixels per second and multiplies by Engine.DeltaTime every update32. A jump sets the vertical speed to -105 px/s33 and starts a 0.2 s7 timer34.
every update:
if varJumpTimer > 0: varJumpTimer -= dt # Player.Update, before the state runs
# NormalUpdate:
if airborne:
mult = 1/2 if |speed| < HALF_GRAV and jump held else 1
speed = approach(speed, MAX_FALL, GRAVITY * mult * dt)
if varJumpTimer > 0:
if jump held: speed = min(speed, varJumpSpeed) # up is negative: keep take-off speed
else: varJumpTimer = 0 # release ends the hold, cuts nothing
if jump pressed and jumpGraceTimer > 0: # Jump()
speed = JUMP_SPEED; varJumpSpeed = speed; varJumpTimer = VAR_JUMP_TIME
moveV(speed * dt) # whole pixels, fraction kept
Holding jump while the timer runs cancels each update’s gravity, so Madeline rises at a constant -105 px/s33,3. Gravity, 900 px/s²35,36, applies once the hold ends. Unlike Sonic’s clamp, an early release cuts no speed3.
A separate rule halves gravity while jump is held and her vertical speed is under 40 px/s37,4. The fall limit is 160 px/s38. Positions are whole pixels, with the fraction kept in a remainder39.
At 60 Hz40, the hold nominally spans 12 updates7, but the timer is a float and its last update rests on rounding. A 0.0166667 s step gives 11 updates; an exact 1/60 s step gives 1241, and 2 px more height11. The released code doesn’t set the step. The widget defaults to 0.0166667 s42, the likelier value (Coyote time explains why).
#Comparing the arcs
- Super Mario Bros. is lopsided: a long, gentle rise, then a fall under 3.5×43 the gravity (standing). A release bends the rise into an early, steep descent1. Held jumps peak at 66 px standing, 71 px walking, and 83 px running44.
- Sonic the Hedgehog is a symmetric parabola under one gravity6. A release cuts the rise to −4 px/frame (
-$400)2 at once, starting a smaller parabola. - Celeste rises in a straight ramp while the hold lasts3, then follows a parabola with a flattened top4.
#History
#Variants and combinations
Celeste also has coyote time, a grace window for late jumps45. Super Mario Bros. and Sonic have none46,47.
References
- ^ abcd
JumpSwimSubswaps in falling gravity, never back, once speed is zero or downward, or once A is not held both this frame and last after aDiffToHaltJumprise. T3Xkeeper0/smb1 · src/prg.asm L4756–4769Jdaster64 n.d. · JUMPING/VERTICAL PHYSICS - ^ abcdeDuring a jump, with no jump button held,
Sonic_JumpHeightclamps any rise faster than 4 px/frame to exactly-$400. T3sonicretro/s1disasm · _incObj/01 Sonic.asm L1273–1288Sonic Retro, “Sonic Physics Guide: Jumping” · Variable Jump Height - ^ abcdeWhile
varJumpTimerruns, holding jump keeps Madeline rising no slower thanvarJumpSpeed, canceling gravity; releasing zeroes the timer and cuts no speed. T3NoelFB/Celeste · Source/Player/Player.cs L2960–2966 - ^ abcdGravity halves while |
Speed.Y| is underHalfGravThresholdand jump is held (or auto-jump is on), so a held jump hangs at the top. T3NoelFB/Celeste · Source/Player/Player.cs L2952 - ^ abcWhile a jump rises with A held, gravity is
JumpMForceData's$20,$20,$1E,$28,$28subpixels/frame² for rows 0–4. T3Xkeeper0/smb1 · src/prg.asm L4858–4859, L4966–4967Jdaster64 n.d. · JUMPING/VERTICAL PHYSICS, Mario table, Holding “A” Gravity (+Y) - ^ abc
ObjectFall's gravity equate adds$38subpixels/frame to an airborne object's downward speed each frame, Sonic's included. T3sonicretro/s1disasm · _incObj/sub ObjectFall & SpeedToPos.asm L5, _incObj/01 Sonic.asm L363Sonic Retro, “Sonic Physics Guide: Air State” · Constants - ^ abc
VarJumpTime, how long holding jump can keep the take-off speed, is 0.2 s. T3NoelFB/Celeste · Source/Player/Player.cs L51 - ^ abcAfter the switch, gravity is
FallMForceData's$70,$70,$60,$90,$90subpixels/frame² for rows 0–4. T3Xkeeper0/smb1 · src/prg.asm L4861–4862, L4968–4969Jdaster64 n.d. · JUMPING/VERTICAL PHYSICS, Mario table, Falling Gravity (+Y) - ^Simulating the jump code on flat ground gives apexes, tapped/held, of 24/66 px standing, 27/71 px walking (row 2), and 29/83 px running (row 4). T4Xkeeper0/smb1 · src/prg.asm L4756–4769, L6664–6696probe
probes/jump-physics/jump-heights.mjs - ^Simulating the jump code (flat ground, out of water) gives apexes of 39 px tapped and 100 px held, airtimes 39 and 62 frames. T4sonicretro/s1disasm · _incObj/01 Sonic.asm L1273–1288, _incObj/sub ObjectFall & SpeedToPos.asm L16–20probe
probes/jump-physics/jump-heights.mjs - ^ abSimulating the jump code (flat ground, no lift boost,
(float)0.0166667step) gives apexes of 7 px tapped and 27 px held, airtimes 15 and 39 updates; an exact 1/60 s step gives 29 px held in 40. T4NoelFB/Celeste · Source/Player/Player.cs L2952–2966, L1660–1673probeprobes/jump-physics/jump-heights.mjs - ^A jump starts only on the frame A goes down while Mario is grounded;
InitJSzeroes the vertical subpixel and sets the jumping state. T3Xkeeper0/smb1 · src/prg.asm L4913–4923, L4931–4942, L4139–4141 - ^ ab
InitJSpicks one of five jump rows by take-off horizontal speed, stepping up at$09,$10,$19, and$1C. T3Xkeeper0/smb1 · src/prg.asm L4943–4955Jdaster64 n.d. · JUMPING/VERTICAL PHYSICS, Mario table - ^Mario's top ground speed is
$18walking and$28running (B held). T3Xkeeper0/smb1 · src/prg.asm L4873–4874, L5000–5014Jdaster64 n.d. · SMB PHYSICS 101 / GROUND PHYSICS, Mario table - ^ ab
PlayerYSpdDatastarts a jump at −4 px/frame (rows 0–2) or −5 px/frame (rows 3–4). T3Xkeeper0/smb1 · src/prg.asm L4864–4865, L4972–4973Jdaster64 n.d. · JUMPING/VERTICAL PHYSICS, Mario table, Initial Velocity (−Y) - ^
InitMForceDatagives jump rows 0–4 a zero speed fraction, so take-off is exactly −4.00 or −5.00 px/frame. T3Xkeeper0/smb1 · src/prg.asm L4867–4868, L4970–4971 - ^
InitJSsetsDiffToHaltJumpto 1, so a release switches gravity only once Mario has risen at least 1 pixel. T3Xkeeper0/smb1 · src/prg.asm L4957–4958 - ^The press frame already moves Mario, since
PlayerPhysicsSubstarts the jump before the state dispatch runsJumpSwimSubandMovePlayerVertically. T3Xkeeper0/smb1 · src/prg.asm L4713–4725, L4796 - ^In rows 0–1, the standing and slow jumps, held gravity is
$20/256 = 0.125 px/frame². T3Xkeeper0/smb1 · src/prg.asm L4858–4859, L6681–6687 - ^In rows 0–1, falling gravity is
$70/256 = 0.4375 px/frame². T3Xkeeper0/smb1 · src/prg.asm L4861–4862, L6681–6687 - ^Vertical motion is 8.8 fixed point; each frame,
ImposeGravityadds speed to position, the fraction via theYMF_Dummysubpixel byte, then gravity to speed. T3Xkeeper0/smb1 · src/prg.asm L6666–6687 - ^
MovePlayerVerticallygivesImposeGravitya maximum fall speed of 4 px/frame for Mario. T3Xkeeper0/smb1 · src/prg.asm L6547–6550, L6688–6696Jdaster64 n.d. · JUMPING/VERTICAL PHYSICS - ^
ImposeGravityclamps a fall of 4 px/frame or more only when the fraction byte is at least$80. T3Xkeeper0/smb1 · src/prg.asm L6688–6696Jdaster64 n.d. · JUMPING/VERTICAL PHYSICS - ^Sonic's jump speed,
son_jumpspeed, is$680subpixels per frame (6.5 px/frame) out of water. T3sonicretro/s1disasm · _incObj/01 Sonic.asm L8, L1218Sonic Retro, “Sonic Physics Guide: Jumping” · Constants - ^
Sonic_Jumpadds the jump speed along the ground normal (CalcSineof angle −$40); on flat ground that is exactly-$680to Y speed, nothing to X. T3sonicretro/s1disasm · _incObj/01 Sonic.asm L1224–1233, _incObj/sub CalcSine.asm L53Sonic Retro, “Sonic Physics Guide: Jumping” · Jump Velocity - ^ abSonic doesn't move on the press frame;
Sonic_Jumpdrops its return address, skipping the rest of the grounded routine,SpeedToPosincluded. T3sonicretro/s1disasm · _incObj/01 Sonic.asm L1236, L317–323Sonic Retro, “Sonic Physics Guide: Jumping” · Bugs when Jumping - ^ abA standing jump curls Sonic up, shrinking his height radius from 19 to 14 and adding 5 to Y, so his feet stay put. T3sonicretro/s1disasm · _incObj/01 Sonic.asm L1250–1254, _Constants.asm L312–314Sonic Retro, “Sonic Physics Guide: Characters” · Sonic
- ^In the airborne-rolling mode (
Sonic_MdJump2), the release check runs beforeObjectFallmoves Sonic and adds gravity. T3sonicretro/s1disasm · _incObj/01 Sonic.asm L359–363Sonic Retro, “Sonic Physics Guide: Jumping” · Variable Jump Height - ^Gravity slows Sonic's 6.5 px/frame take-off below the 4 px/frame release cap within 12 airborne updates. T4sonicretro/s1disasm · _incObj/01 Sonic.asm L8, _incObj/sub ObjectFall & SpeedToPos.asm L5, _incObj/01 Sonic.asm L1276–1284probe
probes/jump-physics/jump-heights.mjs - ^
ObjectFalladds the pre-gravity speed, shifted left 8 bits, to the 32-bit position, so positions are 16.16 and speeds 8.8 fixed point. T3sonicretro/s1disasm · _incObj/sub ObjectFall & SpeedToPos.asm L16–20Sonic Retro, “Sonic Physics Guide: Calculations (Pixel and Subpixel)” · Pixel and Subpixel - ^Sonic 1 never caps falling speed in the air; with
FixBugs = 0,Sonic_JumpHeight's only cap is upward (-$FC0) and skipped after jumps. T3sonicretro/s1disasm · _incObj/01 Sonic.asm L1291–1302Sonic Retro, “Sonic Physics Guide: Air State” · Top Y Speed - ^In each update,
Player.Updateticks both jump timers, the state machine runsNormalUpdate(gravity, hold, and new jump), and thenMoveVappliesSpeed.Y * Engine.DeltaTime. T3NoelFB/Celeste · Source/Player/Player.cs L742–744, L908, L934–935, L322–323JamesMcMahon/monocle-engine · Monocle/Entity.cs L95–98 - ^ abA normal jump sets Madeline's vertical speed to
JumpSpeed, −105 px/s. T3NoelFB/Celeste · Source/Player/Player.cs L49 - ^
Jump()sets vertical speed toJumpSpeedplus any platform lift boost, saves it asvarJumpSpeed, startsvarJumpTimeratVarJumpTime, and zeroes the grace timer. T3NoelFB/Celeste · Source/Player/Player.cs L1660–1673 - ^Celeste's gravity is 900 px/s²; each airborne update moves vertical speed toward the fall limit by
Gravity * mult * Engine.DeltaTime. T3NoelFB/Celeste · Source/Player/Player.cs L24, L2957 - ^In the public Monocle source,
Calc.Approach(val, target, maxMove)movesvaltowardtargetby at mostmaxMovewithout overshooting. T3JamesMcMahon/monocle-engine · Monocle/Util/Calc.cs L852–855 - ^
HalfGravThresholdis 40 px/s, the vertical speed below which (in either direction) holding jump halves gravity. T3NoelFB/Celeste · Source/Player/Player.cs L25 - ^
MaxFall, the normal limit on falling speed, is 160 px/s;NormalBeginstartsmaxFallthere and gravity approaches it. T3NoelFB/Celeste · Source/Player/Player.cs L23, L2760–2763 - ^Celeste's actors sit on whole pixels;
MoveVadds the float distance to a remainder, moves by its rounded value, and keeps the fraction. T2Thorson 2020 · Actor BasicsNoelFB/Celeste · Source/Player/Player.cs L935 - ^Celeste's developers describe the game as locked to 60 updates per second, so the bundled PICO-8 port skips every second frame. T2NoelFB/Celeste · Source/PICO-8/Emulator.cs L268–270
- ^The 0.1 s grace and 0.2 s variable-jump timers end on float rounding; a
(float)0.0166667step gives 5 and 11 updates, and(float)(1/60)gives 6 and 12. T4NoelFB/Celeste · Source/Player/Player.cs L713–720, L742–744WPI DGMI wiki, “Coyote Time” · Examplesprobeprobes/jump-physics/jump-heights.mjs - ^At normal speed, Celeste's
Engine.DeltaTimeis most likely(float)0.0166667s, the XNA and FNA default 166,667-tick step, not an exact 1/60 s. T1FNA-XNA/FNA · src/Game.cs L247NoelFB/Celeste · Source/Player/Readme.md L28JamesMcMahon/monocle-engine · Monocle/Engine.cs L198–199WPI DGMI wiki, “Coyote Time” · Examples - ^In rows 0–1, falling gravity (
$70) is 3.5 times held gravity ($20). T4Xkeeper0/smb1 · src/prg.asm L4858–4862probeprobes/jump-physics/jump-heights.mjs - ^Simulated with A held, Mario's jumps peak at 66 px standing, 71 px walking, and 83 px running. T4Xkeeper0/smb1 · src/prg.asm L4756–4769, L6664–6696probe
probes/jump-physics/jump-heights.mjs - ^Grounded updates refill
jumpGraceTimer; airborne ones subtractEngine.DeltaTimebeforeNormalUpdate, which jumps on a press while it is above zero. T3NoelFB/Celeste · Source/Player/Player.cs L713–720, L2970–2975 - ^Mario gets no grace window after leaving a ledge;
ProcJumpingrefuses airborne jumps unless he is swimming. T3Xkeeper0/smb1 · src/prg.asm L4921–4925 - ^Sonic gets no grace window after leaving a ledge: only the grounded modes,
Sonic_MdNormalandSonic_MdRoll, callSonic_Jump. T3sonicretro/s1disasm · _incObj/01 Sonic.asm L113–116, L318, L348 - ^Swimming jumps use row 5 of the jump tables, or row 6 in a whirlpool. T3Xkeeper0/smb1 · src/prg.asm L4959–4964
Sources
Code
- doppelganger and Xkeeper. 2022. “smb1: Super Mario Bros. disassembly for asm6f (Xkeeper0/smb1).” Commit 1cc3c92. Xkeeper0/smb1 @ 1cc3c92 · licence, all uses
- Sonic Retro contributors. 2026. “s1disasm: Sonic the Hedgehog (16-bit) disassembly (sonicretro/s1disasm).” Commit 15dfa3a. sonicretro/s1disasm @ 15dfa3a · licence, all uses
- Thorson, Maddy and Noel Berry. 2021. “Celeste: released class files (NoelFB/Celeste).” Commit 1b0ce45. NoelFB/Celeste @ 1b0ce45 · licence, all uses
- Thorson, Maddy. 2018. “Monocle Engine (public source, GitHub mirror JamesMcMahon/monocle-engine).” Commit 759c15a. JamesMcMahon/monocle-engine @ 759c15a · licence, all uses
- Lee, Ethan and MonoGame Team. 2026. “FNA (XNA 4.0 reimplementation).” Commit 00498b0. FNA-XNA/FNA @ 00498b0 · licence, all uses
Primary
- Thorson, Maddy. 2020. “Celeste and TowerFall Physics.” Medium. original · archived · licence, all uses
Secondary
- Jdaster64. n.d. “A Complete Guide to SMB's Physics Engine.” original · archived · licence, all uses
- Sonic Retro contributors. 2023. “Sonic Physics Guide: Jumping.” Sonic Retro. original · archived · licence, all uses
- Sonic Retro contributors. n.d. “Sonic Physics Guide: Air State.” Sonic Retro. original · archived · licence, all uses
- Sonic Retro contributors. n.d. “Sonic Physics Guide: Characters.” Sonic Retro. original · archived · licence, all uses
- Sonic Retro contributors. n.d. “Sonic Physics Guide: Calculations (Pixel and Subpixel).” Sonic Retro. original · archived · licence, all uses
- DGMI wiki contributors. n.d. “Coyote Time (DGMI wiki).” Worcester Polytechnic Institute (dgmiwiki.wpi.edu). original · licence, all uses