凄ノ王伝説 (PCE) 攻略/解析

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 が定数の場合、ビットマスクなどを用いることもある)。