In most cases speedrunner tries to manipulate luck by choosing best variant of events that can be influenced by input. So actually "luck" depends on player's patience in rerecording.
But in case of this game there's no obvious difference regardless of any actions you perform right before necessary luck event. You may spend thousands of rerecords for nothing.
Instead, there's way to predict RAM behaviour and calculate needed strategy long before critical luck event. That's it, the game can be played on paper. The result is quite cool: at the beginning of every stage I know exactly what to do (and when) without rerecords.
Unfortunately, this method is game-specific (while rerecords+analysis provide universal way to manipulate luck) and in HinoTori it comes from simple RNG and strict usage of RNG calls.
Here's how randomness in the game works.
At most cases the game doesn't use RNG values to make decisions, most of events are predefined. RNG values can remain untouched during several levels!
RNG can be used by some bosses (esp. by those `falling objects` rooms) and sometimes by rare item drops.
Falling rooms use RNG periodically to set X-coordinate of new falling object. Bosses use RNG to make decisions (jump/shoot/run) or set current angle of shooting. Item drops may use RNG to choose "random item". That's all, nothing else can modify my precious RNG values. Therefore speedrunner can manage those values precisely - everything you need is to know how RNG works.
Here's piece of disassembled code:
C97A: LDA $12 ' take GlobalTimer
C97C: ADC $3F ' add it to RNG_Substractor
C97E: STA $3F ' save sum to RNG_Substractor
C980: LDA $0406 ' take Player_X
C983: ROR ' whether it's odd or even?
' result is saved in Carry_Flag
C984: LDA $40 ' take RNG_Result
C986: SBC $3F ' substract RNG_Substractor from RNG_Result,
' taking into account Carry_Flag
C988: STA $40 ' save result of substraction to RNG_Result
C98A: RTS ' return from subroutine, with random value
' in A register
If you don't get it, here's Visual Basic translation.
temp_a = GlobalTimer
temp_a = temp_a + RNG_Substractor
RNG_Substractor = temp_a ' therefore RNG_Substractor =
' RNG_Substractor + GlobalTimer
temp_a = Player_X
Carry_Flag = temp_a MOD 2 ' even/odd = remainder after division by 2
temp_a = RNG_Result
temp_a = temp_a - (RNG_Substractor + (1 - Carry_Flag)) ' that's how SBC
' instruction of
' NES processor
' actually works
RNG_Result = temp_a ' therefore RNG_Result = RNG_Result -
' (RNG_Substractor + (1 - Carry_Flag))
RETURN (temp_a)
Judging by this listing, there can be two ways to influence RNG usage. First way is to increase GlobalTimer (by pausing the game for necessary amount of frames) before game event calls RNG. Second way is to call RNG routine manually.
If you need to prepare useful "randomness", you can calculate how much times you need to call RNG (but taking into account bosses' own calls, this is tricky).
RNG can be manually called by shooting an enemy at right frame.
Usually when some enemy disappears (after being shot) it leaves "Block" item - you need to collect those and waste them to build walls and platforms. Pretty simple. But KONAMI added really ingenious feature - IF at the moment of enemy disappearing GlobalTimer reaches zero, it's up to RNG to decide whether to drop usual Block or some other item (almost any item!)
While playing Hi No Tori on my NES back in early 90s I figured that if you're lucky enough, sometimes you can receive unexpected item from any enemy (even from skeleton boss from Lv.15).
Here's listing of the game code for item drop routine. This piece of code is executed at the moment you hit an enemy with throwing chisel.
B917: LDA $12 ' watch GlobalTimer
B919: AND #$7F ' but dont take into account 8th bit of GlobalTimer value
B91B: BNE $B939 ' if GlobalTimer is not zero then drop normal Block
B91D: JSR $C97A ' else call RNG subroutine
B920: AND #$1F ' and truncate RNG value to needed form
B922: CMP #$15 ' if this value is still < 15h then drop normal Block
B924: BCC $B939
B926: CMP #$1E ' or if this value is > 1Eh
B928: BCS $B939 ' then drop normal Block
B92A: STA $061B,X ' between 15h and 1Eh... this branch of code drops
' item with ID = value from RNG
B92D: LDA #$02
B92F: STA $0668,X ' ... technical stuff ...
B932: LDA #$FF ' ... make all needed to convert enemy into item ...
B934: STA $067E,X
B937: BNE $B94B ' finally EXIT ---------------------------
B939: LDA #$14 ' here's the branch of code which drops normal Block
B93B: STA $061B,X
B93E: LDA $067E,X ' ... technical stuff ...
B941: EOR #$FF
B943: STA $067E,X
B946: LDA #$00
B948: STA $0668,X
' EXIT is here
VB translation:
EnemyHit_Routine:
' first we found out that current object was hit by player's weapon
' now we must decide whether to turn this enemy into Block or something
temp_a = GlobalTimer
temp_a = temp_a AND 01111111 ' clear 8th bit
if temp_a <> 0 then goto Drop_Normal_Block
temp_a = RND(255) ' Call RNG once
temp_a = temp_a AND 00011111 ' crop generated value
' to the range of 00-1F
if temp_a < 15 then goto Drop_Normal_Block
if temp_a >= 1E then goto Drop_Normal_Block
else ' DropRareItem
Object_ID(current_object_number) = temp_a ' Turn enemy object into item object
temp_a = 2 ' ... technical stuff begins ...
' each object has a number of properties like X/Y coordinates, speed and so on
Object_some_property(current_object_number) = temp_a
temp_a = FF
Object_Direction(current_object_number) = temp_a
' direction property: 00 means `goes to the left`, FF - `to the right`
goto EXIT
Drop_Normal_Block:
temp_a = 14 ' see Item Table - 14 means `Normal Block`
Object_ID(current_object_number) = temp_a ' turn into Block
temp_a = Object_Direction(current_object_number) ' take previous Direction of this enemy
temp_a = temp_a XOR 11111111 ' reverse direction
Object_Direction(current_object_number) = temp_a ' write property into object descriptor table
temp_a = 0
Object_some_property(current_object_number) = temp_a ' write property into
' object descriptor table
EXIT: 'EXIT is here
So in fact the game is violence-free. :) You don't kill anyone, you just turn enemies into objects of another nature. Here's the list.
ITEMS TABLE:
| 0-14 | Blocks |
| 15 | +1 Life |
| 16 | Beads (turn all enemies on screen into Blocks) |
| 17 | Rice Ball (restore energy) |
| 18 | Bag of money (500 points) |
| 19 | Whistle (freeze enemies) |
| 1A | Torch (temporary invincibility) |
| 1B | Mirror (walk through walls and enemies) |
| 1C | SeaShell (power up for energy meter) |
| 1D | Dark Block (+10 Blocks) |
| 1E | Block |
| 1F | Block |
Particularly important for speenrun is Mirror item. It allows you walk through walls, so you can easily pass through undestructable areas of muschievously designed level - without slowing down for a single frame.
The strategy is as follows:
- Prepare RNG_Substractor and RNG_Result values so that (RNG_Result - RNG_Substractor) = 1Ch
- Before you need to walk through wall, find a moment when GlobalTimer = 0 (or 80) and shoot some enemy
- Pick Mirror Item. Now you have 320 frames of unstoppable movement. If you're stuck in a wall, the `mirror mode` timer will freeze at 05, and the game tries to shift player object to the right (but much slower then in Megaman)
Useful note:
You can shoot moving Block item (which is item object) and it'll turn into an item (usually - another Block). Most of players think that they can change direction of moving Block by shooting at it, but actually they destroy one Block and therefore release another (moving to reverse direction). Don't need to say that if you shoot some moving Block while timer reaches zero, you'll turn the Block into something more useful.
Conclusion:
Controlling the luck saved about 10 seconds in my run. Another 2 seconds were saved by better precision with bosses and level tactics. Plus 205 frames come from starting the game earlier.
Morimoto's run was very solid, and without this luck manipulation it couldn't be improved by more than several seconds. I wouldn't start this TAS without such interesting discovery, and I think the run deserves publishing not because of improving old Famtasia run, but because this
insignificant small game suddenly proved to be well-suited for TASing.
The section contains kind of spoilers, so you'd better watch the movie before reading.