スタックマシン
他言語にて作成されたのか、16bit幅のソフトウェアスタック操作による処理が数多く見受けられます。 $0600-$06FFがソフトウェアスタックとして使用されています。($0600-$067F:下位, $0680-$06FF:上位)
ソフトウェアスタック操作サブルーチンを呼ぶことで、スタックに積まれた16bit値を1, 2個POPし、演算結果を1個PUSHします。
スタックポインタとしてXレジスタを使用しており、随所にXレジスタの退避・復帰処理が存在します。
JSR地獄
ソフトウェアスタック操作やそれを用いた細切れのサブルーチン処理が多いため、JSR命令が大量に並ぶことも珍しくありません。 時々ネイティブ命令を吐いたり、最後のJSR命令をJMP命令に置き換える程度の最適化はあるようです。
; Check the destination tile and invalidate the controller input
; if it is a wall attribute.
; Wall : 0, 1, 6, 7, 8, 9, 10, 11, 12, 16
; Argument:
; Stack[0] = Y offset (base:6)
; Stack[1] = X offset (base:8)
VmProc_CheckFieldDestinationTile:
B8E5 : 20 EA 96 JSR VmHelper_SaveMemTemp_06 ;prologue
B8E8 : 20 E1 C0 JSR VmProc_GetMemoryOffsetY_ChipY
B8EB : 20 4E 97 JSR VmOp_Add_AB ;(Y + (6 + yArg)) % 15
B8EE : 20 C1 B8 JSR VmProc_ModScreenTileYOffset
B8F1 : 20 17 98 JSR VmOp_Asl_04
B8F4 : 20 AE 95 JSR VmOp_Swap ;save Y, calculate X
B8F7 : 20 A6 BC JSR VmHelper_PushFieldChipX
B8FA : 20 4E 97 JSR VmOp_Add_AB ;(X + (8 + xArg)) % 16
B8FD : 20 D3 B8 JSR VmProc_ModScreenTileXOffset
B900 : 20 4E 97 JSR VmOp_Add_AB ;(Y << 4) + X
B903 : 20 8D BC JSR VmHelper_PushConst_0500 ;$0500 = Map data (reg Y = 0)
B906 : 20 8D 95 JSR VmOp_ReadOffsetByte ;read map tile
B909 : 20 FE 96 JSR VmHelper_WriteTempAddressWord_02
B90C : 20 27 97 JSR VmHelper_ReadTempAddressWord_02
B90F : 20 49 98 JSR VmHelper_PushConst_0000
B912 : 20 C5 94 JSR VmOp_Compare_Equal ;if( (tile == 0) // sea
B915 : 20 27 97 JSR VmHelper_ReadTempAddressWord_02
B918 : 20 4D 98 JSR VmHelper_PushConst_0001
B91B : 20 C5 94 JSR VmOp_Compare_Equal ; || (tile == 1) // bushes
B91E : 20 2D 95 JSR VmOp_Or
B921 : 20 27 97 JSR VmHelper_ReadTempAddressWord_02
B924 : 20 89 98 JSR VmHelper_PushConst_0010 ; || (tile == 16) // door of time
B927 : 20 C5 94 JSR VmOp_Compare_Equal
B92A : 20 2D 95 JSR VmOp_Or
B92D : 20 27 97 JSR VmHelper_ReadTempAddressWord_02
B930 : 20 61 98 JSR VmHelper_PushConst_0006
B933 : 20 12 95 JSR VmOp_Compare_BGeatherThanOrEqual ; || ((6 <= tile) && (tile <= 12))
B936 : 20 27 97 JSR VmHelper_ReadTempAddressWord_02
B939 : 20 79 98 JSR VmHelper_PushConst_000C
B93C : 20 0B 95 JSR VmOp_Compare_AGeatherThanOrEqual
B93F : 20 19 95 JSR VmOp_And
B942 : 20 2D 95 JSR VmOp_Or
B945 : 20 C9 95 JSR VmOp_CheckNotZero ;){
B948 : 90 03 BCC .Accept ; ClearJoypad();
B94A : 20 BB BA JSR ClearJoypad ;}
B94D : 4C B3 96 .Accept JMP VmOp_ReadTempMemAddr ;epilogue
JSR, RTS命令はそれぞれ6サイクルかかるため、サブルーチン呼び出し1セットにつき12サイクルを消費します。上記ルーチンは34個のJSR命令があるため408サイクル。
1フレームにかけられるサイクル数は29780.5サイクルなので、これだけで1.4%の時間を消費してしまいます。
手続型メインループ
当然処理時間はネイティブ実装よりも圧倒的に劣るため、1フレームに処理できる内容はわずかになります。
フィールド画面では32フレーム、戦闘画面では10フレームを1ループとして動作しており、その中で1フレームだけコントローラーを読んでいます。
つまり、32フレームに一回しかコントローラー入力を受け付けないということになります。
エミュレータのラグフレームカウンタはコントローラ読み取りを元にしていることが多く、このゲームの場合ほぼ毎フレームカウンタが増えることになります。
32フレーム周期では、このフレームは移動先決定処理、次フレーム待機、目的座標へ1px移動、次フレーム待機、…といった流れになっています。
フレームの待機時には、VBlankフラグ監視ルーチンに入るタイプで、サブルーチンをすべて抜けて無限ループに戻ることはありません。