引き続きMight and Magic TAS動画に挑んでいます。クリアタイムを縮めるには「わざとエンカウントして逃げる」「メッセージスキップ」の2点がほしいですが、メモリダンプを見ていても全く発生条件がわかりません。仕方ないのでファミコンのCPU 6502のアセンブラを読んでいます。もはやリバースエンジニアリングです。
命令が非常にシンプルで、レジスタ幅は8bit、レジスタ数はA, X, Yの3つしかありません。読んでいる分には面白いですが、これでソフト書くことを想像すると辛いです。昔の人はこんなん書いてたのかあ……。ま、それはさておいて、プログラムの解析を行った結果、エンカウントのルールが「半分だけ」分かりました。
カウントの条件判定関数(アドレス0xE85Dとそのサブ関数)をLuaで書き直しました。BizHawkのLuaも初めて使ったので書き方がわからず、1回Pythonで書いてから移植するという無駄な行為をしました……。中心となる条件判定関数はこんな感じです。
function rol(v)
local ncarry = (bit.band(v, 0x80) ~= 0)
v = bit.lshift(v, 1)
v = bit.band(v, 0xff)
if (carry) then
v = bit.bor(v, 0x1)
end
carry = ncarry
return v
end
function ror(v)
local ncarry = (bit.band(v, 1) ~= 0)
v = bit.rshift(v, 1)
v = bit.band(v, 0xff)
if (carry) then
v = bit.bor(v, 0x80)
end
carry = ncarry
return v
end
function adc(v1, v2)
v1 = v1 + v2
if (carry) then
v1 = v1 + 1
end
carry = (v1 > 0xff)
return bit.band(v1, 0xff)
end
function sbc(v1, v2)
v1 = v1 - v2
if (not carry) then
v1 = v1 - 1
end
carry = not (v1 < 0)
return bit.band(v1, 0xff)
end
function judge(v0, v1, v2)
local v24e = v0
v523 = v1
v524 = v2
-- Addr: E8A1
local v248 = 0x73
local v249 = 0xc
-- Addr: E8AB
local v24a = v523
local v24b = v524
local v24c = v523
local v24d = v524
-- Addr: E8BF...E8D2
for x = 7, 1, -1 do
v24c = rol(v24c)
v248 = ror(v248)
if (carry) then
carry = false
ra = adc(v24a, v24c)
v24a = ra
end
end
-- Addr: E8D4
carry = true
v24a = sbc(ra, v24b)
-- Addr: E8DD...E8F0
for x = 4, 1, -1 do
v24d = rol(v24d)
v249 = ror(v249)
if (carry) then
carry = false
ra = adc(v24b, v24d)
v24b = ra
end
end
-- Addr: E8F2
carry = true
v24b = sbc(ra, v24a)
v524 = v24b
-- Addr: E8FC
carry = true
v24a = sbc(v24a, v24b)
v523 = v24a
----------------------------------------------
-- Addr: E871
v24f = v24a
ra = 0
-- Addr: E87B ... E88F
for x = 8, 1, -1 do
v24f = rol(v24f)
ra = rol(ra)
carry = true
ra = sbc(ra, v24e)
if (not carry) then
ra = adc(ra, v24e)
end
end
-- Addr: E891
v24e = ra
v24e = v24e + 1
return v24e
end
引数のv0には現在いるマップから決まる一定の値(アドレス0x6191の値)を渡し、引数v1, v2にはカウンタ0x523, 0x524の値を渡します。エンカウント判定の結果が1であれば敵とのエンカウントという意味になります。あとjudge() を呼ぶ前に、グローバル変数のcarry = falseにしておかないと結果が狂います。
内部でキャリークリアしとけ、って思われるかもしれませんがcarry = trueで呼ぶケース(今回は追っかけていませんが)もあるので、勝手にキャリークリアしてはいかんのです……。
話を元に戻すと、カウンタを255フレーム分、変化させながらこの判定関数を呼ぶと、今から何フレーム後にエンカウントするか予測できるわけです。
例えば、上記の画像だと(43, 157, 179)と出ています。これは43フレーム目にエンカウント処理が真と判定されるという意味です。ややこしいことに、Might and Magicのエンカウント判定は移動するボタン(前後左右、Bボタンのどれか)を押した「次のフレーム」に行われるため、42フレーム後に移動すると必ずエンカウントします。同様の理屈で156フレーム後、178フレーム後も移動すると必ずエンカウントします。
エンカウントを理解できたぞ、これならエンカウント楽勝だろ!と意気込んで地上MAPに行ってみたら、町以外(地上、ダンジョンなど)はカウンタの増減ルールが全く違っていて予測は微塵も機能しませんでした。ええ……そんな……。
町だと1フレームに1回しかコントローラの状態を見ないため、カウンタも1しか変化しません。そのため予測が楽でした。ところが町以外のMAPはアイドル時間に全力でコントローラの状態を見るため、70〜80くらいカウントが一気に変わり、さらに嫌なことに毎フレーム変化量が違うので予測ができません。こりゃ無理ですね。
エンカウントと並ぶもう一方の難問「メッセージスキップ」のルールは全く分かりません。基本的にはボタンを押すとメッセージがスキップされるはずなのに、そうじゃない時間がかなりあります。プログラムを解析していると8フレームほど入力を全く見ていない瞬間が何カ所かあって、スキップと関連していそうでしたが、仕組みの解明には至りませんでした。
メッセージスキップを諦めるとかなり時間を食ってしまう(最速スキップで11〜16フレーム、スキップしないと160〜170フレーム)ので、困りましたね。
< | 2021 | > | ||||
<< | < | 10 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | - | - | - | - | 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | - | - | - | - | - | - |
合計:
本日:
管理者: Katsuhiro Suzuki(katsuhiro( a t )katsuster.net)
This is Simple Diary 1.0
Copyright(C) Katsuhiro Suzuki 2006-2023.
Powered by PHP 8.2.15.
using GD bundled (2.1.0 compatible)(png support.)