Published on: 2022-12-23
乱数生成器は 16bit の内部状態を持ち、8bit の乱数を生成する。実装はシフトレジスタとフレームカウンタの組み合わせとなっている。
内部状態の下位 8bit はフレームカウンタでもあり、毎フレームインクリメントされる(オーバーフローしても上位 8bit には繰り上がらない)。
乱数生成時の内部状態更新処理を Rust で書くと以下のようになる:
fn rng_update(state: u16) -> u16 {
let carry = ((state >> 14) ^ state) & 1;
((state ^ (state << 8)) << 1) | carry
}
そして、生成される乱数は更新後の内部状態の上位 8bit と下位 8bit の XOR となる。
内部状態の更新式にも同様の XOR があることから、乱数の bit1-7 は更新前の内部状態の bit8-14 がそのまま使われることに注意。
更新前の内部状態から次に生成される乱数を求める式を Rust で書くと以下のようになる:
fn next_rand(state: u16) -> u8 {
let carry = ((state >> 14) ^ state) & 1;
((self.0 >> 7) ^ carry) as u8
}
このことから、乱数調整において単にフレーム待ちを行うだけでは乱数の下位 1bit しか変わらないので不十分なことが多く、大抵はいくつか前の乱数に遡っての調整を要する。
また、同一フレームで乱数が 2 回生成される場合、内部状態更新式の XOR が 2 回行われるため、内部状態の bit10-15 はフレームカウンタの値によらず一定になる。通常攻撃で敵を倒せなかった場合などはこのケースに該当する。
これらの事情から、本作における乱数調整は難易度が高く、TASでもフレームロスを抑えながら調整するのは難しい場面がある。
rand()
は生成された乱数そのものである。
rand(0..=n)
は基本的には (n+1) * rand() / 256
で生成される(n
が定数の場合、ビットマスクなどを用いることもある)。