I found out this game while searching for games with percentage counters. I started a test run last month, and then started TASing it. I paused on it a bit since I'm busy, but I discovered there was a speedrun of it already on
youtube. The run misses tons of tricks, which motivated me to post a WIP and share my discoveries so far.
WIP. First 2 medallion pieces obtained.
Tricks:
1. Instant acceleration
In the platforming stages (not overworld), being in the air immediately makes you move at the fastest speed available. This means most of the time, hopping around is the fastest way to move, since landing also decelerates you.
2. Transitioning the tent faster
The transition countdown triggers as soon as you pass a certain point back to the tent. By landing from a jump, you can retain a bit of speed and slide closer to the tent. This ends the stage ~100 frames faster.
3. Corner boosting
You can boost horizontally on green blocks and the serpent at the corners of those 2 platforms.
4. Delaying death
https://youtu.be/BaKoP8c7eSc
Unused, but if you placed a Blubba (the purple bear you bounce on) next to an enemy, and land on the bear at the same frame you die, you can remain alive as long as you jump/move. However, stage objects become uninteractable, so this was not used in the run.
5. Moving away from the wulf
If you pressed left and jumped right before you grabbed an item in front of the Wulf, you can move a bit away before said NPC wakes up. This is not always faster however. This area here:
initially saves 2 frames doing this, but then due to worse positioning loses 3 frames by the end.
6. Instant death to NPCs
https://youtu.be/OP1mKN2_cDI?t=1295
The fan NPCs spontaneously die offscreen when you pause a bit here.
7. Avoiding the Wulf
When the Wulf pauses to turn, or stops after jumping up to a platform, he does not harm you. Since (especially later in game) he can run almost x2 faster than you, sometimes, turning back then going forward again in midair will cause him to pause a bit. You can then run away once you land.
8. The Phoenix
It takes quite some time in comparison to swtich to phoenix, so it should be used only if it's indeed faster, and the next helpful monster you wish to use is located next to said phoenix menu-wise. One useful application of the phoenix is that it allows you to land from high places without flinching.
9. Manipulating luck
The springy enemy is required in the following stages:
Lookout Ledge
Hobbled Hamlet
Viper Vines
Industrial Carnage
The direction you (and other creatures) you bounce off from is RNG based. The addresses are 0x06D0, 0x0718 in IWRAM. You can manipulate this by jumping. Unfortunately, there's not much places you can really jump differently enough to change the RNG, but 2 places which are "easy" spots is the final jump in the laboratories stages along with the final jump when grabbing the treasure. Changing RNG also potentially changes the lag later, since it appears other things in the background are influenced by RNG, so it may (or may not) be faster.
10. Walking past tornadoes
If you get damaged or had just used the pheonix, you can jump into the tornado without being caught.
11. Boosting speed with water puddles
You can land from high places without flinching if you land on a puddle. Additionally, the puddle is able to boost your speed above the normal value, so jumping at the very end of the puddle is recommended. It doesn't work if you just got damaged, during the invulnerable phase.
Lua script that displays fadeout frame + positions and speed
Download sabrewulf.luaLanguage: lua
memory.usememorydomain("IWRAM")
--[[
Pointer at 0x1418
+0x0 - X pos
+0x4 - Y pos
+0x20 - pheonix
+0x38 - X speed
+0x3C - Y speed
]]--
local player = {X = 0, Y = 0, Z = 0, XSpeed = 0, YSpeed = 0, Phoenix = 0, Pointer = 0x1418}
local wolf = {X = 0, Y = 0, Z = 0, XSpeed = 0, YSpeed = 0, Pointer = 0x42FC}
local area = {[0] = "Campsite Clearing", "River Crossing", "Blown Away", "Blackwyche Swamp", "Outlaw Inn", "Eastern Karnath", "Wishing Well", "Blackwyche Laboratory", "Karnath Canopy", "Tangle Terror", "Lower Karnath Mines", "Overgrown Outpost", "Knightlore Falls", "Upper Karnath Mines", "Tangle Terror Lookout", "Karnath Laboratory", "Torchlight Torment", "Deep Dark Dugout", "Stinky Cavern", "Mining Mayhem", "Lookout Ledge", "Crumble Crevice", "Stranglehold Swamp", "Underwurlde Laboratory", "Stinger Strangle", "Frantic Fissure", "Hobbled Hamlet", "Stinkhorn Swamp", "Rocky Mount", "Viper Vines", "Terror Temple", "Entombed Laboratory", "Snowy Knoll", "Frosty's Grotto", "Shivery Peaks", "Wafty Shaft", "Icy Nook", "Gusty Gully", "Coalhouse Climb", "Knightlore Laboratory", "Flames of Fury", "Ritval Ruins", "Filthy Factory", "Mortar Mountain", "Industrial Carnage", "House on the Hill", "Heavy Metal", "Nightshade Laboratory", "Tumbledown Temple", "Watch Out Below", "Magical Mayhem", "Town and Out", "Wings of Steel", "Craggy Crack", "This Old House", "Imhotep Laboratory", "Rooftop Rampage", "Temple Plains", "Cluster Keep", "Factory Furnace", "Bind Alley", "Firing Squad", "Cobbled Courtyard", "Dragonskulle Laboratory"}
local fade_frame = 0
function update(address)
local pointer = memory.read_u32_le(address)
if pointer > 0x3000000 and pointer <0x4000000> 0 and memory.readbyte(pointer_wolf+0x66) == 3 then --Wolf is present, and also awake
wolf.X = memory.read_u32_le(pointer_wolf) --Address pointed by pointer + 0x0
wolf.Y = memory.read_u32_le(pointer_wolf+0x4) --Address pointed by pointer + 0x4
wolf.Z = memory.read_u32_le(pointer_wolf+0x8) --Address pointed by pointer + 0x8
wolf.XSpeed = memory.read_s32_le(pointer_wolf+0x38) --Address pointed by pointer + 0x38
wolf.YSpeed = memory.read_s32_le(pointer_wolf+0x3C) --Address pointed by pointer + 0x3C
gui.drawText(0,70,"wolf",null,null,10,null,null)
gui.drawText(0,80,"X"..string.format('%.6f',wolf.X/65536.0).." Y"..string.format('%.6f',wolf.Y/65536.0),null,null,10,null,null)
gui.drawText(0,90,"SpdX"..string.format('%.6f',wolf.XSpeed/65536.0).." SpdY"..string.format('%.6f',wolf.YSpeed/65536.0),null,null,10,null,null)
gui.drawText(0,100,"Z"..string.format('%.6f',wolf.Z/65536.0),null,null,10,null,null)
gui.drawText(0,110,bizstring.hex(pointer_wolf),null,null,10,null,null)
--Predict when will screen fade
if fade_timer > 0 then
fade_frame = fade_timer+emu.framecount()+22 --frame ends on 21, but lags 1 frame
end
gui.drawText(0,120,"Fade frame:"..fade_frame,null,null,10,null,null)
end
end
gui.drawText(0,30,"X"..string.format('%.6f',player.X/65536.0).." Y"..string.format('%.6f',player.Y/65536.0),null,null,10,null,null)
gui.drawText(0,40,"SpdX"..string.format('%.6f',player.XSpeed/65536.0).." SpdY"..string.format('%.6f',player.YSpeed/65536.0),null,null,10,null,null)
gui.drawText(0,50,"Z"..string.format('%.6f',player.Z/65536.0),null,null,10,null,null)
--Pointer for debugging
gui.drawText(50,10,bizstring.hex(pointer),null,null,10,null,null)
--Frame counter thing that determines end acceleration
gui.drawText(0,60,"Frame:"..frame,null,null,10,null,null)
if (player.Phoenix > 0) then --Since when wulf appears, you cannot use the pheonix, this shouldn't be able to overlap. Probably.
gui.drawText(0,70,"Birb:"..player.Phoenix,null,null,10,null,null)
end
--lab lift
gui.drawText(50,20,memory.read_s8(0x57AA),null,null,10,null,null)
end
emu.frameadvance()
end
Data structure:
Referring to IWRAM locations (so 0x03000000 region in vba):
0x1418 - pointer to address for player's location
Player struct: Using the value from above add the following for:
* +0x0 - X position (fixed point version)
* +0x4 - Y pos
* +0x8 - Z pos (for overworld)
* +0x1C - X (simplified int version)
* +0x20 - Pheonix timer
* +0x38 - X speed
* +0x3C - Y speed
0x42FC - pointer to address for wulf location
Wulf struct: Using the value from above, add the following for:
+0x0 - X pos
+0x4 - Y pos
+0x2E (1 byte) - No idea, but turning this to 0 moves camera to it's location
+0x38 - X speed
+0x3C - Y speed
+0x40 (1 byte) - ID
+0x41 (1 byte) - Item flag (0 for NPC, 1 for item)
+0x63 (1 byte) - Direction
+0x66 - Awake?
+0x6B (1 byte) - No idea, but turning this to 0 moves YOU to it's location
+0xD0 - Next NPC/Object X
0x0190 - frames since last transition
0x0230 - timer for when treasure converts to silver/bronze. Curiously stops at 60,000 even if the other timer can go higher.
0x02C2 - armour bool (64 means you have armour)
0x01A4 - the area id
0x5354 - frames left before fadeout
0x53AE - invincibility after damage?
0x53BA - Treasure state (Gold/Silver/Bronze)
0x57AA - the lab elevator state
Edit: Things that affect splicing are:
1. Lag frames can sometimes spontaneously disappear (or appear) later, when you improve an earlier stage.
2. Springy can either eject you left, up or right. In any stage where you have to land on it, a change of 1 frame in the previous stage can affect which direction it bounces. By delaying the tent transition from the previous stage, you can somewhat manipulate the direction. Delaying before entering the stage does not seem to work however.
3. The tent transition speedup trick seems to occasionally fail for whatever reason if spliced in, even if the input appears to be the same. So far, every occurrence of this was "fixed" by modifying the input at the tent, but not sure if this works every single time.
Edit 2:
https://docs.google.com/spreadsheets/d/1rmYdGHPpAbha9eFiMUP4GPH_kiyfsX6pQKIxo_zVFmc/edit?usp=sharing
Google docs for game stages, items, areas, etc.