ファミコンの FF3 の RTA 草案

アイテムの並びがわかってもピアノを弾くのが未だ現実的なのですが、いくつか調査しました. 結果は cheap さんが提案したピアノ演奏+かわのたての方法が有力だとわかりました.

ピアノ演奏の原理

移動画面の CPU address $0072 (ポインタ)の参照先に data 0xdd があればエンディングが始まります.

ねこふんじゃったのピアノ演奏では CPU address $0072 の内容が $b29c になります. 戦闘中の RAM 破壊により、上位バイトだけが破壊されてポインタの内容が $e49c になります. ポインタから順番に data が読まれて CPU address $e4a3 に達したとき、 data が 0xdd なのでエンディングが始まります.

ねこふんじゃったのピアノ演奏後のポインタの下位バイトが 0x9c であることがかなり重要で、踊り子の後の下位バイト 0x88 ではスクリプトが途中で止まってしまいます.

ポインタの上位バイトにかわのたて (0xe4) をつかう場合は、下位バイトは 0x9c 付近かつ 0xa3 以下であることが必須ですが、ウル周辺では都合のいい場所が見つかりませんでした.

他の組み合わせ, ポインタ上位バイトの値ごとの考察

自分の調査ではポインタの上位バイトはアイテム数量またはアイテムIDにすることはある程度は可能です.

○ 0x07 (数量)

実現可能. TAS で利用したもの.

会話でポインタ下位バイトを 0xf8 にして、 CPU address $07f8 から読ませて CPU address $0821 (mirror:$0021) のキー入力で data 0xdd を読み込ませる.

RTA としての欠点:

  • 戦闘後からエンディング開始に約30秒かかる.
  • data 0xdd を生成するためのキー入力(上下右スタートBA)がシビアで失敗したときのやり直しが長い.
    • 買い物後にセーブすれば再開はできる.
  • キー入力の失敗に気づくのも戦闘後で長い.

○ 0x08 (数量)

実現可能. TAS の説明に簡単に書いたもの.

FF3 のプログラムが address $00f9 を初期化しないので別のソフトで該当アドレスを data 0xdd に初期化して、ファミコンの電源を入れたままソフトを交換する.

ポインタ下位バイトは会話で同じ、CPU address $08f8 から読ませて CPU address $08f9 (mirror:$00f9) でエンディング. 利点は戦闘終了後の待ち時間がとても短い.

RTA としての欠点:

  • 上位バイト 0x07 と同様にやり直しが長い.
  • CPU address $00f9 を初期化する場合にキー入力バッファを使うのが常套手段なのだが、 1P のキー入力になっているソフトが少ない.
  • CPU address $00f9 が 2P のキー入力になっているソフトはかなり多いのだが、ファミコンの拡張端子で2Pのキー入力をいれられるデバイスの入手が困難.

☓ 0x06 (トンファー), 0x0e (つえ), 0x1e (ナイフ)

おそらく実現不可.

CPU address $06f8 から CPU address $07f8 または $0821 まで連続で読めないので使えない. CPU address $0700 付近は nametable buffer として使ってるらしいので戦闘開始場所次第はうまくいく可能性がある.

☓ 0x1f (ダガー), 0x24 (ロングソード)

おそらく実現不可.

CPU address $2000 以降は PPU への IO Port で data 0xdd が生成されることはないし、動作不安定であると予想される.

△ 0x62 (かわのぼうし)

実現可能. 戦闘後の待ちが異様に遅い.

CPU address $62f8 から順番に同 $641d (セーブデータ#1の所持金の bit 7:0) を読ませる. 特殊なキー入力はいらない.

RTA としての欠点:

  • ポインタの増加がおそすぎて約5分もかかる.
    • 計測時間ルールが違えば早くなる可能性がある
  • 金額調整後にセーブが必要.

☓ 0x72 (ふく), 0x73 (かわよろい)

おそらく実現不可.

何のバッファか不明だが CPU address $7200-$73ff 付近で data 0xdd が生成されるところをみかけなかった.

☓ 0x8b (どうのうでわ), 0xa6 (ポーション), 0xac(やまびこそう), 0xae (めぐすり), 0xaf (どくけし), 0xb2 (なんきょくのかぜ)

おそらく実現不可.

CPU address $8000-$bfff はポインタ読み込み時 ROM page 0x2c, CPU address $c000-$dfff はポインタ読み込み時 page 0x2d となる.
該当アドレス付近は正式なスクリプトデータなのか、 data 0xdd がほとんどないので使えない.

◎ 0xe4 (かわのたて)

実現可能. 2013年頃からつかわれている.

CPU address $c000-$ffff はポインタ読み込み時 ROM page 0x3e, 0x3f になる.

  • ピアノ演奏の時間が長い以外は安定しているし、操作が簡単である.
  • アイテムの並び次第で戦闘後のエンディング開始もかなり早くできる

かわのたてのアイテムとしての ID は 0x58 だが、 文字としての ID 0x58 は「ピ」を意味し、nametable buffer としてヒ (0xe4) と ゚(0xc1) に分離される. 結果、ポインタ上位バイトには data 0xe4 が書かれる.

△ 0xfa (ブリザド)

たぶん実現可能.

CPU address $fa77 と $fac0 に data 0xdd がある. ただしブリザドを手に入れるにはサスーン城の隠し部屋まで行く必要があり、ピアノ演奏より遅い.

☓ 0xfb (スリプル), 0xfc (ケアル)

おそらく実現不可.

CPU address $fb00-$fcff 付近に data 0xdd がない.

ファミコンの FF3 のメニューの改定の終わり

kazzo で flash cartridge へ program して動作確認をしました.
kazzo... 自分で作ってなんですが10年も経てばボロは出るわけで使いづらいです.

描画途中に CPU address $2001 bit3:2 = 2'b01 にする

前回 PPU A12 がうまく動かず MMC3 の IRQ counter が想定通りに動かないかもしれないと書きましたが、本物のファミコンで想定通りに動きました.

最近のファミコンエミュレータの再現精度は非常に高いことがよくわかりました.

サイトロとこびとのパンで不具合

使用した直後に画面が切り替わり、メニューの最初(アイテム、まほう、そうび...)に戻るわけですが、jsr, rts の構造を無視してスタックを直接操作していてちゃんと動きませんでした.

購入数量、売却数量の不具合

数量の再計算や再描画をさけるために改定した部分でバグがありました.

開発時では毎回店に行く操作が面倒で起動直後に直接お店に飛べるようにしてたので、問題に気づきませんでした.

戦闘中の歩行速度での矛盾

カエルになったときに跳ね途中で止まるので動くたびに上に移動してしまってみえなくなりました.

でぶチョコボに関する深刻な問題

ROM の容量不足

メニューの初期描画の効率化のときはよかったのですが、 IRQ の導入や再描画の削減やUIの改善をやっているうちに追加するプログラムの量が増えていき、容量不足が深刻な問題になりました.

一時的な対策としてでぶチョコボのプログラム領域 (0x300 bytes ぐらい)を消して使っていますが、空き領域を捻出することは不可能であるという判断です.

メニューに関するプログラムは page 0x3c, 0x3d, CPU address $8000-$bfff に配置されており、バンク境界である $9fff-$a000 付近も意識なく連続で使われていて、再利用するサブルーチンもそのように使われています. よって今回のメニューに関するプログラムは page 0x3c, 0x3d に収めきる必要があるのにそれは不可能です.

このため FF3 のパッチは未完成で開発を終えることにしました.

ひきだすの技術的問題

あずけるの改定は完成していますが、ひきだすは難しいです.

現状アイテムは nametable RAM すべてに全部入るのでよかったんですが、ひきだすはおさまらないのでスクロールしたら nametable 追記が必要です. そのプログラムを作るのは別にいいのですが、書く場所がありません.

「かう」と「ひきだす」にはアイテムが装備可能か表示されているのですが、カーソルを動かしたままだと処理が遅くなります. この装備可能の参照が妙に遅く、これも改定したいと気持ちはあるのですがこれも ROM 容量を消費することになるでしょう.

現状のプレイはやりづらい

でぶチョコボはなくてもクリアはできるのですがジョブチェンジのたびにアイテム欄があふれてしまいました. 捨てるか売ればいいんですが預けられないのはよくないです.

ファミコンの FF3 のメニューの描画に IRQ を当ててみた

疑似スクロール

疑似スクロールはアイテム欄が32個(=16行)あるのに対し1画面で16個(=8行)の表示をしています. よろしくないのは1つのnametableでスクロールしているようにみせているので、上下端でカーソルを動かすとアイテム欄の nametable をすべて描画して、これが遅いです.

nametable は1画面分のみ使っているのでもう1画面はまったくつかっていません. これを1度に描画しておき、疑似ではなく本当にスクロールができるかためしました.

ウインドウシステム

ウインドウは文字列がはみ出ないような基本設計がされています.

ウインドウは x, y, width, height の4つを 8x8 pixel (=1 name) 単位で指定して、対象の文字列のポインタを設定することができます. その後、中身が空のウインドウを描画し、その中に文字列をいれていきます. (一旦空のウインドウを描画するのは別の問題があり後述します.)

アイテム欄は中身 8 行の大きさが定義されていますが、これを中身 16+1 (+1 はゴミ箱)にすると一度にすべて描画してくれましたが、Address bit 10 (別仮想画面)をまたぐ nametable の address 更新が想定外だったのでパッチを当てました.

さらに VRAM mirroring を当てるとこの表示になりました.
f:id:na6ko:20211017225539p:plain

ウインドウシステムではさらにカーソルの位置やアイテムの情報も管理できるようになっていて設計は素晴らしいとは思いましたが、 inc 1 つですむような命令を lda ; clc; adc; sta として何箇所も書いているので実装は.... パッチをいれる隙間が多くて助かりました.

スキャンラインカウンタ IRQ を発生してスクロールレジスタを更新する

IRQレジスタ管理はそんなに難しくありませんでした. アイテム欄は縦につながっているので、スクロール管理が少し難しいです. やってみてわかったのは、 CPU address $2005 での横スクロール切り替えは制約が少ないものの、CPU address $2006 での縦スクロールは切り替えは制約が多いです.

IRQ handler を作るために固定領域(CPU address $c000-$ffff)の空きを作ること、IRQ 発生途中でのバンク切り替えに対応することなど ROM 領域の空きを捻出することも求められます.

今回の IRQ handler では CPU address $2001 も hblank 中に更新できるような仕組みをいれてみました. 今のところは空き領域確保の都合で実用化できてないのですが、描画途中に CPU address $2001 bit 3 の nametable rendering を替えることによってアイテム欄を隠すことも mesen などエミュレータではできてます. というのも、 MMC3 の scanline counter は PPU Address bit 12 の立ち上がりを数えているだけなので nametable rendering だけを止めたらちゃんと動くのかは(私の知識がないので)自信がないためです.

試行錯誤はありましたが、疑似スクロールだった処理は16 pixel単位ではあるもの本物のスクロールを利用して再描画時間がなくなり待ち時間は解消できました.

アイテム欄では 1 frame あたり表示期間中に 2 度 IRQ を発生して nametable を本当にスクロールさせています. 装備,預ける,売るは下がアイテム欄だけだったのでそのまま見えるようにしています.
f:id:na6ko:20211017224751p:plain

これは作り途中だった動画です.
www.youtube.com

本当に必要な描画のみにする

nametable の再描画を大きい単位でやっているためで更新の必要がないものも再度書き込みを行っているのが原因です.

アイテム欄で消費や場所の移動をすると従来のプログラムではアイテム欄を更新が必要ない部分も含めて見えている部分を再描画します. アイテム欄のウインドウの面積を2倍にすると再描画時間も2倍になりよくありません. アイテム欄を途中から描画してもらうのは既存のプログラムをみたところ、難解でした.
先述の xywh と一緒にポインタがでてくる内容を完全に理解できないので、 x = 2 (ウインドウの中身), y = 更新対象, w = xx, h = 2 (濁音もいれる) としつつ、ポインタだけを同じ感じにしてなんとかしています. このため、アイテム1つだけ再描画はできずアイテム2つ(=1行)再描画となりました.

;          pt        X Y  x y  w h  pt
;0040 0000-8486 001B-0205 0006-1C10 FC85
;0040 0000-D881 001B-020C 000D-1C10 5081

item_redraw_line:
	ldx	#0
	beq	irl_main
equip_redraw_line:
	ldx	#5
irl_main:
	stx	<$81
	and	#$fe
	sta	<$80
	lsr	<$80
	ldx	#2
	clc
	adc	ic_scroll_offset
	cmp	#$1e
	bcc	irl_0
	sbc	#$1e
	ldx	#$22
irl_0:
	sta	<$39 ;y
	tay
	dey
	sty	<$97
	stx	<$38 ;x
	dex
	stx	<$98
	lda	#$1c
	sta	<$3c ;w
	lda	#2
	sta	<$3d ;h

	ldx	#0
irl_push:
	lda	$7a00,x
	pha
	inx
	cpx	#8
	bne	irl_push
;先頭のアイテムはポインタ算出がわからなかったので正式なアイテム描画をやる
	ldx	<$81
	lda	<$80
	bne	irl_1
	lda	irl_parameter,x
	jsr	$a678
	jmp	irl_cusor_position_restore
irl_1
	lda	#$1b
	sta	<$93
	lda	irl_parameter+1,x
	ldy	irl_parameter+2,x
	jsr	item_pointer_calc
	sta	<$1c
	sty	<$1d
	ldx	<$81
	lda	irl_parameter+3,x
	ldy	irl_parameter+4,x
	jsr	item_pointer_calc
	sta	<$3e
	sty	<$3f
	jsr	$eb2d
irl_cusor_position_restore:
	ldx	#7
irl_pop:
	pla
	sta	$7a00,x
	dex
	bpl	irl_pop
	rts

irl_parameter:
;通常
	byt	$3d
	adr	$85fc-$11
	adr	$8684-$11
;装備
	byt	$2f
	adr	$8150-$11
	adr	$81d8-$11

item_pointer_calc:
	ldx	<$80
;	beq	ipc1
item_pointer_next:
	clc
	adc	#$11
	bcc	ipc0
	iny
ipc0:
	dex
	bne	item_pointer_next
ipc1:
	rts

本当に更新が必要なアイテムの位置を見つける

  • 消費、装備装着、売却はアイテム1個単位
  • 交換はアイテム2個単位
  • 装備解除はアイテム最大5個

苦労したのは装備解除です.外れるアイテムが最大5個で描画の重複を避けるようにソートするのも手間で ROM 領域も消費します. またすべて外すの内部処理中に装備品を1つずつ外すので、キャラクタのステータスを外すたびに再計算しているようでこれがわりと遅い(80 scanlineぐらい)です. このため IRQ 管理に矛盾が生じ画面が乱れました.

ステータス再計算中だけに RTI だけしている NMI handler を勝手に更新して MMC3 の IRQ counter を初期化することで IRQ もちゃんと発生させるようにしました. RTI だけしている NMI handler も基本設計が割り切ってる感じがあってそのデメリットを見ている感じです.

ウインドウの再描画もいらない

アイテム欄以外のメッセージや金額そして装備ウインドウでさえ外枠->中身埋め->文字列入れ直しをやっています. 体感として待ってる感じはないのですが文字列がちらついているのはよくないです.

原因は window の xywh の算出と描画と文字列の描画が1組になるようなしくみだったため、wyxh の算出, x+=1; y+=1; w-=2; h-=2, 文字列の描画だけにしたところほとんど改善しました.

予想と違うのはセリフのような可変長文字列の 0 終端以降は前の文字が残るはずなのに残らなったことです.

唯一都合が悪かったのは装備品を外したときのウインドウのゴミが残るところで、連続しない3つの name(tile) をきれいに直す方法を探る必要があります.

ファミコンの FF3 のメニューの描画を早くした

先日別の方が普通にプレイしている姿をみてて、スタートボタンを押してからメニューがでるまでに毎度1度秒以上かかるのが気になったので改修してみました.

ROM の CRC32 は 0x57E220D0 のものです.

遅い原因

  • 画面描画を止めずに vblank を待って限られた時間に最大で 64 byte 書いている
  • 移動画面でのセリフに使うウインドウのシステムを何故か流用している
    • FF2 からの仕様らしく FF3 でもそのまま使ってしまっている

客観的事実としまして、FF1 はメニュー描画は続編と比較して早く(ファミコンソフト一般として普通)で、FF2 と FF3 はものすごく遅いです.

プログラムをしらべる

対策は画面描画を止めて、vblank を待つことなく1度に VRAM へデータを書き込み、終わったら画面描画を再開することです.

直接の原因

PC:$edcd と PC:$f693 付近の命令が問題となっています.

EDCD: jsr $ff00 ;wait a vblank
EDD0: lda #$02  ;DMA to OAM
EDD2: sta $4014
EDD5: jsr $f6aa ;store to nametable from buffer
EDD8: jsr $c9a9
EDDB: jsr $ede1
EDDE: jmp $c750 ;sound task
F692: pha
F693: jsr $ff00
F696: inc $f0
F698: pla
F699: jsr $f6aa ;store to nametable from buffer
F69C: jsr $ede1
F69F: jsr $c750
F6A2: lda $93
F6A4: jsr $ff03 ;switch a bank for address $8000-$9fff
F6A7: jmp $f683

メニューのプログラム

スタートボタンを押すと画面暗転後に PC:0x3d:$a52f から始まります. MMC3 では CPU ROM bank は address $8000-$9fff, $a000-$bfff で分割していますが、このプログラムでは page は 0x3c と 0x3d をつなげて利用しています.

a52f: lda  #$00
a531: sta  $78f0 ;カーソルに関する変数
a534: lda  #$00
a536: sta  <$25
a538: sta  $2001 ;PPU 描画停止
a53b: sta  $79f0 ;カーソルに関する変数
a53e: sta  $7af0 ;同上
a541: jsr  $dd06 ;CPU の ROM からキャラデータの転送
a544: jsr  $f7bb ;画面外枠だけの nametable を data 0 で埋める
a547: jsr  $c486
a54a: jsr  $ff00 ;wait a VBLANK (いらない)
a54d: lda  #$02
a54f: sta  $4014 ;update OAM (いらない)
a555: lda  #$88
a557: sta  <$fd
a559: sta  <$ff
a55b: jsr  $959f ;PPU 描画再開

メニューのための VRAM 転送命令が続きますが中略します. その転送命令のなかでは直接の原因が利用されています.

下記は描画完了後のユーザーからのボタン入力待ちに関連するループです.

a5a9: jsr  $a7cd
a5ac: lda  #$00
a5ae: jsr  $8000 ;sprite 管理で OAM buffer を更新
a5b1: lda  #$00
a5b3: jsr  $80cb ;同上
a5b6: lda  #$04
a5b8: jsr  $91a3 ;同上
a5bb: lda  <$25
a5bd: beq  $a5c2 ;Bボタンが押されたか
a5bf: jmp  $a646 ;メニュー終了
a5c2: lda  <$24
a5c4: beq  $a5a9 ;Aボタンが押されたか

a646: jsr  $a685 ;画面全体の nametable を data 0 で埋める (いらない)
a649: lda  #$00
a64b: sta  $2001 ;PPU 描画停止
a64e: jsr  $a654
a651: jmp  $8f58 ;移動画面に戻る

PC:$a646 は本当に無駄な処理で単純にこの jsr のみをなくしても早く移動画面に戻ります.

PC:$a5c4 以降はボタンが押されたかで分岐が入り、アイテムや装備の画面切り替えに飛べます.

下記はアイテムのメニューのプログラムの抜粋です.

9ec2: jsr  $9592
9ec5: lda  #$80
9ec7: sta  <$b4
9ec9: jsr  $a685
9ecc: jsr  $956f
9ecf: jsr  $a328
(中略)
9efe: jsr  $a7cd
9f01: lda  #$08
9f03: jsr  $920d
9f06: lda  #$03
9f08: jsr  $8000
9f0b: lda  <$25
9f0d: bne  $9ebc
9f0f: lda  <$24
9f11: beq  $9efe

アイテム以外のメニュー画面も流れは大体同じで、 PC:$a685 で nametable を data 0 で埋めて、 PC:$956f で PPU を描画にして(すでに描画になってるのですが)、VRAM と変数の初期化命令が入り、 PC:$a7cd が呼ばれた後ボタン入力でループを回しています.

プログラムを直す

直接の原因

命令が増えることもあり、空き領域の確保が求められますがここでは省略します.

変数を追加して vblank 待ちを行わないフラグによって分岐するようにしました.

	org	$edcd
	jsr	edcd_new
	
	org	$f693
	jsr	f693_new

	org	xxxx ;PC: $c000-$ffff のどこか
;skip waiting vblank (jsr $ff00)
edcd_new:
	lda	<render_count
	bmi	edcd_skip
to_vblank_wait:
	jmp	$ff00
edcd_skip:
	pla
	pla
	jsr	$f6aa
	jmp	$c9a9

f693_new:
	lda	<render_count
	bpl	to_vblank_wait
 rept 3
	pla
 endm
	inc	<$f0
	jsr	$f6aa
	jsr	$ede1; update scroll register
	jmp	$f683

変数 render_count は address:$00dc 付近を使ってます. address: $00dc-$00e0 あたりは未使用の zeropage のようでした. この領域は reset のあと data 0 で埋めています. (address: $00f0-$00ff 付近は 0 クリアをやってない)

edcd_skip は無理やりやってるので rts 経由せずに pla x2 をいれています(あまりよくないです). PC:$ff00 以外にもサウンドドライバやいらなそうなのは削ってあります.

メニューのプログラム

修正すべてをここには記載できませんが、下記の方針で該当部分を修正します.

  • 初期化
    • jsr/jmp $a646 と jsr $956f は消してかわりに jsr display_off をいれる
    • jsr $ff00 とか OAM への DMA request も消す
    • その他 address $2001 で描画を有効にする命令があれば消す
    • jmp $a646 で省いて VRAM で data 0 にすべきところがでてくるので直す
  • キー入力待ちのループ
    • jsr $a7cd を jsr a7cd_new にする
  • メニューの終了 (PC:$a646)
    • 変数 render_count = 0 にする
display_off:
	lda	#0
	sta	$2001
	lda	#$80
	sta	<render_count
	jmp	$c486 ;clear OAM buffer

a7cd_new:
	lda	<render_count
	lsr	a
	bcs	render_temp_0
;----1度目----
;nametable attribute を書いた後,一旦戻る
;subroutine の外で OAM buffer を書いてもらう
	lsr	a
	bcc	ppu_attr_fill_ff
	jsr	$95b2 ;draw nametable attribute
	jmp	a7cd_new_noattr
ppu_attr_fill_ff:
	ldx	#4
	jsr	ppuram_fill
a7cd_new_noattr:
	lda	#%0001 | $80
	sta	<render_count
	jmp	$A7D7
render_temp_0:
	lsr	a
	lsr	a
	bcs	render_temp_1
;----2度目----
;renderer on, OAM buffer -> OAM
	jsr	$ff00
	lda	#%0101
	sta	<render_count
	jsr	$959f
	lda	#0
	sta	$2003
	lda	#2
	sta	$4014
	jmp	$d308
;----3度目----
;通常処理
render_temp_1:
	jmp	$a7cd

a7cd_new について. jsr $a7cd は共通で呼ばれていることと、OAM の初期化が 描画開始から 1 frame ずれてしまう対策として作りました. メインループに入ってから 2 度は初期化が続きます.

  • 1度目はその他足りてない VRAM の初期化をいれて一度抜けて、 OAM buffer を更新してもらいます. VBLANK は待ちません.
  • 2度目は VBLANK を待ち, PPU を画面描画を再開、直後に OAM buffer を DMA で OAM に転送し、さらにパレットも更新します.
  • 3度目は通常通りです.

このため FF1 と違って nametable と object が同時にすべて出るようになります.

対策後

メニュー表示が FF1 並になりました. この動画は他にも修正入れまくってますがメニュー表示に関しては大体同じです.

www.youtube.com

PC:$a25f 開始から画面描画完了までは 8 frame でできるようになります. 8 frame のうち約半分の時間はキャラクタRAMへの転送(jsr $dd06)です. 残り半分はnametable への転送ですが、もともとの設計が 64 byte 程度のバッファ経由で少しずつ動かすようになっていてこれ以上の短縮は難しいです.

キャラクタRAMへの転送のループも 1 byte 転送で分岐命令が入っていたので 1 byte から 8 byte にかえて画面描画時間を 7 frame に一応は削れはしました. ただし ROM の空きの確保に苦労に対して効果は見合っていませんでした.

お仕事募集

自己紹介

筆者は個人事業主で、電子回路とそれに関わるファームウェア開発を主としております.

2021年になってから半導体の供給不足が問題となり状況は毎月悪化していて回復の見込みがなく、ソフトウェア開発の仕事を募集いたします.

得意分野

6502, 68000, Z80 採用の家庭用または業務用ゲームソフトの解析やソフトの改修を得意としております. 今回の TAS のような古いゲームのソースコードがない状態でのバグ修正や改善ができます.

実績

会社員時代の業務実績がございます. 詳細については個別にご回答いたします.

  • 激しい画面点滅演出の削減
  • 権利表記の修正, 社会情勢の変化に伴う記号や肖像の修正
  • 飛ばせないシーンを飛ばせる用対応もしくはその逆の対応
  • RPG での効率化 (移動速度高速化, 敵出現率低下)

基本、オリジナルのプログラムに最小限の修正を加えて対応しますので、エミュレータなど上位レイヤーで特別な対応が不要となります.

参考納期(画面点滅対応)

  • ハードウェア仕様を筆者が既知の場合は1週間.
  • ハードウェア資料があるが筆者が知らない場合それに加えて2週間.
  • ハードウェア資料もなく開発ツールの開発が必要な場合は1か月以上.

お問い合わせ方法

作業単価などの詳細のお問い合わせは個別に回答いたします.

お手数ですが、コメント欄にメールアドレス記載の上ご連絡ください. メールでご返信いたします.

ファイナルファンタジー3のTASの詳細

FF3 のゲーム開始前

ルールとして許可されているなら address 0x00f9 へ data 0xdd を仕込むことができます. 簡単な調査で見つかったのは address 0x00f9 を 1P のキー入力として使えるのは(superがない)魂斗羅と F1 センセーションの2本でした.

data 0xdd は上下右スタートBAをすべて押すため、ジョイカードを併用してください. ボタンをおしたままカセットを抜いて、電源をいれたまま FF3 をさしてリセットボタンを押してください.

address 0x00f9 を 2P のキー入力として使っているソフトはコナミの中ではかなり多いのですが, 1P は2本だけでした.

祭壇の洞窟 (1回目)

フロアその1

最初のゴブリン戦で3匹のゴブリンからポーションを2つ拾います. 理論上3つのポーションが落ちるようですが、最速プレイとしては利用できる条件ではありませんでした.

宝箱からポーションとかわのたてを拾います. かわのたては2個目のみです. もう1個のかわのたては購入したほうが早いです.

フロアその2

宝箱からポーションを拾います. 一見6歩無駄ですが、戦闘中のアイテム交換を 1回(177 frame)削減できます. 宝箱のための往復での6歩は 96 frame なのでとります.

なんきょくのかぜは遠く時間削減ができないので取りませんでした.

ランドタートル戦は TAS submission #2992 と同じ総和(詳細は後述)で倒しました. 手動操作では運が必要です.

祭壇の洞窟 (2回目)

ウルの村で大量の物資を購入するため 2000 ギルを拾得します.

ウルの村

スクリプトポインタの初期化

前回の TAS ではピアノを弾いてスクリプトポインタを初期化していたのですが、その代わりにそこらへんの人と会話します. ボタン操作の簡略化のため、買い物前に会話するほうがいいです.

買い物

基本方針は下記です.

  • 戦闘中の武器交換回数をへらすためにアイテム欄はできるだけ埋める.
  • アイテムの名称より数量とXの有無のほうが重要.

道具屋は X がつかないアイテムが3種類売ってるのですが、往復に 177 x2 frame 以上かかるので採用していません. 防具屋も実はいかなくても条件を満たせるのですが、アイテム欄を埋める時間の節約と消費金額が少なさ(装備できるかわのたてが安い)の2つの理由で行きます.

TAS では歩数の都合でウルの村で敵と遭う都合とアイテム整理の操作削減のため「防具屋->魔法屋->武器屋->村内の敵が出る場所」の順番に行きます. 歩数の制限と村民たちの妨害がなければ、「武器屋->防具屋->魔法屋->祭壇の洞窟(3回目)」のほうがアイテム整理は楽です.

アイテム整理

TAS では戦闘前に下記の並びにしています.

 右手:かわのたて: 1
 左手:かわのたて: 1
  0:ダガー          :12  1:かわのたて      :11
  2:(空欄)          : 0  3:(空欄)          : 0
  4:(空欄)          : 0  5:(空欄)          : 0
  6:(空欄)          : 0  7:ロングソード    : 1
  8:(空欄)          : 0  9:(空欄)          : 0
 10:ポーション      : 1 11:(空欄)          : 0
 12:(空欄)          : 0 13:(空欄)          : 0
 14:(空欄)          : 0 15:(空欄)          : 0
 16:ふく            : 3 17:かわよろい      : 1
 18:(空欄)          : 0 19:どうのうでわ    : 4
 20:かわのぼうし    : 5 21:(空欄)          : 0
 22:ヌンチャク      : 2 23:ポイゾナ        : 4
 24:つえ            : 7 25:(空欄)          : 0
 (以下すべて空欄)

戦闘中にアイテム欄を下記にすると必要な変数をうまく操作できます. 条件を守れば上記の並びと同じにしなくていいです.

 右手:Y     : 1 
 左手:Y     : 1
 0から15: Y : 1
 16:X       :3+ 17:N       : 1
 18:N       : 1 19:N       :3+
 20:N       :3+ 21:N       : 1
 22:N       : 2 23:ポイゾナ:3+
 24:N       : 7 25:N       : 1
 26:W       : 1 27:N       : 1
 28から31:N : 1
アイテム種類の見方
  • Y: X がつかないもの
    • 装備できる手につける装備品, または消耗品
  • X: X がつくもの
    • 装備できない手につける装備品, 手につけない装備品, 魔法のオーブ
  • N: どれでも可能
  • E: ID の bit7 == 0 && bit1 == 1 のもの
    • 序盤で手に入るものは下記.
    • ヌンチャク,つえ,ナイフ,ダガー,かわのぼうし,ふく,かわよろい
    • 戦闘強制終了フラグを書き込む
  • W: ID で 0x20 から 0x5f ではないもの. (文字として濁音か半濁音ではないもの)
    • 序盤で手に入るものはかわのたてをいれてはいけないので注意
    • FF3 のゲーム前に RAM を初期化しているのなら条件外
  • ポイゾナ: ポイゾナのみ可
数量の見方
  • 0,1,2: 指定通りの数量をいれてください.
  • 3+: 3個以上. 4個だと早く買えます.
  • 7: TAS では 7 個. FF3 のゲーム前に RAM を初期化しているのなら 8 個.
    • ポインタの上位バイトを書き込む
TAS での並びの解説
  • ダガーとかわのたてはアイテム交換を早くすませるためです.
  • 右手左手両方に装備をしてください. (数量は1が必須)
  • X がつかないアイテムは位置の制限が緩いので、ポーションとロングソードは位置交換の過程でそのままにしてます. もしほかに消耗品があれば同様の処置で構いません.
  • 16番のふくはかならず X がつくものをおいてください.
  • 17番以降は X の有無は関係ありません.
    • 17番では E の条件をみたすかわよろいをおいていますが、アイテム交換の過程でうまくいくならダガーで構いません.
  • ダガーとかわのたては12個ずつ買います. アイテム購入操作回数をへらすために 4 個単位で3回買っています. そのため残金がわずかです.
  • かわのたてのかわりにロングソードを交換品にすることも一応可能です. 10単位での買い物を組み合わせた上に、複数人から装備品を外す必要があり、早くありませんでした.

敵に遭う

今回の方法は外では成功しません. 村の中かダンジョンで遭ってください.

FF3 のゲーム前に RAM を初期化していない場合は "上下右スタートBA" を押して敵に遭ってください. この組み合わせで移動できる方向は右だけです. ウル内部の敵がでてくる場所はせまく、右に移動しづらいです.

FF3 のゲーム前に RAM を初期化している場合は移動前のボタン入力の制限はありません.

戦闘では下記の行動です.

  • 1人目(1度目): アイテムでナイフを使う
  • 2人目:
    • アイテム欄をすべて埋めるために武器を交換し続ける
    • 終わったら B ボタンで戻る
  • 1人目(2度目): B ボタンで一旦キャンセルし、コマンド欄に不正な文字列を出す
  • 1人目(3度目): 4番目のコマンドを選び戦闘強制終了
    • (前回のTASでは2度不正な文字列を出してましたが、今回は1度でいいです)

戦闘後

FF3 のゲーム前に RAM を初期化していない場合は 30 秒程度待つとエンディングが始まります. これは pointer が 0x07f8 から 0x0821 まで順番に村内の人をゆっくり動かしているのが原因です.

FF3 のゲーム前に RAM を初期化している場合はすぐにエンディングが始まります. こちらは pointer が 0x08f8 から 0x08f9 で読み込み数が少ないためです.

戦闘時の乱数

基本的に FF2 での説明と同じです.

Zeropage 総和

移動画面から戦闘画面に切り替わるときにプログラムは address $0000-$00ff の zeropage の carry 込みの総和を保存します. 総和は戦闘時の乱数 table への index となり、敵の数や行動の結果が決まります.

総和とパーティのパラメータが同じであれば、経過時間は関係なしに容易に結果が確定できます. 乱数 table の中身は総和の bit1 で 2 通り用意されています.

ゴブリン戦とランドタートル戦は戦闘直前にボタンを複数押すのは総和の調整のためです. (キラービー戦は別の理由)

乱数 index

乱数 index は memory address $0015, $0016, $0017 に利用され、初期値は総和で、乱数計算後に都度加算されていきます. FF3 では変数が3つに増えましたが、基本は $0016 を利用しているようです.

ゴブリン3匹では総和 bit1 = 0 で、戦闘終了時に address $0016 が data 0xfd の場合3個のポーションが落ちることがわかっています. 戦闘中の行動次第で調整できるはずですが、最速ではその実現は不可能でした.

その他見つかったが実用化されなかったもの

会話の省略

ウルの東西の細い道にある程度入った状態で敵を出せれば会話は省略できました. 歩数調整や徒歩時間の都合で使えませんでした.

別のアイテムの並び

未記載分は X 以前は Y:1, X 以後は N:1

 14:EY      : 1 15:Y       : 1
 16:Y       : 1 17:X       : 1
 18:N       :3+ 19:N       :3+
 20:N       : 1 21:N       : 2
 22:ポイゾナ:3+ 23:N       : 7
 24:N       : 1 25:W       : 1
  • EY は E と Y を同時に満たすもの. 実質ダガーのみ.
  • 13:Y:3+ がロングソードのみで必要金額が厳しい.
  • アイテム欄の並びが1つ手前に来るのが特徴. 実際に使ってみたところは手数が多いらしく早くなかった.
  6:Y       : 1  7:Y       :3+
  8:EY      : 1  9:Y       : 1
 16:Y       :3+ 17:Y       :3+
 18:Y       : 1 19:X       : 2
 20:ポイゾナ:3+ 21:N       : 7
 22:N       : 1 23:W       : 1
  • アイテム欄がかなり手前に来るのだが、 Y:3+ が3種類いるので道具屋に行くことが必須となったので不採用.
  • おそらく金額の制限にもひっかかる.

バグの詳細

続きは TASvideos に説明を書きましたのでご覧ください.
http://tasvideos.org/7197S.html

FF2 の script を調べた

他の作業がスランプのためファミコンソフトの解析をしてごまかしてます...

script data の解析

エンディング呼び出しだけ調べてましたが他のも調べました.

data 0x00 から 0xbf (1 byte)

キャラの移動と方向転換、あとは初期化ができるかもしれません.

databit
7:4 chara ID
3   ?
2   move flag (0:move, 1:方向転換のみ)
1:0 direction (0:right, 1:left, 2:down, 3:up)

bit 3 = 1 は初期化に使ってるようですが情報量が少なすぎるのでスクリプトだけではできない気がします.

data 0xc0 から 0xcf (1 byte)
databit
7:4 chara ID 0xc
3   mode
(bit 3 == 0 のときは data 0x00 から 0xbf と同じ)
(bit 3 == 1 のとき)
2:0 button を押したことにするためのコード

chara ID 0xc はプレイヤーを操作しますが、bit 3 == 1 のときは address $0020 を操作してます. 最初のヒルダに会うところとか定期航路での操舵とか聖堂での蘇生後に出る配置用メニューで使われています.

0xc8 は A ボタンを押したことにする操作でしたが、これで会話相手がいるといと event ID #1 が強制実行されたり、会話相手が各種店員だと想定外のことを言ったりしました. このバグの利用はできなさそうです.

data 0xd0 から 0xdf (1 byte)

address $6012-$6013 の data を 1 bit 単位で反転します.
エンディングが呼び出せるので相対的に重要ではないと感じています.

databit
7:4 0xd
3   byte offset
2:0 bit number

address $62bb (宿屋のイベントID 兼 ブリンクの本の武器としての熟練度経験値), data 0x5c の場合、スクリプトが address $6011 (セーブした外のY座標)から実行できますので、任意の 3 byte を生成するにはこれが利用できます. しかし address $6011 の操作にはその場所まで行くのがかなりの手間です.
さらに address $6012-$6013 のゲームの進行中のフラグの管理で操作するとアルテアの町が破壊されて宿屋が減ることを確認しています.

data 0xe0 から 0xef (1 byte)
e0 shake the screen
e1 run battle ship
e2 break battle ship
e3 light off screen?
e4 dance
e5 ???
e6 flash the screen
e7 go to epilogue
e8 sleep; wait key input
e9 tsunami
ea-ec swap backup chara status and chara #3
ed copy chara #3 to backup chara and add Lyla
ee add Leon to chara #3
ef remove chara #3?
  • e1-e3 はスクリプトが続行できないような動きでした.
  • ea-ec は実行アドレスが同じで違いはないみたいです.
  • ea を2度実行するとフラグだけで消えてる初期型レオンにいさんを追加
  • ed は#3にいるキャラを一旦離脱用に退避してレイラを追加
  • ee は後期型レオンにいさんを追加(上書き)
  • 他のキャラはスクリプトから呼べない
data 0xf0 から 0xf8 (2 byte)

引数が 1 byte あります. 各引数は想定値が大きくないものもあるので、変な値をいれるとちょっとおかしくなりますが、致命的なものはなさそうです.

f0 xx show message
f1 xx teleport to outside
f2 xx teleport to inside
f3 xx start battle
f4 xx same command f2
f5 xx set music
f6 xx bit flag set for address $6040-$6043
f7 xx bit flag clear for address $6040-$6043
f8 xx wait xx frames
data 0xf9 から 0xfc (2 byte)

1つ前に実行した移動系コマンドを引数の数繰り返します.
移動系以外をいれると無限ループになってしまうようです.

jump table で実行する PC がすべて同じで違いはないようです(自信なし).

data 0xfd (1 byte)

場所のスタックを pop します. デジョンレベル1と同じです.

data 0xfe (1 byte)

無限ループしてしまうようです. (data 0xff と同じ処理に見えるのですが...)

data 0xff (1 byte)

スクリプト終了で通常操作に戻ります.

TAS の説明の補足

宿屋で「はい」を選ぶときに address 0x0020 の data を 0xe7 にするとエンディングにいけるのは事実なのですが、動作の詳細が私の想定外でしたので記載します.

現状では script system は address 0x0020 を読み、その次の address 0x0021 の data が 0xe7 だったのでエンディングにいけます. address 0x0020 は「いいゆめを...」の反応のときに押すキーで、ここで引数をもつ script data をいれると address 0x0021 はコマンドとしては無効になります.

「いいゆめを...」のところは一旦ボタンを全部離してからなにか押すと動作が進みます. ボタンを1こだけ押すとなにかの移動コマンドになるので重要ではないのですが、ここに data 0xe4 をいれると宿屋のスタッフが踊ったあとエンディングにいけます. ただし指定したボタンを1度に(16.6ms 単位の精度で)全部押す必要があるので手動ではかなり厳しいです.

address 0x0021 は START, SELECT, B,A に関してはOFF->ON のトリガ検出のためのバッファで、方向は上下や左右の同時押しを解消しているプログラムになっているのは確認しましたがちょっと意味がわかりませんでした. そのために入力値にはいくつか制限があります.

bit7 は A ボタンのため 1, bit6:4 は自由, bit3:0 はよくわからないのですが入力可能値は 5 6 7 9 A B D E F の 9 通りです. 引数にあまり自由をもたせられないことになります.

つまり TAS の最後のボタン入力に何かしら演出をいれるとエンディングの発生が伸びますが、TAS としては終わっているので入力時間は変わらないことになります.

mruby のコンパイルがうまくいかない

楽しかった TAS は終わりで mruby の構築をはじめます.
いままでは pacman -S mruby でとってこれるバイナリパッケージを使ってましたが、 require が使えないことが大変によろしくないのでコンパイルをすることにしました.

一連の流れ

git clone https://github.com/mruby/mruby
cd mruby
cp build_config/default.rb build_config/myconfig.rb
(編集後)
rake -v MRUBY_CONFIG=myconfig

build_config/myconfig.rb は必要に応じて機能を追加削除できます.

自分は rake を使ったことがなかったので勝手を覚えるのに手間取りました. というのもこのビルドシステムは結構な規模がある上、並列処理もあるのでしょうけど、エラーメッセージが流れて追いかけるのが大変でした.

MRUBY_CONFIG=default なら msys2 であってもエラーがでなくて助かりました.

iij/mruby-require と fts.h

default ならバイナリパッケージでいいのでここからです. build_config/myconfig.rb に追加します.

  conf.gem :github => "iij/mruby-require"

そうすると自動的にソースを取ってきてビルドとなりますが、エラーが流れてしまって当初は気づきませんでした.

fts.h がないというのは、私の msys2 の中では /usr/include にはあって、 /mingw64/include にはないということです. さらに rake が /usr/include と書いても認識しないらしく、Windows として本当の絶対パスの d:/dev/msys64/usr/include と書いて認識しました.

次の問題が d:/dev/msys64/usr/include の path を build_config/myconfig.rb に書いてはならず、 build/repos/host/mruby-tempfile/mrbgem.rake に書き足せばコンパイルが通ります.

たぶんこれは msys と mingw を混ぜてはいけない注意が示す実例のようで、 /usr/include と /mingw64/include には重複するファイルもあり、混乱を抑えるために mruby-tempfile だけにしたらコンパイルが通っただけだと思われます.

さらに下記の msys ライブラリ (cygwin でいう POSIX 互換レイヤーだと思う)を足すとリンクも通ります.

    spec.linker.library_paths += ["d:/dev/msys64/usr/lib"]
    spec.linker.libraries << 'msys-2.0'

そしてリンクが通ったところで実行ファイル (mirb) は動かないです. require を抜けば動きます. ここどうしていいかわからないです.

仮に動いても msys 互換レイヤ込でバイナリ配布はあまり気が乗らず. fts って何のためにあるのか、どうせならなくしたい、そんな気持ちです.

正規表現ライブラリ

正規表現もバイナリパッケージに入っていません...

  conf.gem :github => "mattn/mruby-onig-regexp" #うごいた
  #conf.gem :github => "mattn/mruby-pcre-regexp" #ダメ
  #conf.gem :github => "pbosetti/mruby-hs-regexp", :branch => "master" #ダメ
  #conf.gem :github => "masamitsu-murase/mruby-hs-regexp", :branch => "master" #ダメ

たくさんあるみたいですが今でも使えるのは mattn/mruby-onig-regexp だけみたいでした. ビルドが通らない、ビルドが通っても落ちる. .... 楽しくないです.

不安定な mirb

基本機能はこれだけにしてます.

  conf.gembox "stdlib"
  conf.gembox "stdlib-ext"
  conf.gem :core => "mruby-math"
  conf.gem :core => "mruby-rational"
  conf.gem :core => "mruby-io"
  conf.gem :core => "mruby-print"

これと mattn/mruby-onig-regexp で mirb をためしたところ画像の感じです.
f:id:na6ko:20210610214849p:plain

特に未使用変数書いただけで Segmentation fault は勘弁して欲しいです.

FF2 の TAS を作った

先週土曜日に完成しました.
https://www.nicovideo.jp/watch/sm38840650

詳細はこっち.
http://tasvideos.org/7136S.html

今回の TAS を通じてわかったのは最適化作業はつまらなく Lua 言語がマジに作れば作るほど貧弱の2点です.

前回ここにかいた 67 回の AB キャンセルは減ることなく、大きな変更はなく投稿してしまいました. 実は昨日、ブリンクのほんを2冊(2刀流で)持つと AB キャンセルの回数は減るのかを調べ忘れていていまさら調べました. その回数は減りはしたんですが熟練度経験値への書き込みが偶数のみということで、各種手続きのほうが時間がかかるので(運良く)作り直しはありませんでした.

はじめての TAS は Lua で全部操作する仕組みでやりました. これは順番のいれかえや乱数調整がやりやすいのでよかったんですが、 Lua の言語仕様が悪い意味で簡単すぎて作り込みたくても作れなくて Squirrel ならできるのにーとかエラーで止まってくれるのに思ってました. 後者は相当きつくちょっとした記述ミスもだまって通ってしまうので修正作業が大変に苦痛でした.

いままで Cheap さんが見つけたものを解析して応用できたのが初めてだったのでちょっと無理をして自分が突っ走ってしまったのはあまりよくなかったかなと思ってます.

それにつきあってくれたピロ彦さんありがとうございました. ただ、彼のためにオーガメイジからのドロップという更新候補をプレゼントしましたし、どうも彼はその問題を解決できてしまったようです. つまり 1 週間ぐらいで TAS は更新されてしまうことになりそうです.

FF2 の皇帝呼び出しを調べたらさらに短縮できた

前回のイベント番号で CPU address $0072 の script (scenario) pointer へ設定される値は下記です. イベント番号@ pointer の table は CPU address $0d:$bfc0 にあり正規の値は 0 から 0x19 でそれを超えると MMC1 の bank 境界をまたぎます.

ROM

FF3 同様エンディングに直行できる script data は決まっていまして 2013 年に自分が調査しています.
https://na6ko.hatenadiary.jp/entry/20130806/p2
正規の手順でしたらイベント番号 0x0f の皇帝登場がまさにそれです. イベント番号 0x40 はエンディングの途中ですが初期化していない変数があるのか止まります.

0x00@ $8000: 0xc2 0xf9 0x07 0xc0 | 0x45@ $8d02: 0xb1 0xa2 0xa4 0x3c
0x01@ $8088: 0xc0 0xf9 0x18 0xc3 | 0x3b@ $8eff: 0xa2 0x46 0xc3 0xc3
0x02@ $8098: 0xc0 0xc0 0xc2 0xf9 | 0x2f@ $9c0c: 0xe5 0xc8 0xf0 0x0b
0x03@ $80a7: 0xf8 0x40 0x13 0x22 | 0x3a@ $a220: 0x1c 0xea 0x9c 0xf2
0x69@ $80a9: 0x13 0x22 0x10 0x21 | 0x26@ $a24c: 0xf3 0x1c 0xe1 0x9c
0x78@ $80bd: 0x12 0x12 0x11 0x12 | 0x65@ $a261: 0xe0 0x43 0xf2 0xb5
0x04@ $811b: 0xf8 0x10 0xe0 0xe0 | 0x6b@ $a262: 0x43 0xf2 0xb5 0x2a
0x05@ $8164: 0xf8 0x10 0xf0 0x11 | 0x48@ $a5dc: 0xbb 0xcb 0xf1 0xbb
0x06@ $81b7: 0xf8 0x10 0xe0 0x2a | 0x66@ $a900: 0xcb 0xfd 0x15 0xa9
0x07@ $8208: 0xc3 0xf9 0x04 0xf0 | 0x6c@ $a900: 0xcb 0xfd 0x15 0xa9
0x08@ $827f: 0xf8 0x10 0x03 0xf9 | 0x4b@ $a90f: 0x1e 0x2e 0x3e 0xfc
0x09@ $82c6: 0xf7 0x39 0x0f 0x1f | 0x3d@ $a99a: 0xa8 0xa1 0xa9 0x9a
0x0a@ $830b: 0xf8 0x40 0x03 0xf9 | 0x32@ $a9fe: 0xd8 0x18 0xed 0xf2
0x0b@ $839b: 0xf8 0x40 0x3a 0x32 | 0x44@ $a9fe: 0xd8 0x18 0xed 0xf2
0x0c@ $83e3: 0x0f 0x1f 0xf8 0x20 | 0x5b@ $ad27: 0x25 0x65 0x46 0xcb
0x0d@ $844d: 0xf8 0x80 0xc2 0xf9 | 0x56@ $b642: 0xf1 0xb8 0xf2 0x6b
0x0e@ $850f: 0xe6 0xe6 0xe6 0x28 | 0x52@ $b890: 0x3b 0xcb 0x3b 0xcb
0x0f@ $8531: 0xf8 0x40 0xf0 0x37 | 0x64@ $c08d: 0xa9 0x7f 0x20 0x00
0x60@ $8546: 0x1b 0x2b 0xc7 0x3a | 0x42@ $c46e: 0xa2 0x3f 0xa9 0xf0
0x5a@ $8560: 0x23 0xc3 0x03 0x13 | 0x58@ $c486: 0xa2 0xef 0xa9 0x00
0x5f@ $8560: 0x23 0xc3 0x03 0x13 | 0x3f@ $c4a0: 0xa9 0x30 0x8d 0x00
0x4c@ $8577: 0x13 0xc3 0x03 0x13 | 0x25@ $c74f: 0xa9 0x0d 0x20 0x03
0x79@ $85b4: 0xf9 0x06 0x5f 0x6c | 0x7f@ $c832: 0xa5 0xff 0x8d 0x00
0x40@ $8620: 0x44 0x26 0xf0 0x45 | 0x49@ $c9fa: 0xad 0x47 0x60 0x29
0x10@ $8690: 0xe6 0xe0 0xe6 0xe0 | 0x7b@ $ca41: 0x20 0x6f 0xd0 0x20
0x11@ $86b4: 0xe6 0xc4 0xe6 0xc6 | 0x28@ $dc30: 0xad 0x02 0x20 0xa9
0x12@ $86eb: 0xf8 0x40 0xc1 0xc1 | 0x22@ $dce3: 0xa9 0x05 0x85 0x61
0x13@ $8719: 0xf8 0x20 0xc4 0xc6 | 0x4a@ $f077: 0x20 0xe8 0xe4 0x91
0x14@ $875e: 0xc2 0xc1 0xf9 0x04 | 0x2b@ $f2c8: 0xad 0xf5 0x62 0x10
0x15@ $876f: 0xe6 0xe6 0x3a 0x32 | 0x4e@ $f476: 0xa9 0x88 0x85 0xff
0x16@ $877e: 0xe9 0xf4 0x17 0xf8 | 0x6a@ $f58d: 0xa5 0x61 0x29 0xe0
0x17@ $8789: 0xf3 0x78 0xf0 0x4d | 0x68@ $fa00: 0x4c 0x9e 0xfa 0x48
0x18@ $8790: 0x05 0xf0 0x4f 0xc1 | 0x38@ $fd85: 0x20 0xa5 0x00 0x8d
0x19@ $87c5: 0x2c 0x22 0x22 0xf9 | 0x6e@ $fe03: 0x4c 0x1a 0xfe 0x8d
0x36@ $88a9: 0x9c 0x91 0xaf 0xc4 | 0x35@ $fe85: 0x10 0x40 0xa9 0xc0
0x33@ $8d00: 0x7f 0x8c 0xb1 0xa2 | 0x37@ $ff85: 0x00 0x00 0x00 0x00

RAM + その他

この中でわかりやすいものは 0x43@ $0020 (ボタン入力)と 0x5c@ $6011 (セーブした外のY座標)です. 最初に気づいたのは CPU address $6011 のほうで徒歩やチョコボで移動してセーブし、エンディングにいけるデータを書き込み、宿屋からエンディングにいけることを確認しました. 目的の場所がかなり遠かったので最速プレイにはなりませんでした.

次に気づいたのは CPU address $0020 で宿屋で宿泊する際にボタン入力から必要なデータをいれると宿屋からエンディングにいけました. これはネリーを助けなくていいし、皇帝を倒さなくていいのでかなり早いです.

熟練度経験値を 0x43 にするために 67 回もたたかうを選びキャンセルすることになるのでまだ縮められるのではと考えています.

0x43@ $0020: RAM             |0x3e@ $2000: undef
0x29@ $184c: RAM mirror $004c|0x6d@ $2000: undef
0x39@ $008d: RAM             |0x34@ $2001: undef
0x2c@ $00a9: RAM             |0x57@ $2008: undef
0x63@ $08a9: RAM mirror $00a9|0x7e@ $2040: undef
0x7c@ $00a9: RAM             |0x7a@ $2048: undef
0x59@ $10ad: RAM mirror $00ad|0x67@ $207f: undef
0x5e@ $18ad: RAM mirror $00ad|0x41@ $20c4: undef
0x6f@ $00bd: RAM             |0x4d@ $20fa: undef
0x1a@ $0200: RAM             |0x2e@ $20fe: undef
0x1b@ $0200: RAM             |0x51@ $20fe: undef
0x1c@ $0200: RAM             |0x55@ $20fe: undef
0x1d@ $0200: RAM             |0x20@ $254c: undef
0x1e@ $0200: RAM             |0x61@ $2842: undef
0x1f@ $0200: RAM             |0x5d@ $2885: undef
0x2d@ $0320: RAM             |0x73@ $2985: undef
0x50@ $0320: RAM             |0x77@ $2a85: undef
0x54@ $0320: RAM             |0x47@ $3020: undef
0x31@ $034c: RAM             |0x62@ $32b0: undef
0x7d@ $158d: RAM mirror $058d|0x70@ $38b4: undef
0x30@ $0ea9: RAM mirror $06a9|0x75@ $38b4: undef
0x4f@ $0ea9: RAM mirror $06a9|0x72@ $3f29: undef
0x53@ $0ea9: RAM mirror $06a9|0x46@ $4014: undef
0x71@ $07e9: RAM             |0x74@ $40bd: undef
0x76@ $07e9: RAM             |0x21@ $4cc0: undef
0x5c@ $6011: RAM             |0x2a@ $4cc0: undef
0x3c@ $6f25: RAM             |0x27@ $4cdb: undef
                             |0x24@ $4cf3: undef
                             |0x23@ $4d4c: undef