ROM dumper からの ROM 以外へのアクセス

半年前から集中力が続かずあきらめ気味になっていたのですが、わりと新しい MCUファミコンカセットをつなぐ機器の第1回試作が終わりました. 第5回ぐらいまで試作はある予定なので完成する自信が全くありません. ですので期待しないでください.

今回はPCとMCUは仮想シリアルポートの形をとり teraterm などで簡単なコマンドプロンプトから ROM, RAM, 内部レジスタがみれるようにしています. (専用ソフトは今後作る予定です)

ASIC 内蔵の SRAM

今の所わかったのは旧設計の kazzo でもできていたカセット上の ROM や RAM (例外あり)に関しては新設計でも問題なく読み書きができています. それとは対照的に Namco 163 の内蔵SRAM(兼音源パラメータ)や MMC5 の Exteded RAM (CPU address $5c00-$5fff) はアクセスできていないのもかわりません.

163 のほうは電源投入直後は address regsiter と auto increment が動作しているようなのですがそれが停まってしまうようで RAM として使えていません. ですのでマインドシーカーのセーブデータ書き換えもできません.

MMC5 のほうは宇宙警備隊と同じレジスタをいじってるつもりですがまったく動いている様子がありません.

今回メモリアクセスの波形だけはそこそこマシにしているので以前から波形の再現精度が悪いので動かないと予想していたのですが別の理由のようです. 他のレジスタも全部キレイに初期化となると簡易コマンドではやってられません.

IRQ counter

163 のφ2カウンタはちゃんと動きました. ただこれはロジックアナライザで波形をみればどのタイミングで IRQ の線が L レベルになるのかがわかるのですが、 MCU で検出してタイミングをユーザーに使える方法が思いつきません.

MMC3 の PPU A12 カウンタはまったく動きません. 偶然に1度Lになって IRQ 禁止レジスタで H にしたのを計測できただけです. MMC3 の場合は CPU と PPU のバスを並列にみて動かしてるので、CPU と PPU のバスをつなげてしまっている設計では難しいのかもしれません.

MMC3 の IRQ のためのカウンタの仕組みは nesdev wiki やそこに貼ってあるリンクでゲートレベルでわかるのですが、それをおっかけてもちゃんと動かないのであれば、やはり ROM dumper としては無理なのか... と諦め気味です.

音源 (未着手)

音源はMHz単位のクロックを停めることなく供給し続けてそれを分周して可聴域の信号をつくるわけですから、リクエストが来るまでφ2が停まってる ROM dumper としてはこれも要求される性能が全然足りないと認識しています.

VRC7 なんかはφ2ではなく Xtal で供給しているのですが、音楽としてのリアルタイム性をもった仕組みを作るのは私の知識では大変です.

すいません

2日ぐらい前は USB や ROM アクセスが早く動くのでとても楽しかったのですがそれ以上は高いレベルを要求しすぎたのかちょっと残念になってしまいました. でも後日は気分を切り替えて楽しい紹介をしたいと思います.

くだらない英語の例

wget (ソフト名)を使ってファイルをダウンロードしました

  • I wget'ed a file.
  • I wgot a file.

くだけた英語で Email を出しましたは I emailed と名詞を動詞にすることがある. くだけた英語で wget を使いましたは emailed に従うと矛盾が発生する.

米国人曰く、wget'ed が自然らしい.

水を1杯ください

  • Give me a cup of water.

液体は数えられない名詞として容器1つを単位とするというのは初歩の英語でやる.

野球場でアイスクリームを食べました

  • I ate a helmet of icecream in the baseball stadium.

MLB の球場の売店では小さいヘルメットの形をした容器でアイスクリームが提供される. これは英文法上とても正しい英語なのにバカっぽくて笑える.

米国人にとってもバカっぽいのか別の言い回しで helmet of ... は使わないようにしているらしい.

FF3 のメモリ破壊応用例

youtu.be

cheap さんのアイディアを元にウルで戦闘をしたあとサラひめとウルに戻ってお祝いされる内容を組んでみました. 今回自分のアイテム並べ総当りツールに手をいれてたんですが、重要ではないアイテムの数量は1固定だったので、cheap さんの案のようにアイテム#0,同#1に3個以上置いた例は考えておりませんでした.

ですので並べ方はまだ何通りもありこれ(と半年前のTAS)が最適かは別です.

ほかに注意するのは安いかわのたてですが、address $0050 と address $0078 に data 0xc1 を書き込んでしまう恐れがあるのでアイテムの並びで保護するか左手を利用して埋める順番の調整が必要です. ロングソードで埋められればこんな手間はいらないのですがロングソードは高いのでお金が足りなくなりがちです.

Any % no credits warp 部門への問題提議

この部門のルールを厳格化するには現行記録を有効かつ私の案を無効にする線引がとても難しいです. 具体的には CPU address $006c と $0073 の操作を禁止するのが一番ですが、それを操作しているかを判定できる人が少なすぎます. 以下はその他の問題です.

戦闘中のアイテム欄溢れを禁止案

現行記録ではアイテム欄溢れを利用しています. メモリ破壊の原因は不正なジョブ番号 0x1e の戦闘コマンドを表示から始まりますので、手間と時間は増えますがアイテム変化技でそのジョブ番号にできればメモリ破壊ができてしまいます.

不正なジョブの利用を禁止案

現行記録ではアイテム変化技から不正なジョブ番号を利用しています. 仮に禁止にしたら no glitched jobs 部門と同じになってしまいます.

くらやみのくもをちゃんと倒せ案

現行記録では不正なジョブ番号 0xb7 で戦闘を強制終了しています.

FF3 のメモリ破壊とスクリプトポインタ

cheap さんご提案の内容を確認したら、ポインタ上位8bitに 0x64 (ミスリルかぶと)をいれて普通の会話(会話後 0xa3f8) と組わせてスクリプトポインタ(cpu address $0072) を 0x64f8 に設定していました.
そこから cpu address $6506-$650b にあるセーブデータその1の名前欄にスクリプトを埋め込み、アイテム取得イベントを起こして任意のアイテムを手に入れてます.

スクリプトポインタが本来取りうる値は 0x98xx-0xb3xx あたりですので、これを利用するとウルとカズスに行くだけでどこでもイベントを呼び出せる状態です. さらに先述の名前欄経由でゲーム進行上のフラグも立てることができます.

ポインタの値で終盤ばかりですが解説しておきます.

$ae00 あたり, やみのせかいへ移動

$ae00 (めぐすり+resetによる初期化)でよべます.

$af3b あたり. 倒せるくらやみのくもとの戦闘

$aef8 (めぐすり+会話), $af00 (どくけし+resetによる初期化), $af17 (どくけし+宿泊)で呼べます. 本来はダーククリスタル関連のフラグはチェックした後のイベントなので2ヘッドドラゴンたちを倒したことにしなくてもいいです.

$aff8 あたり. サロニアに帰る

$aff8 (どくけし+会話)でできます.

ここまで外の大陸のため CPU address $0078 へ data 3 を設定する必要があります. このアドレスと CPU address $0050 (店やポエム朗読が呼べる)はかわのたてをおくと data 0xc1 (半濁音記号) で破壊されやすいので、埋めるアイテム順番をかえるか、無難なアイテムを先においておくほうがよいです.

$b0f8 あたり. ウルに帰る.

$b0d8 (ラッコのあたま+いきかえり)で飛べます. $b0f8 だとウルの中からウルの入口へ移動できないようなのでちゃんと動きませんでした.

進行上は浮遊大陸に戻っているので CPU address $0078 を設定する必要がありません. data 0 以外だとおかしくなります.

重要な終盤イベントは偶然にめぐすりとどくけしから生成できるわけですが、本当に STAFF CREDIT が始まる直前はラッコのあたま(0xb0)かボムのかけら(0xb1)が必要です.

ラッコのあたまを名前欄から生成しておけばそれも可能です.

$b165 あたり. ウルでの最後の会話

$b148 (ボムのかけら+やまびこそうをもらう)

ここにジャンプすると any % との違いはほとんどないのですが no credits warp の寸前ではあります.

FF3 の Any % no credits warp 部門への問題提議

Final Fantasy III - speedrun.com
先日たまたまそのあたりを見ていたら重要なことに気づきました. この部門の名目は「なにをやってもいいがクレジットに(直接)飛ばない」というもので実際にはアイテム溢れなりアイテム変化のバグを駆使した10年前のピロ彦さんのTASをなぞっているだけでした. なんでもいいからたおせるくらやみのくもの戦闘を終わらせた時点で時間計測終了です.

アイテム溢れから発端するメモリ破壊の仕組みを理解した半年前に、倒せるくらやみのくもの戦闘前後のイベントを無理やり実行できることはわかっていました. つまり、ウルの村でメモリ破壊をしてくらやみのくもを呼べば現行記録より大幅な短縮が可能です.

実際に試したところ、現行の Credit に直接飛ぶ Any % とやることがほとんど同じで目的が達成できてしまいました. くらやみのくもは強制終了できないのでパラメータをいじって倒すか倒したことにする必要があると予想していたのですが、それも不要でした.

ルールはなにも逸脱していないのに、ゲームとして遊ぶところが皆無になってしまったため実験成功と同時に困ってしまいました. Any %部門との違いはアイテムの並べ方が若干変わることくらやみのくも関連の演出の待ち時間が長いだけです. やり方も動画も資料はそろえてあるのですが公開するのにためらっています.

FF3 のスクリプトデータの解析

cheap さんの掲示板に投稿しようとおもったのですが、.pl ファイルが not found で投稿できませんでした. (直してくださったらこの件はそっちに書きます)

[引数なし]
00-bf (未調査) FF2 と同じで町人などの移動や方向制御だと思う
c0-cf プレイヤーキャラの移動や方向制御
d0 ゆれる
d1 雷鳴
d2 暗くなる
d3 明るくなる
d4 HP回復
d5 MP回復
d6 隠し通路解錠関連その1
d7 隠し通路解錠関連その2
d8 reset game
d9 アムル西に船を停泊
da dance
db opening
dc poem
dd ending
de なにかのフラグセットと浮上後(寸前)の外の大陸にいく.; b@6011 |= 0x80; b@0078 = 3
df いきかえり
e0 PC $b64f; 外マップでは止まらないが詳細不明
e1 PC $b668; 同上
e2 ひかる
fd 1つ前に移動(デジョンx1).
fe 繰り返し(?)
ff script 終了

[引数1つ]
e3-e6 xx 効果なし; rts のみ
e7 xx アイテム1つ消費. 引数はアイテムID
e8 xx b@6102 &= xx; 状態異常解除(全員), 0x00 で全部解除
e9 xx b@6102 |= xx; 状態異常付加(全員), 0xff で全部付加
ea xx w@601d += xx; 所持金を256ぎる単位で加算
eb xx w@601d -= xx; 上記の減算版
ec xx b@601b += xx; キャパシティ加算
ed xx b@601b -= xx; 上記の減算版
ee xx b@600b = xx; NPC 設定
ef xx 外で起こすようなスプライト制御系統のイベント
f0 xx セリフを出すその1
f1 xx セリフを出すその2
f2 xx address $6020-$603f の RAM へ OR 演算で bit を立てる. ゲーム進行のフラグを立てている. 引数の bit2:0 が対象 data bit で、 bit7:3 が address offset.
f3 xx 上記の AND 演算で bit を下げる版
f4 xx script data 0xf2 から 対象アドレスを $6080-$609f に変えたもの.
f5 xx 上記の AND 演算で bit を下げる版
f6 xx script data 0xe7 の消費を取得に変えたもの
f7 xx 戦闘開始. 引数は敵のグループ番号.
f8 xx BGM 変更. 引数は曲番号.
f9 xx 特定の外の場所へ移動
fa xx 特定の屋内の場所へ移動
fb xx 用途不明; b@0045 = xx; b@0044 = $80
fc xx xx frames 待機

注)

  • 移動,戦闘,演出は滞在中の大陸や屋内屋外で挙動が変わるものが結構ある
  • b@6102 は address $6102 を byte 単位で扱っての意味. w@601d は little endian 16 bit 単位で扱う.
  • &, |, ^ 演算子の意味は C と同じ
  • セリフコマンドが2種類ある理由は未調査

(2022年1月19日追記)
command 0xf3 と 0xf5 が XOR 演算版と書いていましたが誤りでした. 該当部は訂正しました. さらに cheap さんにその間違いをアドバイスするという失態. すいませんでした.

言い訳なんですが FF2 は XOR 演算だった先入観と $b9a6 eor #$ff; $and $6020,y を $b9a6 eor $6020,y とななめ読みして、早合点してました.

f:id:na6ko:20220119005012p:plain

ファミコンの 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) をきれいに直す方法を探る必要があります.