Back to Page
Revision 47 (current)
Edited by Unknown on 1/1/2022 6:13 PM
%%TAB Table of contents%%
%%TOC%%
%%TAB RAM Map%%
||Address||Description||
|0060%%%0061|Score, little endian. Game can only display 4 digits; "00" on the right of score display is static.|
|0062|Timer fractions. Cycles 60-0, normally decrementing once per frame during game play.|
|0063|Level Timer. Decrements when $0062 = 60.|
|0064|Ninpo (Points used for special attacks)|
|0065|Ryu's Hit Points|
|0066|Enemy HP Display (separate from actual enemy HP, stored at $0490-0497)|
|0067-006C|Level Scrolling|
|006D%%%006E|Current Stage|
|006F|Related to stage transitions|
|0075|TODO: explain this|
|0076|Ryu's Lives|
|0080-0083|Ryu's Animations|
|0084|Ryu's facing and state (standing/jumping/clinging to wall/etc.)|
|0085|Ryu's x position, fractions|
|0086|Ryu's x position, pixels|
|0087|Ryu's y speed, fractions|
|0089|Ryu's y speed, pixels|
|008A|Ryu's y position|
|0092|0x00 until Ryu uses his sword, after which it is 0x10, meaning the first sword attack after the game starts is always ineffective. First jumping slash after having used Spin Slash also affected.|
|0095|Invulnerability timer, runs 60-0 starting when Ryu takes damage.|
|0096-009F|pointers|
|00A2|Screen position, fractions|
|00A3|Screen position, pixels|
|00AC|Ryu's x speed, fractions|
|00AD|Ryu's x speed, pixels|
|00B5|Object spawn iterator (increments 8 times a frame)|
|00BF|Global Timer|
|00C9|Current Special Weapon.%%%$00=None%%%$80=Art of the Fire Wheel%%%$81=Throwing Star%%%$82=Windmill Star%%%$84=Invincible Fire Wheel%%%$85=Jump and Slash|
|02xx|Sprite Data|
|03xx|Background Data|
|0400-0407|Enemy ID|
|0408-040F|Frames until enemy action (changing direction, throwing/shooting stuff)|
|0438-043F|Enemy movement|
|0440-0447|Enemy facing (left/right)|
|0448-044F|Enemy x speed, fractions|
|0450-0457|Enemy x speed, pixels|
|0458-045F|Enemy x position, fractions|
|0460-0467|Enemy x position, pixels|
|0468-046F|Enemy y speed, fractions|
|0470-0477|Enemy y speed, pixels|
|0478-047F|Enemy y position, fractions|
|0480-0487|Enemy y position, pixels|
|0490-0497|Enemy hit points (only non-zero for bosses and those grey disc-tossers)|
|04B8-04BA|Boss Explosion Animations|
|04BB|Spec. Weapon C x position|
|04BC|Spec. Weapon B x position|
|04BD|Spec. Weapon A x position|
|04BE|Spec. Weapon C y position|
|04BF|Spec. Weapon B y position|
|04C0|Spec. Weapon A y position|
|04C1-04C6|Spec. Weapon Speed (used differently for each weapon)|
|04C7|Spec. Weapon time-out, fractions (hourglass, invincible fire wheel)|
|04C8|Spec. Weapon time-out (hourglass, invincible fire wheel)|
|04D0-04D7|Lantern(or bug or bird)/Power-Up x position, fractions|
|04D8-04DF|Lantern/PU x position, pixels|
|04E0-04E7|Lantern/PU y position, pixels (no fractions for this)|
|04E8-04EF|Frames until power-up vanishes|
|05xx|Unused|
|06xx|Sound|
%%TAB Lua Scripts%%
%%SRC_EMBED lua
--Displays most of the RAM addresses relevant to making a TAS over or around the game's info display.
local function NGRAMview()
yspd = memory.readbytesigned(0x89) + (memory.readbyte(0x87)/256)
xpos = memory.readbyte(0x86) + (memory.readbyte(0x85)/256)
ypos = memory.readbyte(0x8A)
scrnpos = memory.readbyte(0xA3); scrnsub = memory.readbyte(0xA2)
timerf = memory.readbyte(0x62); timer = memory.readbyte(0x63)
rnga = memory.readbyte(0xB5); rngb = memory.readbyte(0xBF)
ninpo = memory.readbyte(0x64)
bosshp = memory.readbyte(0x497); abosshp = memory.readbyte(0x496)
inv = memory.readbyte(0x95)
gui.text(25,9,string.format("Y-Spd:%6.3f",yspd))
gui.text(25,17,string.format("Position: %5.1f, %3d",scrnpos+xpos+(scrnsub/256),ypos))
gui.text(129,17,string.format("[%05.1f+%02X,%02X]",xpos,scrnpos,scrnsub))
gui.text(73,33,string.format("%3d:%02d",timer,timerf))
if bosshp > 0 then
if bosshp > 16 then gui.text(177,41,string.format("%02d",abosshp))
else gui.text(177,41,string.format("%02d",bosshp)); end; end
if inv > 0 then
gui.text(229,33,string.format("%02d",inv));end
gui.text(208,17,string.format("B5:%3d ",rnga))
gui.text(208,25,string.format("BF:%3d ",rngb))
gui.text(81,41,string.format("[%02d]",ninpo))
end
gui.register(NGRAMview)
emu.frameadvance()
%%END_EMBED
%%SRC_EMBED lua
--Accurately tracks the game's inaccurate conception of time, displaying how many "seconds" elapse over the course of a game session.
--Obviously, it is not smart enough to account for save states, but here it is in case somebody finds this interesting.
local minutes=0;seconds=0;frames=0
while true do
prevt = memory.readbyte(0x62)
emu.frameadvance()
currt = memory.readbyte(0x62)
if prevt ~= currt then
frames=frames+1;end
if frames == 61 then
seconds=seconds+1;frames=0;end
if seconds == 60 then
minutes=minutes+1;seconds=0;end
gui.text(199,9,string.format("%02d:%02d:%02d",minutes,seconds,frames))
end
%%END_EMBED
%%SRC_EMBED lua
curves = {}
function PredictBird()
-- feos, 2014
-- draws birds trajectories
-- color marks direction
for slot = 0, 7 do
if (memory.readbyte(0x400 + slot) ~= 11) or (memory.readbyte(0x498 + slot) == 0) then
curves.slot = nil
else
if (curves.slot == nil) then curves.slot = {} end
local ryuY = memory.readbyte(0x8A)
local ryuX = memory.readbyte(0x86)
local birdY = memory.readbyte(0x480 + slot)
local birdX = memory.readbyte(0x460 + slot) + memory.readbyte(0x458 + slot)/256
local birdSpeed = memory.readbytesigned(0x450 + slot) + memory.readbyte(0x448 + slot)/256
local newY = 0
local newX = 0
local newSpeed = 0
while (#curves.slot <= 200) do
if (#curves.slot == 0) then
if (birdY > ryuY)
then newY = birdY - 1
else newY = birdY + 1
end
if (birdX > ryuX)
then newSpeed = birdSpeed - 16/256
else newSpeed = birdSpeed + 16/256
end
newX = birdX + newSpeed
else
local index = #curves.slot
local tempY = curves.slot[index].oldY
local tempX = curves.slot[index].oldX
local tempSpeed = curves.slot[index].oldSpeed
if (tempY > ryuY)
then newY = tempY - 1
else newY = tempY + 1
end
if (tempX > ryuX)
then newSpeed = tempSpeed - 16/256
else newSpeed = tempSpeed + 16/256
end
newX = tempX + newSpeed
end
table.insert(curves.slot, {oldY = newY, oldX = newX, oldSpeed = newSpeed})
end
if (#curves.slot == 200) then table.remove(curves.slot, 1) end
for index = 1, #curves.slot do
local color = nil
if (curves.slot[index].oldSpeed < 0) then color = "#008800" else color = "#0000ff" end
gui.box (curves.slot[index].oldX - 1, curves.slot[index].oldY - 1,
curves.slot[index].oldX + 1, curves.slot[index].oldY + 1, color)
end
for index = 1, #curves.slot do
gui.pixel(curves.slot[index].oldX, curves.slot[index].oldY, "white")
end
end
end
end
%%END_EMBED
%%SRC_EMBED lua
function GetCell(X,Y)
local temp = memory.readbyte(0xE7CC+SHIFT(X,4))+memory.readbyte(0x5F)
if (temp >= 0xC0) then temp = temp-0xC0 end
Y = Y-0x40
if (Y < 0) then Y = 0 end
temp = SHIFT(Y,5)+temp
return temp
end
function DrawBG(arg,offset,x,y)
local color2 = "#00ff00ff"
local function box(color,text)
gui.box(x,y+offset,x+16,y+offset+16,color)
if (text == 1) then
gui.text(x+1,y+offset+1,string.format("%d",arg))
end
end
local function line(up,down,left,right)
if (up == 1) then gui.line(x ,y+offset ,x+16,y+offset ,color2) end
if (down == 1) then gui.line(x ,y+offset+16,x+16,y+offset ,color2) end
if (left == 1) then gui.line(x ,y+offset ,x ,y+offset+16,color2) end
if (right == 1) then gui.line(x+16,y+offset ,x+16,y+offset+16,color2) end
end
if (arg ~= 0) then
if (arg == 1) then line(0,0,0,1) -- right wall
elseif (arg == 2) then line(0,0,1,0) -- left wall
elseif (arg == 3) then line(0,0,1,1) -- two-sided wall
elseif (arg == 4) then line(1,0,0,1) -- right corner
elseif (arg == 5) then line(1,0,1,0) -- left corner
elseif (arg == 6) then line(1,0,1,1) -- two-sided corner
elseif (arg == 7) then line(1,0,0,0) -- floor
elseif (arg == 8) then box("#ff000066",0) -- ejecting block
elseif (arg == 9) then box("#00ff0066",0) -- ladder
elseif (arg >= 12) and (arg <= 15)
then box("#ffffff66",0) -- exits
else box("#00ff0066",1)
end
end
end
function ViewBG(style)
-- feos, 2014
-- style: 0=none, 1=new, 2=old, 3=both
local base = 0x300
local RyuX = memory.readbyte(0x86)
local RyuY = memory.readbyte(0x8A)
local RyuYspeed = memory.readbytesigned(0x89)
local RyuXspeed = memory.readbytesigned(0xAD)+memory.readbyte(0xAC)/256
if (AND(memory.readbyte(0x84),4) == 0) then RyuYspeed = 0 end
local RyuCell = GetCell(RyuX, RyuY+RyuYspeed)
local RyuRow = math.floor(RyuCell/6)
local Screen = memory.readwordsigned(0x51)
if (AND(style,1) == 1)
and (memory.readbyte(0x1FC) == 0x87)
or (memory.readbyte(0x1F3) == 0xD8) then
for tRow = RyuRow-14, RyuRow+14 do
for tLine = 0,5 do
local address = base+((tRow*6+tLine)%0xC0)
local hi = SHIFT(memory.readbyte(address), 4)
local lo = AND(memory.readbyte(address),0xF)
local x = (tRow-RyuRow)*16+RyuX-RyuX%0x10-Screen%0x10
local y = tLine*32+64
DrawBG(hi, 0,x,y)
DrawBG(lo,16,x,y)
end
end
gui.box(xpos-9,ypos+RyuYspeed-1,xpos+5,ypos+RyuYspeed-5,"#0000ff66")
end
if (AND(style,2) == 2) then
for cell = 0,191 do
local hi = SHIFT(memory.readbyte(base+cell), 4)
local lo = AND(memory.readbyte(base+cell),0xF)
local bX = math.floor(cell/6)
local bY = cell%6
local rX = (RyuRow%32)*6-1
local rY = math.floor(RyuY/16)*8-32
if (hi == 0) then hi = " " else hi = string.format("%X",hi) end
if (lo == 0) then lo = " " else lo = string.format("%X",lo) end
gui.text(bX*6,bY*16+9,hi.."\n"..lo)
gui.box(rX,rY,rX+6,rY+8,"#00ff0000")
end
end
end
%%END_EMBED
%%SRC_EMBED lua
function Spawns()
-- feos, 2014
-- uncovers which spawns will occur per frame
local SubCur= memory.readbyte(0x50)/25.6
local PosCur= AND(memory.readbyte(0x51),0xF)
local BlCur = memory.readbyte(0x4E)
local Blptr = memory.readword(0x96)
local Yptr = memory.readword(0x98)
local IDptr = memory.readword(0x9A)
local Count = memory.readbyte(0xB4)
local Iterator = memory.readbyte(0xB5)-8
local IteratorLast = memory.readbyte(0xB5)-1
if (Blptr == 0) then return end
while (Iterator < 0) do Iterator = Count+Iterator end
if (IteratorLast < 0) then IteratorLast = Count+IteratorLast end
local Interrupt = AND(memory.readbyte(0x4C),0x40)
local forward = memory.readbyte(0x3D)
if (memory.readbyte(0x1FC) == 0x87)
or (memory.readbyte(0x1F3) == 0xD8) then
for i = 0,Count-1 do
local color1 = "white"
local block = memory.readbyte(Blptr+i)
local ypos = memory.readbyte(Yptr +i)
local id = memory.readbyte(IDptr+i)
local x = i*16%256+1
local y = 57+math.floor(i/16)*30
if (block == BlCur) then gui.box(x-1,y-1,x+12,y+23,"#00ff0088") end
if (forward == 0) then backspawn = -0xD else backspawn = 0xE end
if (block == (BlCur+backspawn)) then gui.box(x-1,y-1,x+12,y+23,"#ff00ff88") end
if (i+1 >= Iterator) and (i+1 < Iterator+8)
or (i+1 < Iterator+8-Count) then color1 = "#ffccaaff" end
if (Interrupt > 0) then color2 = "red" else color2 = "#44ffffff" end
gui.text(x,y,string.format("%X\n%X\n%X",block,ypos,id),color1)
gui.text(108,41,string.format("Block: %X.%02d.%d\nIterator: %02d-%02d/%d",
BlCur,PosCur,SubCur,Iterator,IteratorLast,Count),color2,"#000000ff")
end
end
end
%%END_EMBED
%%TAB Symbolic Namelists
{{Ninja Gaiden (U) ''''[[!]]''''.nes.ram.nl }}
%%SRC_EMBED sh
$003D#Whichwayisforward#
$004C#DrawingInterrupt#
$004E#CurrentSpawnBlock#
$0050#temp XposSub#
$0051#temp Xpos#
$0052#temp XposHi#
$005D#NewBlockLo#
$005E#NewBlockHi#
$0060#ScoreLo#
$0061#ScoreHi#
$0062#Timer_frames#
$0063#Timer_seconds#
$0064#ninpo#
$0065#RyuHP#
$0067#ScrollBlockSubS#
$0068#ScrollBlockS#
$0069#ScrollPosSub#
$006A#ScrollPos#
$006B#ScrollBlock#
$006C#ScrollArea#
$006D#Current_stage#
$006E#Current_room#
$0070#ProcLo#
$0071#ProcHi#
$0073#Busy Slots#
$0074#Current Slot#
$0079#temp State#
$007A#temp Facing#
$0084#Ryu state#
$0085#Ryu XposSub#
$0086#Ryu Xpos#
$0087#Ryu YspeedSub#
$0089#Ryu Yspeed#
$008A#Ryu Ypos#
$008C#Ryu BGcollision#
$008E#Ryu BGcollisionX#
$008F#Ryu BGcollisionY#
$0095#Inv. Timer#
$0098#YpointerLo#
$0099#YpointerHi#
$009A#IDpointerLo#
$009B#IDpointerHi#
$0096#BlockPtrLo#
$0097#BlockPtrHi#
$00A2#ScreenPosSub#
$00A3#ScreenPos#
$00AC#Ryu XspeedSub#
$00AD#Ryu Xspeed#
$00B4#SpawnCount#
$00B5#SpawnIterator#
$00BF#Global Timer#
$0300#LevelBlocks#
$0400#ID#
$0408#Timeout#
$0410#Action#
$0438#Movement#
$0440#Facing#
$0448#XspeedSub#
$0450#Xspeed#
$0458#XposSub#
$0460#Xpos#
$0468#YspeedSub#
$0470#Yspeed#
$0478#YposSub#
$0480#Ypos#
$0488#BGcollision#
$0490#HP#
$0498#State#
$0600#Sound#
%%END_EMBED
{{Ninja Gaiden (U) ''''[[!]]''''.nes.7.nl }}
%%SRC_EMBED sh
$DD4D#Slot: BusyCheck#
$DD57#Object: Next#
$DD5A#Slot: ScanLoop#
$DD63#Slot: First#
$DD6C#Object: Handle#
$E024#Object: Positioning#
$E243#Object: Common#
$E2E5#Object: Init#
$E66F#Slot: BusyMask#
$F1AC#Object: HandleTemp#
$E677#Collisions: DoAll#
$E67E#Collisions: LowEnough#
$E691#Collisions: UpperRight#
$E6B0#Collisions: LowerRight#
$E6C3#Collisions: UpperLeft#
$E6DD#Collisions: LowerLeft#
$E782#Collisions: Flags#
$E792#Collisions: FindBlockType#
$E7BA#Collisions: CheckBlockHalf#
$E7C0#Collisions: ReadHighNibble#
$E7C7#Collisions: ReadLowNibble#
$EB81#InAir#
$F603#Load New Block#
$F5FB#Six Blocks#
$C195#ProcsLo#
$C196#ProcsHi#
$C21D#Object: Tyson#
$C75D#Object: Bird#
$DFBD#IsRyuAround?#
$C227#Walk (sleep)#
$C29D#Attack!!!#
$C253#Jump (seek)#
$C232#Midair#
$C234#OnGround#
$C264#Aim#
$C276#GetReady#
$DFAB#CheckDistance#
$C259#RyuIsHere!#
$C22D#Act#
$E5D3#ReadPos#
$E5D5#Spawns: GetX#
$E5D9#Spawns: GetSide#
$E5E7#Spawns: GetY#
$E5EE#Spawns: GetID#
$E595#Spawns: CheckNewSpawn#
$E5A6#Spawns: NextUnit#
$E5A7#Spawns: CapTheIterator#
$E144#DmgCollisions#
$E122#CollisionMasks#
$E578#BackSpawn: LeftScroller#
$E581#BackSpawn: RightScroller#
$DDEB#slashing?#
$DDEF#or duckslashing?#
$DECF#SlashCollision#
$DF1F#Damage enemy#
$DF3A#Enemy dead#
$DF3D#Branch if not a boss#
%%END_EMBED
{{Ninja Gaiden (U) ''''[[!]]''''.nes.0.nl }}
%%SRC_EMBED sh
$B300#Xhitboxes#
$B400#Yhitboxes#
$B500#Points table#
$B530#HP table#
$B560#Damage table#
%%END_EMBED
%%TAB_END%%
!!! Movement
!! Horizontal Movement
Ryu has only three possible horizontal speeds: 1.5, 1, and 0.5 pixels per frame.
* 1.5 p/f: when running either direction or pressing forward while airborne.
* 0.5 p/f: when pressing back while airborne.
* 1 p/f: when bouncing after taking damage from an enemy, until contact with a wall or platform. (the player has no control over Ryu's movement during this)
! Preserving speed
Use of ↔, ← + ↕ or ← or → + B or A (←/→ + B/A only applicable when airborne, and if used for more than one from B and A must alternate every frame)
serves to prevent a new speed value from being written, thereby preserving speed when transitioning from one type of movement to another. This is primarily useful for full-speed backwards air movement, but can alsobe used for more precise adjustments to Ryu's position.
Example: ↔ on the frame control is regained after
taking damage, then releasing ← and continuing on holding → puts Ryu a half-pixel behind where he'd be if only → had been held, which can actually be favorable in some instances. Care must also be taken to avoid this happening where it would be undesirable, such as pressing ← for one frame mid-jump to trigger an enemy spawn, then pressing →+B on the following frame. This would leave Ryu one pixel behind where he would have been if the attack had come one frame later, after resuming normal forward movement.
! Sticky Floors
Landing on certain tiles (mostly found at platform edges, but not always or exclusively) will stop Ryu's forward movement for one frame. Usually this is
avoidable, but in cases where it is not, and it is possible to jump on the following frame, it provides an opportunity to execute a special attack without losing time.
!! Vertical Movement
Ryu move upwards in four ways: jumping from a floor or platform, jumping from a wall, being hit by an enemy, or climbing ladders.
Each of these has its own rules, detailed below.
* standing jump: rises 48 pixels, peaks 19-24 frames after jumping.
pattern: -4,-4,-4,-4,-4,-3,-3,-3,-3,-3,-2,-2,-2,-2,-1,-1,-1,-1,0,0,0,0,0,0,1...
* wall jump: rises 15 pixels, peaks 10-14 frames after jumping.
pattern: -2,-2,-2,-2,-2,-1,-1,-1,-1,-1,0,0,0,0,0,1...
* damage boost: rises 29 pixels, peaks 14-19 frames after hit.
pattern: -3,-3,-3,-3,-3,-2,-2,-2,-2,-2,-1,-1,-1,-1,0,0,0,0,0,0,1...
* climbing a ladder moves Ryu 1 pixel per frame.
! Recovery Walljump
When you hit by an enemy, you have no control until Ryu contact with a wall. So the fastest way to reduce 1.0px/f speed and "uncontrollness" length, is jumping into an enemy next to a wall while staying "between" the wall's climbable part and the enemy.
This obviously requires enemy position manipulation and Ryu's X and Y positioning.
A perfect Recovery Walljump is possible to carry out with only 1 frame of negative direction (for example a wall-Ryu-enemy situation in 3 frames: →, being hit (-0.5px ~ -1.5px), → + A)
! Wall-Climbing
__TODO:__ what do we need to document about this?
!!! Attacking
If you want to know the first frame a given enemy can be hit with the sword from a given jump without many tedious re-records, toggling a cheat that sets $82=2 will keep the sword in the attack state for the rest of a jump after B is pressed. Additionally you can poke some greater value to the enemy's HP to see a range of frames Ryu can attack it. Adjust by one frame for down+B if necessary.
!!! Randomness and manipulation
!! Knife tossers
From power on, $BF (Global Timer) increments once every frame, from 0 to 255. It is used to determine the delay (in frames) between tosses and the speed of the tossed objects for the four different object-tossing enemies (actually all the same enemy, just with variant sprites). A logical shift right is performed on the value of $BF for the toss-delay (so, 128 possible values). $BF is directly copied to the X and Y sub-pixel speeds and an AND #$01 sets the x speed. The Y speed is simply set to -3.
$BF is also used similarly for the final boss and the 'shrimp' it spews, but with two shift-rights for the spew-timer and a few more steps for the X speed to get possible values between -3 and 3.
!! Tysons
Tysons constantly do 10-frame jumps, checking if Ryu is around each time they touch the ground. When Ryu is within 32 pixels, they change their action ($410) to "ready" as they land, after the next jump they go "steady", the next jump they attack. It sounds consistent, but in fact it has a lot of randomness.
* Inherited Y sub-pixels (see below) make them land sooner or later by 1 frame
* Graphical interrupts (occuring every 10 frames as you run) freeze them for 1 frame.
* If those interrupts occur right when Tyson is on the ground and is about to change his action, he will fail to do it this time, which means his attack may be delayed by ~10 frames, allowing you to pass.
!! Birds
Birds look so smart only because their AI is so simple. Each frame they are below Ryu, their Y pos increments, otherwise it decrements. Each frame they are to the right from Ryu, their X speed fractions decrease, otherwise they increase. Use the trajectory script to predict them.
!! Bats
They spawn at certain Y position which is read from configs, but then gets overwritten. BatSpawnYpos = RyuYpos - 0x10. You would want to manipulate their height if you are going to damage boost from them and land as soon as possible.
!! Cheetahs
They use to run in one direction all the time, but sometimes they turn around. It happens when they touch the ground and hit the red (sticky) block, first half, that thing that freezes Ryu too if he does it.
!! Inherited Sub-Pixels (and manipulation)
New enemy sub-pixel position gets added to the subpixel of the previous enemy in this slot that he had while disappearing. If the previous enemy disappeared at 0x07.00, the new one will have position 0xEF.80 (previous 0x08.80 => new 0xF0.00).
There's also a 1 frame window to manipulate this: 1 frame before they would spawn, you remove the direction frame. Obviously this only saves time, if you already need to sacrifice 1.5px.
!! Spawns, their delays and cancellations
The game divides levels into blocks of ~16 pixels. For each block, level configs are read on which object must spawn. The object limit per room is stored in $B4. $B5 iterates through all possible spawns for a given room, and picks up those whose block matches the current level block. It iterates by 8 units per frame, and the game can spawn up to 30 units per room. The game can't read through all units every frame, if there are more of them than 8, so some spawns get delayed, if the block already requires the spawn, but the iterator isn't at that unit yet.
Objects configs consist of 4 things: target level block, X position, Y position, and ID. X position can only be either 0x10, or 0xF0, putting it on one of the two screen sides. This way, by delaying the spawn of the object relatively to your own progression, you can affect their positions in the level. Screen-wise, it will spawn at the same place, but in the level it will be different. Just track which bunch of units was iterated through this frame, and delay the moment when it matches the current block. Use the Spawns lua function for that.
Another trick is delaying and even cancelling spawns by moving backwards for 1 frame when the iterator runs through the particular spawn unit. If there are many enemies to spawn in the room, it will read that unit only once per 3-4 frames. So during a jump, you can press the opposite direction during these few frames, moving back only by half a pixel, and then again moving forward by 1.5 pixel per frame. But the fewer enemies the room has, the more frames you need to press backward, because iterator would hit the unit in question more often. Sometimes it doesn't even work because of that.
!! Interrupts
Every ~16 pixels (10-11 frames as you run) occurs and interrupt that loads the new graphics block into video memory. It takes so much time that the frame it occurs most calculations except for Ryu positioning are omitted. If you catch that frame and twitch left-right at 1.5 speed (or just a half pixel, depending on your position), the enemies will freeze. Interrupt is detected by looking at address $4C, which is 0xC1 or 0x80 during those frames.
!! Level Timer
Every second remaining on the level timer will take three frames to be converted to points and added to the score after a boss is killed—a fact that would seem to be at odds with doing things as fast as possible. But since the timer fractions, the "subtimer", carry over between stage transitions, there will be an optimal range of initial values for the subtimer to have when entering the boss stage so that a second that would otherwise remain on the clock will tick off during the boss fight. A way to find the maximum (the minimum is 0) desired subtimer value for a given, otherwise optimized, boss fight is to compare the 61 frames it takes the subtimer to tick a second off with (F) how many frames it will decrement during the fight (starting at either the first frame of input or the frame after and ending on the frame the boss dies or the frame after). F modulo 61 = max desired subtimer value. With that it can then be determined if the existing subtimer value is optimal or if it will be feasible to manipulate a value within that range.
!!! Glitches with no known application
!! Taking damage at the same time a boss is killed
__TODO: haha, 3-3 actually does this__
!! Vertical screen-wrapping
That happens when taking a hit from a vertical position of 4, 5, 14, 15, 16, 25, 26, 35, 36, 45, 46, 55, 56, 65, 66, 75, 84, 93, 102, or 111 or jumping from a wall with a vertical position of 73, 82, or 91, because there is no cap on falling speed and because the game only checks if Ryu is in the eight pixel death zone at the bottom of the screen, but doesn't care if he passes it, so falling from the right position will just make him wrap around to the top of the screen.
!! Making Enemies Fall from Platforms
__TODO:__ Spawn position dependent. Anything need to say?
!! that one thing where you pass through an enemy or grab a power-up and graphics glitch out for a frame
That's a lag frame. The only place where it's not a lag frame is the end of 4-1.
__TODO:__ The block config iterator is not a constant number. 8 for 1-1, 1 for 1-2, 3 for 2-1 etc. It's probably $C1-10 (18 == 8, 13 == 3, 1 == 1 <-- yeah... probably)%%%
__TODO:__ reword prespawn manipulation%%%
__TODO:__ write down enemy slot system ("It's the highest spare slot. Which means if you make it busy yourself, the object will just spawn in the next "highest spare slot".)%%%
__TODO:__ solve the "how to monitor birdy" problem, I think using the speed as a counter MIGHT be enough%%%
__TODO:__ fill in all the gaps