ダビスタ96の乱数について
基本
ダビスタ96の乱数は、線形合同法を用いた16bit の乱数だと判明した。アドレスは0x7E1600(下位), 0x7E1601(上位) の2byte である。
現在の乱数X(n)が次の乱数X(n+1)のシード値になり、次のように計算される。
X(n+1) = ( X(n) * 771 + 129 ) % 65536
プログラム等に詳しくない方のために補足しておくと、
771倍して129を足して65536で割った余り、という意味である。この乱数の周期は32768 で、単独では65536通りの乱数値をすべて出現させることはできない。
ただし、種付け→出産時などにタイミングによって変わる変数が代入されることがあり、これにより結果的に全通りが使用される。
エミュレータ等で実行可能な方は、調教画面でこの変数に0 を入力してから
坂路またはプールの調教を実行してみると、体重変動の1回だけ乱数が呼ばれて129に変わる、
同じように変数に1を代入してから実行してみると771*1 + 129 = 900 となることが確認できるだろう。
指定範囲一様乱数の生成
上で得たのは 0-65535 までの値を取り、乱雑な順序で現れる数列であるが、一般的にこのままではゲーム等には利用しにくい。例えば rand(100) のようにすると均等確率で0-99 の値が得られるようなrand()関数を定義できると
5% の確率で処理したいときに rand(100) < 5 とシンプルに記述することができる。
そのためにダビスタ96で定義されている関数は、上で得た0x7E1600(下位), 0x7E1601(上位) に 格納されている乱数X の上下バイトを逆転した変数Yを使用した以下の形になっている。
これをこのサイトでは今後、DS96rand(100) と呼ぶことにする。
Y = (X / 256) + (256 * (X % 256)) = [0x7E1600] * 256 + [0x7E1601]
DS96rand(N) = Y % N
つまり、DS96rand(100) は Yを100で割った余り。乱数Xではなく上下入れ替えたYを使う理由は、最下位ビットが周期性を持つ 線形合同法の欠点を回避するためであると思われる。 このような処理をしておかないと、有名な例ではこんなことになる。
乱数の呼ばれる回数
外から代入が無い限り、線形合同法の乱数列は現在値=シード値 から始まる決まったパターンとなるため、このアドレスの変化前と変化後の値を調べると乱数が呼ばれた回数を特定することができる。
いくつかの場合で乱数の呼び出し回数を下に挙げる。
レース中は数千回の呼び出しがあるため、なかなか個別の意味を明かすのが困難であるが、
最も興味深い部分である産駒の能力決定では19+α回の呼び出ししかないため、比較的容易に解析することができた。
行動 | 呼出回数 | 備考 |
---|---|---|
坂路/プール 調教 | 1 | 体重減少 2kg/4kg |
単走 調教 | 150程度 | 各フレームでの移動速度? + 故障 |
併せ馬 調教 | 300程度 | パートナーの移動速度も? |
所有馬無, 10月~3月の週送り | 4 | 繁殖牝馬セリ 入替枠,牝馬ID,年齢,受胎済 |
出産(名付けの瞬間) | 代入後 19~ | 能力決定 |
BC(12頭)パドックまで | 100程度 | イレ込みやスピードのランダム変化等 |
BC(12頭)2400m レース | 6000程度 |
乱数表を用いた高速乱数
レースに出走するライバル馬の決定など、非常に多くの回数の乱数呼び出しが必要だが2の倍数の幅の乱数でよく、偏りなどが生じたとしてもそれほど致命的でもないような処理には
乱数表を用いた高速乱数が代わりに用いられることがあるようだ。
乱数表はROMの0x291895 以降の4096バイトにあり、0-255の数値がランダムに連なっている。
例えば、0-63の64通りからランダムな数値を得る場合
0x7E1600のメイン乱数の値を1増やして、4096で割ったあまりを計算する。
乱数表のその場所にあった数値(0-255)を4で割った結果が求める乱数である。
X = X + 1
XX = X % 4096
FAST96rand(64) = RandTable[XX] / 4
これをこのサイトでは今後、FAST96rand(64) と呼ぶことにする。XX = X % 4096
FAST96rand(64) = RandTable[XX] / 4
乱数シード0x7E1600を共有するため、簡易乱数の呼び出しによるシード値の増加は、 その後の通常乱数の結果にも当然影響することに注意。