スターソルジャー (FC) 解析資料

Published on: 2021-06-10
Updated on: 2021-06-19

敵AI

ほとんどの敵のAIはいわゆるバイトコードで実装されている。
つまり、直接 6502 で書くのではなく、「ある方向へ移動する」「弾を撃つ」などの命令を備えたバイトコードで記述し、それを 6502 で書かれた仮想マシン上で動かしている。
この方式の最大の利点は「処理の中断」が可能なことだろう。例えば「30フレーム横へ動き、60フレーム下へ動き、...」などといった処理を 6502 で直接書くと、多くの場合複雑なステートマシンを実装することになり面倒である。

バイトコードでなく専用ルーチンで動いている敵はルイド(ID: 0x11), リューク(ID: 0x14), ソープラー(ID: 0x21), スターブレイン(ID: 0x22, 0x23)のみ。
ここではバイトコードに関する解説のみ行う。

以下、バイトコードで実装された敵のうちジェリコ、ラザロを「ボス」、それ以外を「ザコ」と総称する。

ツール

バイトコードのアセンブラ/逆アセンブラを作成した。以下、命令のニーモニックはこのアセンブラを基準とする。

playground で実際のバイトコードの動作が見られる。

仮想マシン

敵1体ごと(ボスの場合、1パーツごと)に仮想マシンが割り当てられる。
なお、1つの敵編隊内ではバイトコードが共有されるが、エントリポイントは敵1体ごとに異なる。

ここでは仮想マシンの実装をやや抽象化して記述する。また、レジスタ名などは筆者が独自に命名したものである。

アドレス空間

仮想マシンのアドレス空間は 8bit で、ROM 上のバイトコードがマップされる。このアドレス空間は読み取り専用である。

レジスタ

仮想マシンは以下のレジスタを持つ:

名前用途
pcu8プログラムカウンタ
loop_counteru8ループカウンタ
loop_start_addru8ループ開始アドレス
jump_on_damageu8ザコの場合:被弾時のジャンプ先
ボスの場合:HP
sleep_timeru8仮想マシンのスリープタイマー
homing_timeru8自機追尾タイマー
inv_xboolx 方向の移動を反転
inv_ybooly 方向の移動を反転

ループ関連レジスタが1組しかないことからわかるように、1重ループのみサポートしている。

仮想マシンの動作

毎フレーム以下の動作を行う:

命令セット

仮想マシンは以下の命令を持つ:

オペコードニーモニック機能実行の中断
0x00..=0x3Fmove敵を指定方向へ移動Yes
0x40jumpジャンプNo
0x41..=0x4Fset_sleep_timersleep_timer を設定Yes
0x50, 0x52..=0x5Floop_begin指定回数のループを開始No
0x51loop_endループ末尾No
0x60..=0x6Fshoot_direction方向指定弾を撃つ (未使用)No
0x70..=0x7Fset_sprite敵メタスプライトIDを設定No
0x80..=0x8Fset_homing_timerhoming_timer を設定No
0x90..=0x93set_inversioninv_x, inv_y を設定No
0xA0set_position敵座標を設定No
0xA1set_jump_on_damagejump_on_damage を設定Yes
0xA2increment_sprite敵メタスプライトIDをインクリメントNo
0xA3decrement_sprite敵メタスプライトIDをデクリメントNo
0xA4set_part敵パーツ番号を設定No
0xA5randomize_x敵座標x をランダムに変更No
0xA6randomize_y敵座標y をランダムに変更No
0xB0bcc_x(敵座標x) < (自機座標x) ならば分岐No
0xB1bcs_x(敵座標x) >= (自機座標x) ならば分岐No
0xB2bcc_y(敵座標y) < (自機座標y) ならば分岐No
0xB3bcs_y(敵座標y) >= (自機座標y) ならば分岐No
0xC0..=0xCFshoot_aim自機狙い弾を撃つNo
0xF0restore_musicBGMをデフォルトに戻すNo
0xF1..=0xFFplay_sound指定した効果音を鳴らすNo

0x00..=0x3F (move)

1Byte 命令。

敵をオペコードの表す方向へ移動させる。
inv_x, inv_y の設定に従って方向が反転する。

敵が高速化フラグを持つとき、特定条件下でスピードが増す。

実行を中断する。
ただし、敵が再行動フラグを持つとき、特定条件下でさらに 1 ステップ実行する。

0x40 (jump)

2Byte 命令。0x40 <addr> の形式。

プログラムカウンタを <addr> に設定する。

実行を継続する。

0x41..=0x4F (set_sleep_timer)

1Byte 命令。

sleep_timer の値を 4*(オペコード下位4bit) にする。

実行を中断する。

0x50, 0x52..=0x5F (loop_begin)

1Byte 命令。

loop_counter の値をオペコード下位4bitに、loop_start_addr の値を次命令のアドレスにする。
オペコード 0x50 は 256 回のループとなることに注意。

実行を継続する。

0x51 (loop_end)

1Byte 命令。

loop_counter をデクリメントし、その結果が 0 なら次命令のアドレスへ、さもなくば loop_start_addr へジャンプする。

実行を継続する。

0x60..=0x6F (shoot_direction)

1Byte 命令。ゲーム内では未使用。

オペコード下位4bitの方向に方向指定弾を撃つことを試みる。
敵編隊パラメータの影響を受けない。

敵弾ランクなどの影響により、発射がキャンセルされることがある。
また、特定条件で誘導弾になることがある。

実行を継続する。

0x70..=0x7F (set_sprite)

1Byte 命令。

敵メタスプライトIDを (基本値)+(オペコード下位4bit) にする。

実行を継続する。

0x80..=0x8F (set_homing_timer)

1Byte 命令。

オペコード 0x80 なら homing_timer の値を 252 にする。
それ以外の場合、homing_timer の値を 4*(オペコード下位4bit) にする。

自機追尾処理に戻って実行を継続する。

0x90..=0x93 (set_inversion)

1Byte 命令。

inv_x をオペコードの bit0 に、inv_y をオペコードの bit1 にする。

実行を継続する。

0xA0 (set_position)

3Byte 命令。0xA0 <x> <y> の形式。

敵座標x を <x> に、敵座標y を <y> にする。

実行を継続する。

0xA1 (set_jump_on_damage)

2Byte 命令。0xA1 <addr> の形式。

jump_on_damage<addr> にする。

実行を中断する。

0xA2 (increment_sprite)

1Byte 命令。

敵メタスプライトIDをインクリメントする。

実行を継続する。

0xA3 (decrement_sprite)

1Byte 命令。

敵メタスプライトIDをデクリメントする。

実行を継続する。

0xA4 (set_part)

2Byte 命令。0xA4 <part> の形式。

敵パーツ番号を <part> にする。

実行を継続する。

0xA5 (randomize_x)

2Byte 命令。0xA5 <mask> の形式。

敵座標x の <mask> で指定されたbitたちをランダムに変更する。

実行を継続する。

0xA6 (randomize_y)

2Byte 命令。0xA6 <mask> の形式。

敵座標y の <mask> で指定されたbitたちをランダムに変更する。

実行を継続する。

0xB0..=0xB3 (bcc_x, bcs_x, bcc_y, bcs_y)

2Byte 命令。0xB0..=0xB3 <addr> の形式。

敵座標と自機座標を比較し、条件を満たしていれば <addr> へ、さもなくば次命令のアドレスへジャンプする。

実行を継続する。

0xC0..=0xCF (shoot_aim)

1Byte 命令。
オペコード下位4bitは意味を持たない(何らかの拡張予定があったのかも)。

自機狙い弾を撃つことを試みる。
弾抑制フラグ弾高速化フラグ誘導弾化フラグの影響を受ける。

敵弾ランクなどの影響により、発射がキャンセルされることがある。
また、誘導弾化フラグを含む各種条件により誘導弾になることがある。

0xF0 (restore_music)

1Byte 命令。

BGMをデフォルトに戻す。ラザロのBGMを元に戻すのに使われる。
ただし、現在ゲームオーバーBGMが流れている場合は何もしない。

実行を継続する。

0xF1..=0xFF (play_sound)

1Byte 命令。

オペコード下位4bitの効果音を鳴らす。

実行を継続する。