docs

Physics reference

Every physics constant in the engine, with the disassembly address it came from.

Draft — pending reviewupdated 2026-07-25

This is the engine's provenance registry, rendered as-is. Every constant that governs movement, collision, sprites, or timing appears here with its value, its unit, and the address in the reference game's disassembly that it was traced to.

It exists because the claim this project makes — that the physics are exact — is only worth something if it is checkable. A number in a table is an assertion; a number with an address next to it is evidence.

How to read a row

  • Value — as the engine stores it. Speeds and accelerations are integers in subpixels: 1 px = 16 subpx, and velocities are 16-bit with the low byte a 1/256 fraction, so $0180 means 1.5 subpx per frame per frame. Arrays are tables indexed exactly as the original indexes them; where that indexing is unobvious, the note says how.
  • Status
    • verified: traced to a disassembly address, which is in the row.
    • corroborated: no single address, but agreed by community documentation (TAS resources, forum research) from more than one direction.
    • verify: still open. The engine uses it, a source suggests it, nobody has confirmed it against the disassembly yet. These are marked in the source with a [VERIFY] tag, counted by CI, and the count has to reach zero before launch.
  • Note — why the number is what it is, and any quirk that comes with it. The notes are where the actually surprising behaviour is documented.

Every constant in the engine source carries the same citation as a comment, and a CI check fails the build if a physics constant appears without one. The registry and the code cannot drift apart without the build going red.

The table below shows the address; the full source list behind each constant — which disassembly, which forum thread, which TAS resource — lives in packages/engine/physics-provenance.json, alongside the comment on the constant itself.

Found a disagreement?

That is the most valuable bug report this project can receive. If you can show a case where the engine and the reference game diverge — a frame count, a subpixel, an edge case in a collision — open an issue with the reproduction. During the competition it is also a community prize entry, and a demonstration that lands as a failing test in the repo is the strongest form of it.

100 of 971 shown · registry updated 2026-07-24

ConstantValueUnitStatusAddress
AIR_CATCH_WINDOW_OPTIMAL

Optimal (TAS) window. Preserve exactly.

3
framescorroborated
AIR_CATCH_WINDOW_STANDARD

Standard input window to regain height during flight.

11
framescorroborated
AIR_HORIZONTAL_ACCEL

Identical to ground: the same MarioAccel_ row 0 is used airborne. PlayerInAir only gates the ice-table substitution at $00D753 (ice accel applies on the ground only). IMPORTANT companion fact: releasing the d-pad in mid-air applies NO deceleration - CODE_00D764 drains the P-meter then returns early when PlayerInAir != 0. Air friction of -1.0 applies only while holding a direction at or past the target.

384
1/256 subpx/frame^2verified$00D345
BABY_YOSHI_GROWTH_COUNT

INC SpriteMisc1570 / CMP #$05. Eating any powerup ($74-$77) grows it in one bite instead ($01A2AD).

5
sprites eatenverified$01A2F7
BABY_YOSHI_GROWTH_FREEZE_FRAMES

YoshiGrowingTimer := $40. SpriteLock and PlayerIsFrozen are both held non-zero for the whole window - the world stops. Pose from GrowingAniSequence ($01EBC2), index (timer-1)>>3.

64
framesverified$01A2F1
BABY_YOSHI_GROWTH_Y_RISE

SpriteYPos -= $10 when the baby becomes sprite $35.

16
pxverified$01A2C7
BALLNCHAIN_ANGULAR_STEP

A 9-bit angle in $1602 + $151C stepped by +2 or -2 depending on bit4 of the anchor's X position.

2
units/frameverified$02D62A
BALLNCHAIN_RADIUS

InitBallNChain: $187B = $38. The grey chained platform $A3 uses $30 = 48.

56
pxverified$018533
BALLNCHAIN_RADIUS_GREY_PLATFORM

InitGreyChainPlat: $187B = $30.

48
pxverified$018537
BALLNCHAIN_ROTATION_PERIOD

512 angle units at 2 per frame. Position = anchor + (CircleCoords[angle] * radius) >> 8 on each axis.

256
framesverified$02D62A
BANZAI_BILL_SPAWN_SIDE_RULE

InitBanzai erases the sprite unless SubHorizPos returns Y = 1, i.e. unless the player is to its LEFT.

1
flagverified$018525
BANZAI_BILL_SPEED

LDA #$E8 every frame; no gravity, no level collision. Drawn as a 4x4 tile block (64 x 64 px).

-24
subpx/frameverified$02D590
BASEBALL_XSPEED

BaseballSpeed db $18,$E8, spawned as extended sprite $0D at x + $10 (facing right) / x - $08 (left) from BaseballTileDispX ($02C460, db $10,$F8) with DATA_02C462 (db $00,$FF) as the high bytes, same Y. Baseball ($02A254) calls ONLY the X integrator - no Y integration and no gravity - so the ball flies perfectly horizontally until the screen-edge erase. snesrev ExtSpr0D_Baseball (// 02a254) agrees.

24
subpx/frameverified$02C464
BIG_BOO_AIM_OFFSET_ABOVE_PLAYER

BigBoo's alternate entry point loads $20 instead of $10.

32
pxverified$01F8DC
BLOCKED_ABOVE

The $77 bit the head sensor sets; tryJump and the rising-Vy cancel both read it.

8
bitmaskverified$00EB77
BLOCKED_BELOW

The $77 bit the foot sensors set. Separate arm of RunPlayerBlockCode_EB77 from the side bits.

4
bitmaskverified$00EB77
BLOCKED_LEFT

DATA_00E90A db $01,$02 — the $77 PlayerBlockedDirs bit a left-side sensor sets.

2
bitmaskverified$00E90A
BLOCKED_RIGHT

DATA_00E90A db $01,$02 — the $77 PlayerBlockedDirs bit a right-side sensor sets. Engine-native grouping; SMW never names the set.

1
bitmaskverified$00E90A
BLOCK_BOUNCE_DURATION

bounce_spr_timer ($16C5) = 8 at spawn; the replacement tile is written when it reaches 0.

8
framesverified$028752
BLOCK_BOUNCE_PLAYER_XPUSH

DATA_0290DA db $80,$E0,$20,$80; $80 is the sentinel for 'do not touch the player'. Direction 3 sets player yspeed to 0 via DATA_0290D6.

[null, -32, 32, null]
subpx/frameverified$0290DA
BLOCK_BOUNCE_TYPE_TABLE

DATA_00F05C. Value 6 spawns bounce-sprite id 7 (spinning turn block); value 7 short-circuits to the brick-shatter path.

[1, 5, 1, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 6, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 2, 2, 2, 1, 1, 7, 17, 16]
codeverified$00F05C
BLOCK_BOUNCE_XSPEED

BlockBounceXSpeed db $00,$40,$C0,$00.

[0, 64, -64, 0]
subpx/frameverified$0290D2
BLOCK_BOUNCE_YACCEL

kBounceSpr01_TurnBlock_YAcceleration db $10,$00,$00,$F0.

[16, 0, 0, -16]
subpx/frame^2verified$0290DE
BLOCK_BOUNCE_YSPEED

BlockBounceYSpeed db $C0,$00,$00,$40 indexed by hit direction.

[-64, 0, 0, 64]
subpx/frameverified$0290D2
BLOCK_CONTENTS_TABLE

DATA_00F080. item = v>>1, powerup-dependent = v&1. Negative values index DATA_00F100 by block X position. item 1=mushroom $74, 2=flower $75, 3=star $76, 4=feather $77, 5=1-up $78, 6=coin, 7=multi-coin, 8=vine $79, 10=P-switch $3E.

[128, 0, 0, 30, 0, 0, 5, 9, 6, 129, 14, 12, 20, 0, 5, 9, 6, 7, 14, 12, 22, 24, 26, 26, 0, 9, 0, 0, 255, 12, 10, 0, 0, 0, 8, 2]
codeverified$00F080
BLOCK_GENERATE_TABLE_PAGE0

TileToGeneratePg0, generate indices 0-8. Index 2 = erase ($25), index 3 = vine ($06), index 5 = the spinning-turn-block tile ($48).

37,37,37,6,73,72,43,162,198
map16 low byteverified$00C08D
BLOCK_GENERATE_TABLE_PAGE1

TileToGeneratePg1, generate indices 9-23. Index 9 = invisible solid ($52), 10 = multi-coin ($1B), 12 = turn block ($1E), 13 and 22 = used block ($32), 19 = ON/OFF block ($12).

82,27,35,30,50,19,21,22,43,44,18,104,105,50,94
map16 low byteverified$00C0E4
BLOCK_HIT_DIRECTION_MASK

DATA_00F0EC indexed by hit direction j: 0 = from below, 1 = from the right, 2 = from the left, 3 = from above. ANDed with DATA_00F0A4[block].

[8, 1, 2, 4]
bitmaskverified$00F0EC
BLOCK_REPLACEMENT_TILE_TABLE

DATA_00F0C8; index into TileToGeneratePg1 ($00C0E4 = $52,$1B,$23,$1E,$32,$13,$15,$16,$2B,$2C,$12,$68,$69,$32,$5E) offset by 9. Generate-index 13 -> tile $32, the used block.

[14, 19, 14, 13, 14, 16, 13, 13, 13, 13, 10, 13, 13, 12, 13, 13, 13, 13, 11, 13, 13, 22, 13, 13, 13, 17, 17, 18, 13, 13, 13, 14, 15, 12, 13, 13]
generate-indexverified$00F0C8
BLOCK_RESPONSE_MASK_TABLE

DATA_00F0A4, 36 entries indexed by (tile_lo - $11). Index 33 (mask 4 = from above only) is the spin-jump turn-block break.

[12, 8, 12, 8, 12, 15, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 3, 3, 8, 8, 8, 8, 8, 8, 4, 8, 8]
bitmaskverified$00F0A4
BLURP_VERTICAL_PERIOD

Derived: 8 speed steps x 4 frames each way. Peak-to-peak excursion is 4 px.

64
framesverified$0384EF
BLURP_XSPEED

db $08,$F8. Direction fixed at spawn by FaceMario; Blurp never re-reads Mario's position.

8
subpx/frameverified$0384C6
BLURP_YACCEL

db $01,$FF, applied when EffFrame & 3 == 0.

1
subpx/frame per 4 framesverified$0384C8
BLURP_YSPEED_MAX

db $04,$FC. Direction flips on exact equality.

4
subpx/frameverified$0384C4
BOBOMB_BLAST_DURATION

$1534 = 1 and $1540 = 64 at fuse end; BobOmbExplosion runs until $1540 reaches 0, then status 0.

64
framesverified$019624
BOBOMB_BLAST_HITBOX_GROUP

RESOLVED: $1662 is forced to $11 for the whole explosion, and clipping group $11 is (dispX -24, width 64, dispY -24, height 64) - a 64x64 px box centred on the sprite. Previous [VERIFY] cleared.

17
clipping-indexverified$02808A
BOBOMB_BLAST_PLAYER_HIT_FRAMES

The player check additionally requires (uint8)($1540 - 16) < $20, so only 8 of the 16 sprite-hit frames can hurt Mario.

[47, 46, 39, 38, 31, 30, 23, 22]
timer-unitsverified$02808A
BOBOMB_BLAST_SPRITE_HIT_FRAMES

Sprites are killed on the frames where (($1540 >> 1) & 3) == 3.

[63, 62, 55, 54, 47, 46, 39, 38, 31, 30, 23, 22, 15, 14, 7, 6]
timer-unitsverified$02808A
BOBOMB_FLASH_THRESHOLD

$15F6 ^= (2*$1540) & $0E once $1540 < $40.

64
timer-unitsverified$019624
BOBOMB_FUSE_EXPIRY_ACTIONS

At $1540 == 1 the handler plays SFX_KAPOW, sets $1534 = 1, $1540 = $40 and SpriteStatus = 8 (back to NORMAL), and clears $1686 bit3 so the blast can hit other sprites. The Bobomb main ($0189D8) then sees $1534 and jumps to BobOmbExplosion ($02808A). Because the carried-sprite handler also calls CODE_019624 every frame, this happens identically in the player's hands - the status change to 8 drops it out of the carry state. Below $1540 == $40 the sprite flashes by XOR-ing (($1540 << 1) & $0E) into SpriteOBJAttribute ($15F6).

null
verified$019630
BOBOMB_FUSE_TICK_DIRECTION

The Bob-omb branch of the status-9 handler returns before CODE_01969C, so it never runs IncrmntStunTimer (++$1540 every 2 frames) or UnstunSprite. $1540 is only ever decremented, by the generic per-slot timer block CODE_0180D2 ($0180D2), which runs for every non-zero status when sprites are not locked - so the fuse ticks 1 per frame, in status 9 and while carried alike.

-1
units/frameverified$019624
BOBOMB_SELF_FUSE_TIMER

When the walk timer expires the Bob-omb sets status 9 and $1540 = $40.

64
framesverified$0189E4
BOBOMB_STOMP_FUSE_TIMER

SetStunnedTimer gives sprite $0D the 255 value, and the id-13 branch of $019624 skips IncrmntStunTimer, so it ticks 1 per FRAME rather than 1 per 2 frames.

255
framesverified$019624
BOBOMB_WALK_TIMER

InitBomb sets $1540 = $FF. An untouched Bob-omb walks 255 frames then sits down by itself.

255
framesverified$018566
BONYBEETLE_SPIKE_CYCLE_FRAMES

$1528 advances once per two frames (odd frames only) and wraps at 256, arming $1540 = $A0 each wrap.

512
framesverified$01E58B
BONYBEETLE_SPIKE_WINDOW

SBC #$08 / CMP #$60 - while $1540 is in [8,104) a stomp HURTS instead of crumbling. 96 frames of exposed spikes per 512-frame cycle.

[8, 104]
timer-unitsverified$01E5E0
BOOSTREAM_REFLECT_RULE

Sprite $B0 negates Xspeed on $1588 & $03 and Yspeed on $1588 & $0C - a perfect reflection with no gravity or acceleration. The ring layout routine CODE_039020 (called every 8 frames, phase = (slot ^ EffFrame) & 7) is [VERIFY].

1
flagverified$038FA4
BOO_AIM_OFFSET_ABOVE_PLAYER

TileGenerateTrackB ($18C1) is subtracted from the player's Y before the vertical distance test. $10 for the Boo $37 and Boo Block $AF, $20 for the Big Boo $28.

16
pxverified$01F8E5
BOO_CHASE_ACCEL_PERIOD

TrueFrame & 7 == 0 gates the +-1 step on each axis, so 64 frames from rest to full speed.

8
framesverified$01F989
BOO_CHASE_SPEED

DATA_01F8CF db $08,$F8. Applies to both axes and to $37, $28 and $AF alike.

8
subpx/frameverified$01F8CF
BOO_DECEL_PERIOD_MASK

TrueFrame mask for [Boo $37, Big Boo $28, Boo Block $AF]; the shy Boo sheds 1 subpx per axis on each unmasked frame. 16 / 32 / 8 frames to stop from full speed.

[1, 3, 0]
frame-maskverified$01F94A
BOO_FREEZE_EXEMPT_RANGE

CORRECTED NOTE. ADC #$0A / CMP #$14 is a +-10 px horizontal window, but it does NOT make a near Boo keep moving. Both range tests branch to CODE_01F92F, which freezes when the shy latch $C2 is set. The window's real jobs are (a) at $01F906, to keep an already-shy Boo shy without re-evaluating the player's facing on its 32-frame beat, and (b) at $01F914, to bypass the turn logic. A shy Boo freezes at ANY distance once it faces the player; only a far Boo that still has to turn keeps chasing during the turn.

10
pxverified$01F914
BOO_FREEZE_RECHECK_PERIOD

$1540 = $20. The 'is Mario facing me' test is SAMPLED, not continuous - turning around does not stop a Boo until the next multiple of 32 frames.

32
framesverified$01F8FA
BOO_SHY_LATCH_RULE

$C2 = 1 (shy) iff SubHorizPos's Y equals PlayerDirection ($76), i.e. the player is facing the Boo. Re-evaluated only when $1540 hits 0, then reset to $20 (32 frames). Decision matrix: $C2 == 0 -> always chase; $C2 == 1 and near -> freeze; $C2 == 1, far, already facing the player -> freeze; $C2 == 1, far, not yet facing -> set $15AC = $1F and chase while turning.

null
verified$01F8E1
BOO_STREAM_TRAIL_PERIOD

CODE_039020 is a TRAIL EMITTER, not a ring re-form. Every 8 frames (per-slot phase, ((slot ^ EffFrame) & 7) == 0) sprite $B0 spawns MINOR extended sprite $0A at its own position with MinExtSpriteTimer = $30 (48 frames) and its own X speed. There are 12 minor slots, round-robined via MinExtSpriteSlotIdx when full. The stream Boo itself is a billiard ball: no gravity, XSpeed negated on horizontal tile contact, YSpeed negated on vertical.

8
framesverified$039020
BOO_TURN_ANIMATION_FRAMES

$15AC = $1F; the facing actually flips at $15AC == $10, half-way through.

31
framesverified$01F92A
BOUNCE_SPRITE_SLOTS

Searched 3 down to 0; overwrites via $18CD when all four are busy.

4
slotsverified$028752
BOUNCE_SPRITE_TYPE_NAMES

BounceSpriteNumber = DATA_00F05C value + 1 ($02882A). The dispatch table at $02904D names each.

turn-no-turn,music,question,sideways,translucent,onoff,turnblock
BounceSpriteNumber 1..7verified$02904D
BRICK_SHATTER_PLAYER_YSPEED

Bounce type 7 (spin-jump break of tile $1E): player_yspeed = -48, 50 points, brick pieces spawned, tile blanked via generate index 2.

-48
subpx/frameverified$028752
BULLET_BILL_SPEED_TABLE_X

BulletSpeedX, indexed by $C2. No gravity, no acceleration, no wall reaction.

[32, -32, 0, 0, 24, 24, -24, -24]
subpx/frameverified$018FD7
BULLET_BILL_SPEED_TABLE_Y

BulletSpeedY, same index.

[0, 0, -32, 32, -24, 24, 24, -24]
subpx/frameverified$018FDF
BULLET_BLASTER_EDGE_SUPPRESS_RANGE

No shot when the blaster is within 16 px of the camera's left edge.

16
pxverified$02B39C
BULLET_BLASTER_PERIOD

ShooterTimer = $60 after every attempt, whether or not the shot happened.

96
framesverified$02B37C
BULLET_BLASTER_PLAYER_SUPPRESS_RANGE

ADC #$11 / CMP #$22 - no shot while |playerX - shooterX| <= 17. This is what lets the player stand on a blaster.

17
pxverified$02B3A6
BULLET_DIAGONAL_GENERATOR_PERIOD

DiagonalBulletTimer advances on odd frames only and fires at $A0 = 160 counts.

320
framesverified$02B0CD
BULLET_GENERATOR_PERIOD

GenerateBullet: EffFrame & $7F == 0. Y = (camY + (rand & $7F) + $20) & $F0, side and direction from bit0 of the same random byte.

128
framesverified$02B08A
BUOYANT_PLATFORM_GRAVITY

Platforms2 ($01B563, reached from FloatingSpikeBall $01B559; the brief's $01B99F is not this code) uses if (YSpeed < $40) ++YSpeed - gravity +1, not the shared +3. Shared by $5C, $5D, $A4 and $C4.

1
subpx/frame^2verified$01B563
BUOYANT_PLATFORM_RISE_CAP

While $164A SpriteInLiquid is set, YSpeed -= 2 each frame until it reaches the cap: $F8 (-8) for ids below $5D (the falling checkerboard $5C) and $FC (-4) for $5D and above (orange water platform $5D, floating spike ball $A4, falling grey platform $C4). Net in-liquid acceleration is -1/frame. Only $A4 integrates X; the platforms are vertical-only.

[-8, -4]
subpx/frameverified$01B58A
CAMERA_H_WINDOW_HALF_WIDTH

CameraLeftBuffer = trigger - $0C, CameraRightBuffer = trigger + $0C. Total window 24 px, not the 16 px commonly quoted from RGME.

12
pxverified$00F6DB
CAMERA_TRIGGER_FACING_LEFT

DATA_00F6B3 dw $0090,$0060 indexed by $13FF = PlayerDirection*2. Facing left keeps the player right-of-centre.

144
pxverified$00F6B3
CAMERA_TRIGGER_FACING_RIGHT

Second entry of DATA_00F6B3.

96
pxverified$00F6B3
CAMERA_TRIGGER_INITIAL

LDA #$0080 / STA CameraMoveTrigger ($142A) on level init.

128
pxverified$00A7B9
CAMERA_TRIGGER_SLIDE_RATE

DATA_00F6BF entries at +8/+10 are dw $FFFE/$0002; applied in HandleLRScrolling ($00CE4C) when $1400 CameraProperMove is set. 144->96 takes 24 frames.

2
px/frameverified$00F6BF
CAMERA_TURNAROUND_DRAG

DATA_00F6CF entries dw $0001/$FFFF reduce the camera's horizontal move by 1 px while the trigger is sliding toward its target.

1
px/frameverified$00F8AB
CAMERA_V_BOTTOM_CLAMP_HORIZ_LEVEL

LDA #$00C0 passed into CODE_00F7F4 for non-vertical levels; vertical levels pass (LastScreenVert-1)<<8.

192
pxverified$00A28A
CAMERA_V_FLOOR_CLAMP

DATA_00F6AD dw $0000,$0000,$00C0, indexed by the same scroll-direction index.

[0, 0, 192]
pxverified$00F6AD
CAMERA_V_SCROLL_DOWN_MAX

Index 1. Downward scroll is never gated by the grounded check.

5
px/frameverified$00F6A7
CAMERA_V_SCROLL_FREE_UP_MAX

Index 2, used when VerticalScrollEnabled ($13F1) is 0 and the level's vertical setting is not 1.

6
px/frameverified$00F6A7
CAMERA_V_SCROLL_UP_MAX

DATA_00F6A7 dw $FFFD,$0005,$FFFA. Index 0 = normal upward scroll.

3
px/frameverified$00F6A7
CAMERA_V_WINDOW_BOTTOM

Second entry. Screen-Y above 124 scrolls the camera down.

124
pxverified$00F69F
CAMERA_V_WINDOW_SPLIT

CMP #$0070 selects which of the two window edges is used.

112
pxverified$00F7F4
CAMERA_V_WINDOW_TOP

DATA_00F69F dw $0064,$007C. Screen-Y below 100 scrolls the camera up.

100
pxverified$00F69F
CAPE_AIRCATCH_BUDGET_TABLE

Indexed by (falling Vy >> 3); signed minimum is retained in MaxStageOfFlight while FlightPhase >= 2. Table entries 9-10 are unreachable.

[0, 0, 0, -8, -8, -8, -12, -16, -56]
subpx/frameverified$00D7D9
CAPE_AIRCATCH_CLIMB_GRAVITY
-12
subpx/frame^2verified$00D7A5
CAPE_AIRCATCH_DEAD_YSPEED_WRITE

STX PlayerYSpeed+1 at $00D8B4 is overwritten by the STA at $00D94C in the same frame. Only the STZ MaxStageOfFlight matters. Do not model the write.

true
verified$00D8AF
CAPE_AIRCATCH_FAST_FRAMES

back / neutral / back. The neutral frame changes NextFlightPhase so the second press bypasses CapePumpTimer. This is an input PATTERN, not a tolerance window - the spec's phrase '3-frame window' is a mis-framing.

3
framescorroborated$00D849
CAPE_AIRCATCH_FLOAT_GRAVITY

Used at phase 1 once the budget is spent or the X-speed sign disagrees with facing.

1
subpx/frame^2verified$00D7A5
CAPE_AIRCATCH_FULL_RISE

114.75 px over 62 rising frames, from budget -56 (requires sustained flight phase 6, Vy >= 64).

1836
subpxverified$00D8AF
CAPE_AIRCATCH_PARTIAL_RISE

34.75 px over 34 rising frames. Budgets -8, -12 and -16 all give this because the -12/frame ramp overshoots them on the same frame.

556
subpxverified$00D8AF
CAPE_AIRCATCH_STANDARD_FRAMES

Frames from the first backward input to the first rising frame when the input is HELD: 1 immediate step + 8 timer frames + 2 frames for the phase-1 clamp/gravity to turn Vy negative. Not a tolerance window.

11
framescorroborated$00D856
CAPE_FLIGHT_BACKWARD_ACCEL

MarioAccel_ row 14 L columns; only applied on frames with a NEW B press (see CAPE_FLIGHT_BACKWARD_NEEDS_B_PRESS).

-6
subpx/frame^2verified$00D345
CAPE_FLIGHT_BACKWARD_NEEDS_B_PRESS

Backward d-pad during flight falls through to $00D764 (no friction while airborne) unless byetudlrFrame bit7 is set. TASVideos: 'pressing left and B will slow Mario down; press B repeatedly to slow down more'.

true
verified$00D6B1
CAPE_FLIGHT_BACKWARD_SPEED_RANGE

Attractor of accel -6.0 / target -8 / friction +1.0; cycle -13,-12,-11,-10,-9,-8,-7.

[-7, -13]
subpx/framecorroborated$00D742
CAPE_FLIGHT_BACKWARD_TARGET
-8
subpx/frameverified$00D535
CAPE_FLIGHT_CEILING_BEHAVIOUR

Rising + PlayerBlockedDir & $08 zeroes Vy at the top of the player update, before integration. Flight is not cancelled.

Vy=0, FlightPhase unchanged
verified$00CD24
CAPE_FLIGHT_FACING_LOCKED

The flight branch never writes PlayerDirection and skips the skid/turnaround block entirely.

true
verified$00D6C3
CAPE_FLIGHT_FORWARD_ACCEL

MarioAccel_ row 14 R-walk/R-run = $0400. With target 48 and friction -1.0 this produces the 51,50,49,48,47 cycle.

4
subpx/frame^2verified$00D345
CAPE_FLIGHT_FORWARD_TARGET

DATA_00D535 row 14; identical for all four run levels.

48
subpx/frameverified$00D535
CAPE_FLIGHT_GRAVITY_TABLE

DATA_00D7A5[phase+3] for phase 1..6. Phase 1 uses +1 instead of -12 when MaxStageOfFlight==0, when PlayerXSpeed+1==0, or when X-speed sign disagrees with facing.

[-12, 1, 3, 4, 5, 6]
subpx/frame^2verified$00D7A5
CAPE_FLIGHT_PHASE_COUNT

Phases 0-6; phase 0 means not in the dive/swoop cycle.

7
phasesverified$00D7C8