This page documents information about Ninja Gaiden
. Many of the tricks demonstrated here are near impossible in real time and documented for the purposes of creating Tool-assisted Speedruns.
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 |
--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()
--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
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
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
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
Ninja Gaiden (U) [!].nes.ram.nl
$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#
Ninja Gaiden (U) [!].nes.7.nl
$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#
Ninja Gaiden (U) [!].nes.0.nl
$B300#Xhitboxes#
$B400#Yhitboxes#
$B500#Points table#
$B530#HP table#
$B560#Damage table#
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...
pattern: -2,-2,-2,-2,-2,-1,-1,-1,-1,-1,0,0,0,0,0,1...
pattern: -3,-3,-3,-3,-3,-2,-2,-2,-2,-2,-1,-1,-1,-1,0,0,0,0,0,0,1...