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 (seperate 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 |
0088 | hypothesized unused y position fractions |
0089 | Ryu's y speed, pixels |
008A | Ryu's y position |
0092 | 00 until Ryu uses his sword, after which it is 10(hex), meaning the first sword attack after the game starts is always ineffective |
0095 | Invulnerability timer, runs 60-0 starting when Ryu takes damage. |
0096-009F | "level objects" |
00A2 | Screen position, fractions |
00A3 | Screen position, pixels |
00AC | Ryu's x speed, fractions |
00AD | Ryu's x speed, pixels |
00B5 | Object spawn iterator (rolls by 8 within each 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 untils 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
while (Iterator < 0) do Iterator = Count+Iterator end
if (IteratorLast < 0) then IteratorLast = Count+IteratorLast end
local Interrupt = AND(memory.readbyte(0x4C),0x40)
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 (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
$004C#DrawingInterrupt#
$004E#CurrentSpawnBlock#
$0050#temp XposSub#
$0051#temp Xpos#
$0052#temp XposHi#
$005D#NewBlockLo#
$005E#NewBlockHi#
$0060#ScoreLow#
$0061#ScoreHi#
$0067#ScrollBlockSubS#
$0068#ScrollBlockS#
$0069#ScrollPosSub#
$006A#ScrollPos#
$006B#ScrollBlock#
$006C#ScrollArea#
$0070#ProcLo#
$0071#ProcHi#
$0073#Busy Slots#
$0074#Current Slot#
$0079#temp State#
$007A#temp Facing#
$0084#Ruy 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#
$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#
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#
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...