Submission #7170: naruko's NES Final Fantasy II "game end glitch" in 04:38.87

Nintendo Entertainment System
game end glitch
(Submitted: game end glitch)
(Submitted: SQF-FY PRG-0.nes JPN PRG0)
FCEUX 2.3.0
16760
60.0988138974405
815
Unknown
Submitted by naruko on 7/15/2021 8:42 AM
Submission Comments

Final Fantasy II improved TAS

Submission translated by xxezrabxxx

Overview

This is a revised version of TAS #7136.
  • Defeat the Ogre Mage and pick up The Spellbook of Blink.
  • The Spellbook of Blink's only proficiency experience as a weapon will be 67 in the next battle.
  • Call up the ending from the inn.
  • No subframe reset or arbitrary code execution

Encounter manipulation

The enemy group is determined by the player's position and address $00f7, and the value is incremented by a structured number of steps. The default value of address $00f7 in FCEUX is 0xff, and it is power-cycled to be the closest 2 to 0xff so that the Ogre Mage will appear.

RNG Manipulation

zeropage sum

The basis of the random number is the sum of the zeropage data and carry bit when switching from the overworld to battle. The sum is the value of the A-register at PC $0B:$97DC. The sum is a very important parameter in the RNG manipulation, which determines the following:
  • Number of monsters in the group encounter (address $00f7)
  • Whether a pre*emptive attack is avaliable
  • The monster's first action
  • The contents of the RNG table (2 types)

The RNG Index (address $0042) and the RNG Table (addresses $7a48-$7b47)

After initialization, the sum is used as a RNG index and is incremented after various RNG calculations. The incremented number depends on the calculation contents (eg; monster's action selection, turn order determination, attack damage calculation ...).
The random value is determined by the table address + index, but the contents of the table depend on bit 1 of the sum. The following is the first 32 bytes of the table contents.
(bit1 = 0)
7A48:  01 04 0D 28 79 93 45 D0 71 AB 02 07 16 43 CA 5F
7A58:  E1 A4 12 37 A6 0C 25 70 AE 0B 22 67 C9 5C EA BF
(bit1 = 1)
7A48:  02 0C 3E C7 1A 84 96 F0 B2 83 91 D7 35 F4 C6 1F
7A58:  9D EC 9E E7 85 9B F6 D0 12 5C 31 F7 D5 2B D9 3F	

How to manipulate random numbers before battle (except for the Black Knight battle):

Random Encounters during the movement can be manipulated with the input keys just before the battle (address $0020, $0021) and the A and B button counters during the movement (address $0024, $0025).
For example, before the Ogre Mage battle, the input keys were the only controls that were 16 short of the sum of the objectives, so I adjusted it by pressing the A and B buttons 8 times during the movement.
As for the input keys to be pressed just before moving, A, B, SELECT, and START are not included in these conditions, so you can press them freely without any consequences. However, if you press the direction keys at the same time, the priority is determined in the following order:
RIGHT > LEFT > DOWN > UP

Black Knight Battle

Since most of the zeropage is filled with data 0 just before the battle, the operation relies on the frame counter at address $00f0-$00f1. Since address $00f0-$00f9 is not intentionally initialized since power-on, its initial value will vary depending on the SRAM used in the console.
In this TAS, the default value of FCEUX is selected as the SRAM default value, but this default value has a long latency for the battle.
The fastest summation is the following 7 ways, with the monster's first attack and the first turn killing 3 allies without crit. In this TAS, 0x02 is used.
0x02, 0x0c, 0x0d, 0x2c, 0x4d, 0x96, 0xab

Ogre Mage Battle:

zeropage sum

In the summation below, there is one Ogre Mage and the player has the advantage because he attacks first:
bit1 = 0: 0x09, 0x14, 0x18, 0x25, 0xac
bit1 = 1: 0xfe
The RNG index before attacking an Ogre Mage determines whether or not the Ogre Mage will pick up a fraction of the blink. The following four conditions are required before the drop operation:
  • Attacking characters
  • Type of RNG table
  • RNG index
  • Number of valid blinks for ogre mage (address $7e3a)
And the random number index is only a few as shown below:
table #0       | table #1
C <$42  b@7e3a | C <$42  b@7e3a
-- | --
0 0x16  0x01   | 0 0x1d  0x01
0 0xd4  0x01   | 0 0x93  0x01
0 0x49  0x03   | 0 0xd0  0x02
2 0x16  0x01   | 0 0x52  0x04
2 0xab  0x01   | 1 0x93  0x01
2 0xd4  0x01   | 1 0xd0  0x02
2 0x49  0x03   | 1 0x52  0x04
2 0x42  0x05   | 2 0x1d  0x01
               | 2 0x6d  0x01
               | 2 0x93  0x01
               | 2 0x97  0x02
               | 2 0xd0  0x02
               | 2 0x2a  0x03
               | 2 0x52  0x04
  • C: Chara # (0: Flio, 1: Maria, 2: Guy)
  • <$42: RNG index. Address $0042
  • b@7e3a: the Evasion Value of the Ogre Mage
  • chara #1 cannot defeat the Ogre Mage in table #0

Predicting the RNG variation

You can change the order of actions of your character by changing your equipment or the target of your attacks. You can change the order of your actions by changing your equipment or the target of your attacks.
If you or the monster chant magic, the variation of RNG index is large.

Manipulating RNG index

When you use a consumable item, the RNG index is increased by 1, and you can increase it again and again by canceling it. However, it is not time efficient.

RNG in a TAS

I decided that it was impossible to defeat the Ogre Mage quickly in one turn and pick up the Blink book, so I ended the battle in two turns.
I have examined and analyzed the player's equipment in all aspects, including the seemingly unfavorable sums.
During the analysis, there is one way in which the condition was met with a sum of 0x18, and two ways with a sum of 0xfe. The fastest is the one with the sum of 0x18, where the Ogre Mage's Blink chant increased the random number index to 0x49.

Goblin Fight

The only summation that satisfies all of the following conditions is 0xfd.
  • One goblin
  • No first strike against each other
  • The goblin's action is run away.
  • The player can't run away.
Chara #1 (Maria) will write data 0x43 to address $62bb as proficiency experience by equipping Blink's book as a weapon and choosing "attack" 67 times.
The formula is as follows:
Experience = initial experience + number of actions - level reword
  • Initial experience: 20
  • Action count: 67
  • Level reward: 20

Inn Event

The address $62bb written in the goblin battle is originally the event ID for the Inn.
  1. The script pointer address (address $0072-$0073) is calculated from the event ID, and if ID# 0x43, data $0020 is written to the pointer.
  2. Since address $0020 is used as the user's button input, data 0xe7 is generated to address $0020 when the user presses left, down, right, select, B, and A on the joypad.
  3. When the script system reads it, the ending will begin.
The button input for "Yes" will be saved at address $0021, and the button input that closes the message window will be saved at address $0020, and the script will be loaded.
In the previous TAS, the address $0020 was not important because the A button was pressed to close the message window.
This time, I've combined the buttons for the dance (data 0xe4).
This will delay the start of the ending, but will not affect the final button input time.

概要

これは TAS #7136 の改訂版です.
  • オーガメイジを倒してブリンクのほんを拾得
  • その次の戦闘で武器としてのブリンクのほんの熟練度経験値を 67 にする
  • 宿屋からエンディングを呼び出す
  • サブフレームリセットや任意コード実行はなし

エンカウント調整

敵グループはプレイヤーの位置と address $00f7 によって決まり、決まった歩数で値が加算されます. FCEUX での default では address $00f7 の初期値が 0xff で、オーガメイジが出るように 0xff から一番近い 2 になるよう電源再投入をしています.

乱数調整

zeropage 総和

乱数の基準はフィールドから戦闘に切り替わるときの zeropage の carry 込の総和です. 総和は PC $0B:$97DC での A レジスタの値です. 乱数調整では総和が非常に重要なパラメータで下記が決まります.
  • 敵グループ(address $00f7)の中でのモンスターの数
  • 先制攻撃の有無
  • モンスターの初回行動
  • 乱数テーブルの中身 (2種類)

乱数 index (address $0042) と 乱数 table (address $7a48-$7b47)

総和は初期化以降, 乱数 index として使用されて各種乱数算出のあと加算されます.加算回数は計算内容(モンスターの行動選択、ターンでの順番決定、攻撃のダメージ値計算) によって異なります.
乱数値は table address + index で決まりますが、 table の内容は総和の bit 1 に依存します. 下記は table の中身の先頭から 32 byte です.
(bit1 = 0)
7A48:  01 04 0D 28 79 93 45 D0 71 AB 02 07 16 43 CA 5F
7A58:  E1 A4 12 37 A6 0C 25 70 AE 0B 22 67 C9 5C EA BF
(bit1 = 1)
7A48:  02 0C 3E C7 1A 84 96 F0 B2 83 91 D7 35 F4 C6 1F
7A58:  9D EC 9E E7 85 9B F6 D0 12 5C 31 F7 D5 2B D9 3F

戦闘前乱数操作方法 (くろきし戦は除く)

移動中のランダムエンカウントは戦闘直前の入力キー (address $0020, $0021)と移動中のAボタン,Bボタンカウンタ (address $0024, $0025) で待ち時間無しで総和を任意の値を作成できます.
例えば今回のオーガメイジ戦前では入力キーの操作だけは目的の総和から 16 足りないため、移動途中に A ボタンと B ボタンを 8 回押して調整しています.
移動直前に押す入力キーは A,B,SELECT,START は条件に含まれてないので自由におせますが、 方向キーは同時に押した場合は下記の順に優先度が決まっています. 上を除き移動したい方向キーは移動したい方向以外にも複数押せます.
移動方向優先度: RIGHT > LEFT > DOWN > UP

くろきし戦

戦闘直前に zeropage の大半が data 0 で埋められるので、操作方法は address $00f0-$00f1 のフレームカウンタに頼ることになります. address $00f0-$00f9 は電源投入時から意図的に初期化されていないので、利用する本体内部の SRAM によって初期値が異なります.
この TAS では SRAM 初期値として FCEUX の default を選んでいますが、この初期値は待ち時間が長いです.
最速に終わる総和は下記 7 通りで、モンスターの先制攻撃と第1ターンで味方3人をクリティカルなしで仕留めてくれます. この TAS では 0x02 を使用しています.
0x02, 0x0c, 0x0d, 0x2c, 0x4d, 0x96, 0xab

オーガメイジ戦

zeropage 総和

下記の総和ではオーガメイジ1匹かつプレイヤー側が先制攻撃をするため有利です.
bit1 = 0: 0x09, 0x14, 0x18, 0x25, 0xac
bit1 = 1: 0xfe

ファイアのほんでの攻撃とブリンクのほんの drop

オーガメイジを攻撃する前の乱数 index でブリンクのほんの拾得の可否が確定します. drop 操作に必要な攻撃直前の条件は下記の4つです.
  • 攻撃キャラ
  • 乱数tableの種類
  • 乱数 index
  • オーガメイジのブリンクの有効回数 (address $7e3a)
そしてその乱数 index は下記のわずかなものです.
table #0       | table #1
C <$42  b@7e3a | C <$42  b@7e3a
-- | --
0 0x16  0x01   | 0 0x1d  0x01
0 0xd4  0x01   | 0 0x93  0x01
0 0x49  0x03   | 0 0xd0  0x02
2 0x16  0x01   | 0 0x52  0x04
2 0xab  0x01   | 1 0x93  0x01
2 0xd4  0x01   | 1 0xd0  0x02
2 0x49  0x03   | 1 0x52  0x04
2 0x42  0x05   | 2 0x1d  0x01
               | 2 0x6d  0x01
               | 2 0x93  0x01
               | 2 0x97  0x02
               | 2 0xd0  0x02
               | 2 0x2a  0x03
               | 2 0x52  0x04
  • C: Chara # (0:Flio, 1:Maria, 2:Guy)
  • <$42: 乱数 index. address $0042
  • b@7e3a: オーガメイジの回避値
  • chara #1 は table #0 ではオーガメイジを倒せない

乱数の変動の予想

にげる、たたかうなどでは変動は少ないですが変動値は同一ではありません. プレイヤーキャラでも装備を変える、攻撃対象を変えることである程度行動順番を操作できます. 魔法詠唱をすると乱数 index の変動は大きいです.

乱数 index の操作

アイテムで消耗品を利用すると乱数 index が 1 増え、キャンセルすると何度も増やせます. ただし時間効率が悪いです.

TAS での乱数

1ターンで早くオーガメイジを倒し、ブリンクのほんを拾得するのは不可能と判断し、2ターンで戦闘を終わらせています.
一見有利ではない総和も含めてプレイヤーの装備を全通りで調べて解析をしました.
解析中に条件を満たせたのは総和 0x18 で1通り、総和 0xfe で2通りあります. 最速は総和 0x18 のもので、オーガメイジのブリンクの詠唱により乱数 index が 0x49 に増えたところを仕留めています.

ゴブリン戦

下記の条件をすべて満たす総和は 0xfd のみの1通りです.
  • ゴブリンが1匹
  • お互いに先制攻撃なし
  • ゴブリンの行動がにげる
  • プレイヤー側はにげられない
Chara #1 (Maria) がブリンクのほんを武器として装備して 67 回たたかうを選ぶことで、熟練度経験値として address $62bb へ data 0x43 を書き込みます.
計算式は下記です.
経験値 =  初期経験値 + 行動回数 - レベル補正
  • 初期経験値: 20
  • 行動回数: 67
  • レベル補正: 20

宿泊イベント

ゴブリン戦で書き込まれた address $62bb は本来は宿泊時のイベント ID です.
  1. イベント ID から script pointer address (address $0072-$0073) が算出され、 ID# 0x43 の場合は pointer へ data $0020 が書き込まれます.
  2. address $0020 はユーザーのボタン入力として利用されているため、ジョイパッドの 左,下,右,セレクト,B,Aをすべて押すと address $0020 へdata 0xe7 が生成されます.
  3. script system がそれを読み込むとエンディングが始まります.
はいを選んだ時のボタン入力は address $0021、 セリフを消す時のボタン入力は address $0020 に保存された状態でスクリプトが読み込まれます.
前回の TAS ではセリフを消す時に A ボタンを押したので address $0020 は重要ではありませんでした. 今回はダンスをするボタンの組み合わせ (data 0xe4) を行っています . これによりエンディングの開始が遅くなりますが最終ボタン入力の時間には影響がありません.

Appendix

sum in zero page

97D5: ldx #$00
97D7: adc <$00,x
97D9: inx
97DA: bne $97d7
97DC: sta $0042 ;sum in zero page
97DF: and #$02  ;a seed of random value table

event ID to script pointer

(at the inn)
8DCA: lda $62bb ;load ID number
8DCD: sta <$6c
8DCF: jmp $91b8
(load <$6c, store address into script pointer <$72)
CBF4: lda <$6c ;ID number
CBF6: beq $cbfb
CBF8: jmp $c6f4
C6F4: asl a
C6F5: tax
C6F6: lda #$0d
C6F8: jsr $fe03 ;switch a bank
C6FB: lda $bfc0,x
C6FE: sta <$72 ;script pointer.l
C700: lda $bfc1,x
C703: sta <$73 ;script pointer.h
C705: lda #$01
C707: sta <$6c
C709: lda #$00
C70B: sta <$17
C70D: rts
(0xbfc0+(0x43<<1): 0xc046)
C046: 20 00 fe jsr $fe00

Special Thanks

Cheap, Pirohiko, Mimi-Hisakaki, xxezrabxxx

Ezra’s note

Me and Naruko spent a good deal of time checking over what was translated and fixing errors. I am sure I made some mistakes but I hope what I have given to you is understandable to a good degree. I had lots of fun and learned a lot. Thanks for Naruko-san for letting me translate it. いいゆめ!

Samsara: Judging!
Samsara: Yeah, this game is thoroughly broken now. I'm glad to see the new route optimized and submitted, and the audience seems glad as well with very positive voting. Given that this run calls the ending (despite not being ACE), I am adding the "game end glitch" branch and accepting it as an improvement to the published GEG run. Excellent work!
Spikestuff: Publishing.
Samsara: Changing intended tier to Standard due to a recent site change.
Last Edited by adelikat on 11/6/2023 2:53 PM
Page History Latest diff List referrers