コガネシティ孵化ロードの蒼髪お姉さんについてです。以前の検証(リンク)で「プレーヤーがいる方向に近付く習性がある」と意気揚々に発表したところでした。が、これ違いました。ごめんなさい。集計結果に偽りはないのですが、近付くように見えた理由を、内部仕様*1を拝見して金銀のNPCについて勉強させていただきました。

お姉さんの生データ
コガネシティのデパート左でポケギアについて語る女性NPC。通称、蒼髪お姉さん。まず彼女の詳細データです。
| 外見 | エリートトレーナー |
|---|---|
| 色 | デフォルト(青) |
| 住所 | コガネデパートの左 |
| 移動タイプ | 徘徊 |
| 時間帯 | 昼夜問わず |
| 移動範囲 | 上下2マス・左右1マス |
| 話しかけた時 | 動く |
| 視界に入った時 | 近寄ってこない |
金銀のNPCには個別にこれらデータが割り当てられています。例えば、コガネシティの少し北側にいるエリートトレーナーは同じ外見ですが色指定が異なり「色:グリーン(翠髪)」が割り当てられています。さて、今回は「移動タイプ:徘徊」について調べます。
移動タイプ:徘徊
移動タイプには「静止」「上下往復」「左右往復」「旋回」などが分けられていますが、徘徊タイプはランダムに動くNPCです。実装を見ると、 単純にランダムに動かずいくつかの段階を踏んでいることがわかります。
①やる気抽選
NPCは毎フレーム移動を考えてはいません。一定のタイミングで移動しようという処理が入ります。具体的には、下記のようになっています。
call Random
cp $20 ;
jr nc, .done ;
-
Randomにより0〜255の乱数を取得 -
$20(10進数で32)未満なら動くフェーズに入る -
それ以外は何もしない
この時点で、NPCは約32/256≒12.5%の確率でしか動こうとしないということです。今回はこれを「やるき抽選」と呼ぶことにします。この段階では、向きは変わらず、失敗すると見た目も座標も据え置き、この後には停止時間も与えられます。
②向き抽選
やる気抽選を通過したNPCは、次に向くべき方向を決めます。 具体的には、下記のようになっています。
call Random
and %00000011 ;
ld [OBJECT_DIRECTION], a
-
乱数の下位2bitを使い、0〜3を生成
-
乱数により上下左右のいずれかを選ぶ
-
向き情報をメモリに書き込み
この時点で動いていませんがメモリ上の向きは書き換わるという設計です。これが「向き抽選」です。
③物理演算
向きが決まると、その向きが1マス進むことができるかどうかのチェック(物理演算)が行われます。
_RandomWalkContinue:
call CanObjectMoveInDirection
jr c, .bump ;
この CanObjectMoveInDirection の中では、主に以下がチェックされます。
-
マップ境界を超えていないか
-
行動許可範囲内か
-
壁・水・他オブジェクトがないか
-
画面外に出すぎていないか
失敗時には移動は不許可となり、立ち状態(停止時間)になります。そして向きを元に戻す処理は存在しません。
④グラフィック更新
全て通過してようやく左右上下のグラフィックが更新されて移動アニメーションが始まります。先の判定で失敗していると、
-
向きは更新されたまま
-
座標は動かない
となります。これがNPCの「振り向き」という現象です。
ここまでが移動タイプ「徘徊」の動くまでの流れでした。ポイントは「徘徊タイプはランダムで動く前提だが振り向きもあり得る」ということです。ところが、この話では前回の調査結果で生じていた、初期位置からの振り向きが異様に多い(約7割)ことが説明できないことに気付いてしまいました。なぜなら初期位置の左右は壁が無いためです。蒼髪お姉さんの秘密はまだもう少し続きます。

画面の外で時は動き始める
ではなんで蒼髪お姉さんは初期位置で異様な頻度で振り向くのか。壁も無いのに。ここでの鍵は画面に映る前からNPCの処理が始まっていることにあります。
OBJECT_ACTION_RADIUS EQU 6
- プレイヤーから縦横6マス以内に入った時点でオブジェクトとして起動
上記のような仕様になっています。画面に実際に映っている範囲はおおよそ11×10マスですが、処理範囲はその外枠2マスということです。つまり、まだ画面に映っていない段階で、徘徊処理を開始します。

問題になってくるのはここから。今回はプレーヤーが自転車で下から近付くことを前提に話を進めます。まずNPCは画面外にいる状態で方向抽選を行います。この状態で、左or右or上を引いた場合、次のマスは「画面外に出ないか」という先ほどの物理演算(CanObjectMoveInDirection)に弾かれます。が、下だけは違います。下は「画面内」に入るため移動が成功することになります。そのため、
-
左or右or上を引く⇒動けずに振り向きのまま停止時間(フリーズ)
- 下を引く⇒すぐに歩く
という違いが生じると考えられます。

いや、ちょっと待てよ。ここで違和感を覚えます。理屈上、やるき抽選に外れた場合には下向き振り向きでフリーズ状態の可能性があっても良いはずな気がします。だがしかし実測データでは一度も起きていない。蒼髪お姉さんの秘密はまだまだ続きます。
初期化で世界は動く
ここでの鍵は、演算圏突入時の初期化処理です。
StepFunction_Reset:
call EndSpriteMovement
ld [OBJECT_STEP_TYPE], STEP_TYPE_FROM_MOVEMENT
ret
この処理が発生するとNPCに対して
-
内部カウンタ(停止時間)をリセット
-
次フレームで必ず行動指示
となります。つまり蒼髪お姉さんは、動くかどうかの「やる気抽選」をすっ飛ばして強制的に「向き抽選」を行うことになります。ということで、
- やる気抽選が無く向き抽選
- 向き抽選で下を引けば画面内の下に歩く
結果、「下振り向きが0回」となると考えられます。
予想される確率と推察
本件、元々蒼髪お姉さんにどれだけ邪魔されるか?という孵化作業の効率検討から始まった調査です。これまで整理した内部仕様を前提に理論上どの程度の頻度で各挙動が起きるのかを整理します。ここで示す推測値は初期化処理直後の1回目の向き抽選と、その後のやる気抽選の1回*2を実施した値です。
| お姉さんの挙動 | 推測値 | 事由 | 前回検証結果 |
|---|---|---|---|
| 私に近付く方(下)に移動 | 27.3% |
初期化処理(下)+初期化処理(下以外)×やる気抽選成功×下移動 |
25.9% |
| 私の方(下)振り向き | 0% | 画面外の下は振り向きは無い | 0% |
| 私と逆方向(上)へ移動 | 2.3% | 初期化処理(下以外)×やる気抽選成功×上移動 | 0.9% |
| 私と逆方向(上)へ振り向き | 21.9% | 初期化処理(上)×やる気抽選失敗 | 21.6% |
| 横(左)へ移動 | 2.3% | 初期化処理(下以外)×やる気抽選成功×左移動 | 2.2% |
| 横(左)振り向き | 21.9% | 初期化処理(左)×やる気抽選失敗 | 24.8% |
| 横(右)へ移動 【衝突】 |
2.3% | 初期化処理(下以外)×やる気抽選成功×右移動 | 1.3% |
| 横(右)振り向き | 21.9% | 初期化処理(右)×やる気抽選失敗 | 23.3% |
想定している結果と検証結果にはまだ乖離が起きています。ここで私と逆方向(上)へ移動と横(右)へ移動(衝突)の2つを注目します。理論値(約2.3%)に対して、実測値はいずれも低いです。ここから推測になりますが、恐らくゲームの処理タイミングが影響しているのではないでしょうか。
【仮説1】プレイヤー位置との競合
やる気抽選に成功&右方向移動決定し、NPCが移動を試みるタイミングとプレイヤーが座標を更新するタイミングが合致した場合、誰もいないマスがプレイヤー衝突判定で塞がれている。
【仮説2】上の画面枠外判定
やる気抽選に成功&上方向移動決定し、NPCが移動を試みるタイミングとプレーヤーが座標を更新するタイミングが合致した場合、本来画面境界ギリギリとなる移動先のマスが画面外判定で移動できない。
この2つの仮設が正しいとすると、上方向と右方向の移動成功率の理論値は低下しそうです。
まとめ
今回は以前調査したコガネシティの蒼髪お姉さんについての追加の研究でした。お姉さんは、
- 別に私に興味は無いし認識はしていない
- 画面外で無茶な仕事を強いられている
ということ分かりました。まだまだ最終結果には至っておらずフレーム処理の勉強も必要そうです。蒼髪お姉さんには想像以上に秘密が隠されています。そして不本意な労働環境に置かれているのかもしれません。お姉さんを救いたい。