TOP > プログラミング関係解説&調査 > テレポートの仕様解説

テレポートの仕様解説

当ページの内容は、ある程度のプログラミングの知識を必要とする。
まず、プログラミング関係解説&調査をひととおり読んでいただきたい。

本作のデータや各種計算式(ダメージ量の算出方法など)を掲載している、世界の合言葉は森部様には、アキラがパーティにいる時に「逃げる」コマンドの代わりになる「テレポート」についても仕様解説がある。
ここでは、仕様解説を元にして、プログラムではどうなっているのかなどについて、少し踏み込んだ説明をする。

また、「テレポート」で使われている疑似乱数計算方法は、近未来編のアイテム改造の確率計算など、他の場所でも使われていることも判明したので、乱数の計算方法と共に別ページで紹介していく。

このページでは、「テレポート」について説明をする。

以下、「世界の合言葉は森部」様による、テレポートの仕様の説明テキストファイルから引用した。

C0/378F-

乱数1:128以上でテレポート先変更なし (Y:48EF+00)

乱数1:128未満でテレポートイベント発生
( 乱数2・3・4は0~98までの様子)
 乱数2:66~98以上で台所に
 乱数2:0~32でトイレに
 乱数2:33~65で風呂に
  乱数3:0~32、66~98何も起こらず
  乱数3:33~65で
   乱数4:33~98で何も起こらず
   乱数4:0~32で妙子イベント

妙子イベントは1/2*1/3*1/3*1/3で1/54だろうか?

ここで、

C0/378F-
(Y:48EF+00)

という、2ヶ所の謎の文字列が何か気になる方もいらっしゃるかもしれない(筆者もだが)。
これは、本作におけるプログラムに関連した数値である。
前ページの、スーパーファミコンのプログラム・65C816関連の基礎知識を読んでいただいたという前提で説明をする。

上の「C0/378F-」は、「ライブ・ア・ライブ」のゲームソフト内データのアドレス「10378F」以降に記録されている文字列3バイト(つまり、10378F, 103790, 103791番目の文字)を、スーパーファミコンのメモリ領域にロードした時の位置である。
その3バイトの文字列は「20 00 B9」で、65C816においては「JSR $B900」という命令になる。
このJSRという命令は「Jump to Subroutine」、指示された位置に記してある計算手順へジャンプしろという指示になる。
位置は$B900と指定されており、細かい説明は省くが、スーパーファミコンのメモリ領域における$C0/B900という位置を意味している。
この$C0/B900から始まるサブルーチン(プログラム)で、テレポート先を決める計算が行われている、という意味での「C0/378F-」なのである。

「Y:48EF+00」についてだが、前ページの「レジスタ」の項目で説明した、「インデックスレジスタY」が関係してくる。
JSR $B900」が実行されるタイミングで、インデックスレジスタYには16進法2バイト分の「48EF」という値が記録されている。16進数の値なので、この先では「$48EF」と、頭に「$」を付けて、10進数とは区別する。
(※なお、乱数1が48EFである、という意味ではない)

JSR $B900」の後に実行される命令で、まず、乱数1を算出する(乱数計算方法は次のページで紹介している)。
次に、「乱数1が128以上かどうか?」という判定が入り、128以上なら判定後に通常の「逃げる」を行うサブルーチンに移動する。
128未満だと、「インデックスレジスタYの値を+3する = $48F2」という処理をしてから、テレポート先を決める乱数2以降の判定が始まる。
ここでの+3は16進数表記での足し算なので$48EF + $3だと一番下の位の$Fが繰り上がり、2番目の位が$Eから$Fになり、結果、答えが$48F2となる、という点に注意。

乱数2以降の判定は、以下のように行われる。

  1. 最初に「乱数2は66以上か」を判定して、66以上なら台所に飛ぶ。
    66未満なら2.へ。
  2. 「インデックスレジスタYの値を+2する = 48F4」という処理をしてから、「乱数2は33以上か」を判定して、33以上なら風呂に飛ぶ。
    33未満なら3.へ。
  3. 「インデックスレジスタYの値を+2する = 48F6」という処理をしてから、トイレに飛ぶ。

つまりテレポート失敗時は必ずインデックスレジスタYの値が増え、その値でどこに飛ぶか判断可能であるから、
Y:48EF+00
と、インデックスレジスタY「Y:48EF」が「+00」されていく、という意味で記してあるのだろう。


ここまでは実際のプログラムは紹介せずに説明したが、この先は一歩踏み込んで、実際のプログラムを紹介しながら説明する。
ただし、乱数の計算方法については別ページとする。
実は本作における乱数の計算方法は2種類ある。
戦闘中で乱数が必要となった時に計算して作る「戦闘用乱数」と、マップ(フィールド)にいる時に1/60秒に1回のサブルーチンで計算されて作られる「マップ乱数」である。
このふたつは、計算方法も全く異なるし、それぞれの説明で1ページ分を要するので、ここではあくまでもテレポートの仕様に絞る。

以下、テレポートコマンド実行時の、重要な部分のみ抜粋したサブルーチンである。
わかりやすいよう一応色分けをしてみたが、赤い文字の$C0/378Fはメモリ領域のアドレスで、青い文字のJSR $B900はそのアドレスに書かれた命令文である。
重要な処理は太字にした。
インデックスレジスタYに収納された値を「Y:48EF」のように併記する。
セミコロン「;」の後はコメント扱いで処理の簡単な解説。
以下、アキュムレータはA、インデックスレジスタXはX、インデックスレジスタYはYとする。
ここまでで説明していない用語も混ざっているが、わからなくても特に問題はない。
また、乱数生成のサブルーチンについては省略した。
「ここでの乱数生成にはマップ乱数生成のサブルーチンを2回使っている」「マップ乱数を生成すると、その乱数がXに入った状態になる」ことだけ覚えていただければ良い。
詳しい説明は後に記してある。

$C0/378F JSR $B900    ;Y:48EF [$C0:B900]へジャンプ

;($C0:B900~の処理省略。ここで1つ目のマップ乱数を生成して、Xレジスタに乱数が入る)

$C0/3792 PLB          ;Y:48EF DBレジスタにスタックから値をプル
$C0/3793 CPX #$0080   ;Y:48EF Xの数値と$0080(10進数の128)を減算比較(X - $0080)
$C0/3796 BCS $05      ;Y:48EF キャリーフラグが立っているなら[$C0:379D]へ(テレポート成功)
;この先はテレポート失敗時の処理
$C0/3798 INY          ;Y:48EF Yをインクリメント
$C0/3799 INY          ;Y:48F0 Yをインクリメント
$C0/379A BRL $E404    ;Y:48F1 [$C0:1BA1]へジャンプ

$C0/1BA1 LDA $00022E  ;Y:48F1 Aに[$00:022E]の値をロード
;(省略)
$C0/1C23 INY          ;Y:48F1 Yをインクリメント
;(省略)
$C0/1C2A JMP ($429F,x);Y:48F2 「$C0:$429F + X」へジャンプ
;(テレポート失敗時は$C0:37A4へジャンプ)

$C0/37A4 PHB          ;Y:48F2 DBレジスタの値をスタックに積む
;(省略)
$C0/37AC JSR $B900    ;Y:48EF [$C0:B900]へジャンプ

;($C0:B900~の処理省略。ここで2つ目のマップ乱数を生成して、Xレジスタに乱数が入る)

$C0/37AF PLB          ;Y:48F2 DBレジスタにスタックから値をプル
$C0/37B0 CPX #$0042   ;Y:48F2 Xの数値と$0042(10進数の66)を減算比較(X - $0042)
$C0/37B3 BCS $0C      ;Y:48F2 キャリーフラグが立っているなら[$C0/37C1]へジャンプ
$C0/37B5 INY          ;Y:48F2 Yをインクリメント
$C0/37B6 INY          ;Y:48F3 Yをインクリメント
$C0/37B7 CPX #$0021   ;Y:48F4 Xの数値と$0021(10進数の33)を減算比較(X - $0021)
$C0/37BA BCS $05      ;Y:48F4 キャリーフラグが立っているなら[$C0/37C1]へジャンプ
$C0/37BC INY          ;Y:48F4 Yをインクリメント
$C0/37BD INY          ;Y:48F5 Yをインクリメント
$C0/37BE BRL $E3E0    ;Y:48F6 [$C0:1BA1]へジャンプ

乱数1の判定

さて、「乱数1が128以上かどうか?」という判定はどこかというと、以下の部分になる。

$C0/3793 CPX #$0080   ;Y:48EF Xの数値と$0080(10進数の128)を減算比較(X - $0080)
$C0/3796 BCS $05      ;Y:48EF キャリーフラグが立っているなら[$C0:379D]へ(テレポート成功)

※厳密には「Xの数値」というのは2バイト(16進数で4文字分)になるのだが、ここでは下2桁分、1バイトを取り出して計算をする。
16進数1バイトの最小値は$00で最大値は$FF。10進数なら最小値は0で最大値は255に当たる。

キャリーフラグについては前ページで説明したが、「2進数で計算した時、足し算で繰り上がりが起きた または 引き算で繰り下がりが起きなかった」時にキャリーフラグが立つ、という仕組みのことである。理屈は抜きにしてそういうものと思っておいていただければ問題ない(もちろん理屈がわかるに越したことはない)。

Xに入っている値が乱数1であり、「X - $0080」を計算しろという命令が「CPX #$0080」にあたる。
$0080」とは、「16進数の80」のことであり、10進数の128である。
差を取っているので、
「計算結果が0か正の数なら、乱数1は128以上なのでテレポート成功」
「計算結果が負の数なら、乱数1は128未満なのでテレポート失敗」
となり、条件分岐として使えるのである。
だが実際には、
「計算した結果キャリーフラグが立ったら[$C0:379D]へ(テレポート成功)」
という条件である。
これはどういうことか説明をする。

例として、乱数1(X)が$AB(10進数の171)だったら、テレポート成功パターンである。
計算は以下のようになる。

X - $0080 = $AB - $80

この計算を行うとキャリーフラグが立つかどうかを調べるのだが、引き算なので、「2進数で計算した時、引き算で繰り下がりが起きなかった」時にキャリーフラグが立つ。
$AB」と「$80」を2進数に変換すると、それぞれ、「10101011」「10000000」である。
頭に「$」をつけると16進数であることを表すのと同じく、頭に「%」をつけると2進法表記を意味する。
では実際に引き算をするが、2進数の引き算も、普段の10進数の引き算と考え方は同じで、それぞれの位で引き算をすれば良い。桁数の多い引き算を筆算でする(縦に数字を並べて上から下を引く)場合と同じようなイメージである。

$AB - $80 = %10101011 - %10000000 = %00101011

2進数だと8桁になっているが、たとえば一番下の1桁目(20の位)や2桁目(21の位)は「1 - 0 = 1」だし、3桁目(22の位)は「0 - 0 = 0」ととても単純である。
8桁目(27の位)は「1 - 1 = 0」。
つまり、どの位も繰り下がりが起きていない。
引き算で繰り下がりが起きなかったので、キャリーフラグが立つ。

逆に、%10000000を引く時に繰り下がりが起こるのは、8桁目が0の時の時、つまりXが%10000000より小さい時だけである。
10進法だと128未満だと引き算で繰り下がりが起きるので、キャリーフラグが立たない。

ということで、
「キャリーフラグが立つなら、乱数1は128以上なのでテレポート成功」
「キャリーフラグが立たないなら、乱数1は128未満なのでテレポート失敗」
という処理が行われ、確率がそれぞれ1/2で分岐している。
また、乱数1が128未満の時、つまりテレポート失敗の時は、飛び先を決めるため、以下の乱数2の判定へ移行する。

乱数2の判定

乱数2の判定が行われているのは以下。

$C0/37B0 CPX #$0042   ;Y:48F2 Xの数値と$0042(10進数の66)を減算比較(X - $0042)
$C0/37B3 BCS $0C      ;Y:48F2 キャリーフラグが立っているなら[$C0/37C1]へジャンプ

ここでXと16進数0042(10進数の66)の数値の比較をしている。
この時にXに入っているのは乱数1ではなく乱数2である。
上ではかなり端折ったが、乱数1の判定から乱数2の判定までにも色々な計算や値の読み込みが行われているので、ここでは既にXに乱数2が収納されている。
CPX #$0042の結果、キャリーフラグが立ったら乱数2の値は66以上ということであり、台所に飛ぶことが決定され、これより後の乱数比較はスキップし、$C0:37C1にジャンプする。
66未満なら続けて以下の処理が行われる。

$C0/37B5 INY          ;Y:48F2 Yをインクリメント
$C0/37B6 INY          ;Y:48F3 Yをインクリメント
$C0/37B7 CPX #$0021   ;Y:48F4 Xの数値と$0021(10進数の33)を減算比較(X - $0021)
$C0/37BA BCS $05      ;Y:48F4 キャリーフラグが立っているなら[$C0/37C1]へジャンプ

INYは、Yの値を+1する命令なので、2回行うことでYの値が48F2から48F4まで増加。
その後に、CPX #$0021で、Xと16進数0021(10進数の33)の数値の比較をしている。
キャリーフラグが立ったら乱数2の値は33以上である。66以上は既に先に判定済みなので、ここでキャリーフラグが立つのは33以上66未満ということ。
この時点で、風呂に飛ぶことだけは決定になり、$C0:37C1へジャンプする。風呂に飛んだ後の判定は更に後に行われる。

ここまで処理した結果、キャリーフラグが立っていないのなら、Xの値は33未満ということである。
この時点でトイレに飛ぶことが決定。

$C0/37BC INY          ;Y:48F4 Yをインクリメント
$C0/37BD INY          ;Y:48F5 Yをインクリメント
$C0/37BE BRL $E3E0    ;Y:48F6 [$C0:1BA1]へジャンプ

INY48F6までYが増えた後に、$C0:1BA1へジャンプする。

乱数3と乱数4の判定

風呂に飛ぶ場合のみ乱数3以降を判定する。
乱数2で既に風呂に飛ぶことだけは決定しているので、バトル画面から暗転し、風呂のマップやBGMの読み込みが終わったあたりで乱数3の判定が入るようだ。
乱数3以降の判定のプログラム(サブルーチン)は、乱数2における$C0/37AF~$C0/37BEを再度使っている。

最終編について

最終編については以下のようになっている。

最終編:
乱数2回判定で両方128未満なら心のダンジョン
合わせて確率1/4か?

その後2回判定して同様に128前後で分けて、テレポート先を4つから選んでいる

小小:子供&悪夢のヘルメットの前のエリアの左上
小大:同エリアの左下
大小:血の魔装の下の方
大大:大臣の左の方

この判定も$C0/3793~のサブルーチンが使われている。
ただこの時は$C0/378Fの時点でY:4272である(Yの値で何のシナリオでのテレポートが判断できるということでもありそうだ)。
心のダンジョンに飛ぶかどうか、$C0/3793~のサブルーチンを2回使い、どちらもキャリーフラグが立たない時は心のダンジョンに飛ぶ。
ただしサブルーチン1回目でキャリーフラグが立った、つまり乱数が128以上だったら、2回目の判定はいちいちやらないで「逃げる」成功パターンとなる。

心のダンジョンに飛ぶ場合の2回判定もやはり$C0/3793~のサブルーチンが使われるが、こちらは必ず2回判定で、行き先が上の通りに決まる。

また、心のダンジョンに飛んでからは、判定が入らなくなる。
JSR $B900」そのものが実行されなくなるので、心のダンジョンに飛ぶことに対応した何らかのフラグが立ち、「JSR $B900」を行わないようになるのだろう。


要するに本作では、

  • 0~255の乱数を1/2の確率で(0~127, 128~255)分岐させるサブルーチンが$C0/3793~
  • 0~98の乱数を1/3の確率で(0~32, 33~65, 66~98)分岐させるサブルーチンが$C0/37AF~

と、いうことになる。

先に述べたが、テレポート用の乱数は、「マップ乱数」生成用のサブルーチンが使われている。
「マップ乱数」を作るサブルーチンは、マップ(フィールド)に居る時、1フレームにつき1回処理されて、秒間60個ペースで乱数を作り続けているのだが、それとは別に、ランダムに起きるイベントの時にも「マップ乱数」のサブルーチンを利用して乱数を作ってイベント分岐の処理をしている。
筆者が確認した限りでは、テレポート以外に、以下に利用されている。

  • 近未来編のアイテム改造
  • たい焼き屋の手伝い時の客
  • チビッコハウスの教室のオルガンの曲
  • チビッコハウスの物干し竿下のアイテムの取得順序
  • 西部編・デロスに罠アイテムを依頼した時の行動

これらは「マップ乱数」用サブルーチンを利用して作った乱数で、イベント分岐を発生させている。
次ページにて紹介する。



このページをシェアする

上へ