2013/03/31

Critterの一生とPapyrusのログ(後編)

お待たせしました。Critterのお話の後編です。
さて、前回の記事では、スカイリムのいたるところにいる、ありふれた存在でありながら、セラーナちゃんなみに凶悪なスクリプトを背負って生きている「Critter」のしくみについて、書かせていただきました。
今回はそのCritterが、我が家のレイクビュー邸の周りで何やら怪しげな動きをしているようなので、いったい何が起きているのか、Papyrusのログを調査してみたいと思います。
Critterの物の怪に憑りつかれた?マイホーム……レイクビュー邸。
ちなみに前回、我が家の周辺でCritter関連のエラーが多発するようになったのは、ソルスセイム島から帰宅して以来、と書いたのですが、改めて確認してみたところ、出発前のセーブデータにも、すでに同種のエラーが出ておりました。
どうやらソルスセイム島から帰宅してCTDした折に、たまたまログを見て発見しただけで、だいぶ前からうちのデータはCritter病にやられていたようです。

…というかですね……結論から先に言ってしまいますと、我が家のCritterエラーの一つはCritterのいるところではどこでも、それこそオープニングのヘルゲンすら経由していない、まっさらなニューゲームの状態でも起きる現象だったということが分かりました。
つまり、ある意味このゲームの「仕様」みたいなもので……ログに膨大なエラーが出るのが不安なのであれば、故意に何度も「あること」を行ったりしないように気をつけながら、プレイするしかない状態です。
まあ、気をつけるといっても、気をつけようがない気もするんですけど……しかし今まではそんなこと意識しなくても、滅多にCTDなんて起きなかったのですからね。普通に遊んでる分にはきっと大丈夫なんでしょう。(と思いたい)

というわけで、今回おばちゃんが取っ組み合ったCritterのエラーは、Critterが生息する場所では誰でも気づかずに起こしてるんじゃないかと思われるエラーなので、少なくとも今のところはまだCTDを起こすような深刻なタイプのエラーではありません。
ですので、そういった異常事態の起こっている特殊なケースについては、おばちゃんはよくわかりません。
そこのところをどうかご理解の上、あくまで事例の一つとしてお読み下さい。


さて、我が家の周囲で起こるCritterのエラーの詳細なんですが、こんな感じ(パターンA)とこんな感じ(パターンB)のエラーが繰り返し発生しております。
■パターンA
[00/00/2013-00:00:00AM]error: Cannot check if the reference has a named node because it has no 3D
stack:
 [ (FF000FB9)].ObjectReference.HasNode() - "<native>" Line ?
 [ (FF001A6E)].critterMoth.GoToNewPlant() - "critterMoth.psc" Line 295
 [ (FF001A6E)].critterMoth.OnUpdate() - "critterMoth.psc" Line 147
■パターンB
[00/00/2013-00:00:00AM]error: Unable to call Delete - no native object bound to the script object, or object is of incorrect type
stack:
 [None].ObjectReference.Delete() - "<native>" Line ?
 [ (FF0011A1)].critterFish.disableAndDelete() - "Critter.psc" Line 295
 [ (FF0011A1)].critterFish.OnCellDetach() - "critterFish.psc" Line 292
ちなみにパターンAのエラーでは「critterMoth」という名称が見られるものばかりなんですが、パターンBは「critterFish」だけでなく「critterMoth」や「critterdragonfly」などの名称も見られます。
……ま、こんなエラー行だけでは、蝶や魚やトンボが何かエラー出してるなあ、というくらいのことしかわかりませんね。
ですので、このエラー吐いてる奴らが、いったいどこのSpawnerから生まれ、どの処理の過程でつまずいてるのか、スクリプト内に細かくデバッグ用のコメントを挿入して、徹底的に出所を洗ってやりたいと思います。
さあて……キミたちの親の顔をじっくり見てやんよ。

↓こんな感じで各処理ごとにコメントを吐き出させて、Critterの足跡を追ってみます。
[00/00/2013-01:18:49AM] ◎[critterspawn < (000E9090)>]OnLoad開始
[00/00/2013-01:18:49AM] ◎[critterspawn < (000E9090)>]ShouldSpawn開始
[00/00/2013-01:18:50AM] ◎[critterspawn < (000E9090)>]OnLoad…プレイヤー圏外のため待機します
[00/00/2013-01:18:50AM] ◎[critterSpawn01 < (00047992)>]OnLoad開始
[00/00/2013-01:18:50AM] ◎[critterSpawn01 < (00047992)>]ShouldSpawn開始
[00/00/2013-01:18:50AM] ◎[critterspawn < (00047992)>]OnLoad開始
[00/00/2013-01:18:50AM] ◎[critterspawn < (00047992)>]ShouldSpawn開始
[00/00/2013-01:18:50AM] ◎[critterSpawn01 < (00047992)>]OnLoad…プレイヤー圏外のため待機します
[00/00/2013-01:18:50AM] ◎[critterspawn < (00047992)>]OnLoad…プレイヤー圏外のため待機します
[00/00/2013-01:18:50AM] ◎[critterspawn < (03012A55)>]OnLoad開始
[00/00/2013-01:18:50AM] ◎[critterspawn < (03012A55)>]ShouldSpawn開始
[00/00/2013-01:18:50AM] ◎[critterspawn < (000479EF)>]OnLoad開始
[00/00/2013-01:18:50AM] ◎[critterspawn < (000479EF)>]ShouldSpawn開始
[00/00/2013-01:18:50AM] ◎[critterSpawn01 < (000479EF)>]OnLoad開始
[00/00/2013-01:18:50AM] ◎[critterSpawn01 < (000479EF)>]ShouldSpawn開始
[00/00/2013-01:18:50AM] ◎[critterspawn < (03012A55)>]IsActiveTime開始
[00/00/2013-01:18:50AM] ◎[critterspawn < (000479EF)>]IsActiveTime開始
[00/00/2013-01:18:50AM] ◎[critterspawn < (000479EF)>]OnLoad…プレイヤー圏外のため待機します
[00/00/2013-01:18:50AM] ◎[critterSpawn01 < (000479EF)>]IsActiveTime開始
[00/00/2013-01:18:50AM] ◎[critterspawn < (03012A55)>]OnLoad…Critter生成開始!
[00/00/2013-01:18:50AM] ◎[critterspawn < (03012A55)>]SpawnInitialCritterBatch開始
[00/00/2013-01:18:50AM] ◎[critterspawn < (03012A55)>]SpawnCritter開始
[00/00/2013-01:18:50AM] ◎[critterspawn < (03012A55)>]SpawnCritterAtRef開始
[00/00/2013-01:18:50AM] ◎[critterSpawn01 < (000479EF)>]OnLoad…Critter生成開始!
[00/00/2013-01:18:50AM] ◎[critterSpawn01 < (000479EF)>]SpawnInitialCritterBatch開始
[00/00/2013-01:18:50AM] ◎[critterSpawn01 < (000479EF)>]SpawnCritter開始
[00/00/2013-01:18:50AM] ◎[critterSpawn01 < (000479EF)>]SpawnCritterAtRef開始
[00/00/2013-01:18:50AM] ◎[critterspawn < (03012A55)>]Critter[Firefly < (FF001A8C)>]を生成しました
(※以下、記事中のPapyrusのログはすべて、見やすいように抜粋して編集しています)

さて、検証した状況についてなんですが、まずソルスセイム島から直接コンソールコマンドを使ってレイクビュー邸の家の中に移動し、そこからレイクビュー邸の外(エクステリア)に出て、また家の中に入って、また外に出て…という行動を何回か繰り返したシチュエーションでデータを取っています。
なぜそんな状況で検証したのかといえば、それが最初にCTDを起こした状況だったからです。……もっともCTDを起こしたのはその最初の一回のみだったんですけどね。
その後はどんなに無茶をしても全然落ちなくなってしまったので、あえてその状況を再現する必要もないかな、と思ったのですが、まあ、せっかくなのでCTD当時と似たような状況で検証してみました。
ちなみにレイクビュー邸の家の中から外に出ると、おばちゃんの環境では必ずロード画面が発生します。(外から家の中に入る際は発生しません)
ですから家の中から外に出ると、外の世界にあるものは「OnLoad」されるわけですね。
また、家の外に出ると、初回は必ず山賊さんたちがお帰りなさいアタックをしにきます。
養子たちもこづかいをせびりにきたり、いらないものを押し付けてきたり、テスト中のパパを何かと妨害しにやってきます。
二人とも立派に空気の読めないノルドとして、日々成長しているようでとても嬉しいです。

さて、そんなテストプレイ時のログをチェックしてみたわけなんですが、まずプレイヤーがレイクビュー邸の外に出た時点で、最大25個のCritterのSpawnerが一斉に「OnLoad」イベントを開始していることを発見しました。
えっ、レイクビュー邸の周囲って、そんなにCritterっていたっけ!?とビックリしましたね。
レイクビュー邸の付近にいる生き物で、おばちゃんがとっさに思い出せるのは、パインウォッチ方面のカニさんのご夫婦が棲んでる水たまりのトンボと小魚くらいです。
あとは、家畜の囲いの側にも蝶々が飛んでたっけなあ……
そういえばレイクビュー邸の上空には鷹が二羽飛んでますが、あれは別系統のスクリプトで動いている生き物なので、今回の調査からは除外しております。
(※鷹はFxfakeCritterScriptというスクリプトがついた生き物で、虫や魚などのCritterクラスを継承している連中とはちょっと種類が違うんです)
レイクビュー邸付近は風光明媚、スカイリムの中ではかなり緑豊かな自然に恵まれた地方ですが、さすがに25箇所もの生き物たちの発生ポイントには心当たりがありません。
そんなわけで、どこにそんな大量のSpawnerが配置されているのか、まずログに出ているRefIDを頼りに、CKを使って付近をしらみつぶしに捜索してみることにしました。
すると、どうやら(-5,-15)から(-1,-19)のセルの範囲にあるSpawnerが一斉にOnLoadされたらしい、ということが分かりました。
緑のがSpawnerのOnLoadイベントの反応があった区域。
「BYOHHouse1EXterior」「BYOHHouse1EXterior02」がレイクビュー邸の外回りです。
おばちゃんはてっきり「OnLoad」されるセルの単位って、CKのCell Viewウィンドウで編集する一区画分なのかと思ってたんですが、「Interior」ではないセルの場合だと、連結してる区画を複数まとめたブロック単位になるんですね。
ちなみに「Onload」が発生したSpawnerの分布図から見るに、プレイヤーのいるマスを中心に5×5の区画がまとめて「OnLoad」イベントの発動対象となっているようです。
(上図だとグリーンの背景で塗りつぶした範囲です)
レイクビュー邸の場合、邸宅は2つの区画にまたがっていますので、家の外周をぐるっと回ると縦方向には計6マス分の区画が反応することになります。

これはもしかして、Skyrim.iniの設定の「uGridsToLoad」という奴の大きさかしら……
確か遠くの方まで遠景を描写するためにSkyrim.iniを「uGridsToLoad=7」にするという技があったかと思うんですけど、通常5のところを7に増やしたら、5×5=25が一気に7×7=49となるわけで……iniを弄ってる人は、ほぼ二倍の区画分のSpawnerが一斉にOnLoadされたりするんでしょうかねえ、
まあ、Onloadされたとしても、プレイヤーが近くに行くまではCritter自体は生成されないのでそんなに負担にはならないでしょうが……しかしデフォルトの5×5でも、おばちゃんの想像をはるかに越えたスケールです。
だって、レイクビュー邸の西棟2Fのバルコニーからちょっと顔を出しただけで、リバーウッドの大守護石近くの狩人さんキャンプ付近のサケのSpawnerまで反応してるんですよ。
そんな広範囲に存在するSpawnerが全員、プレイヤーが圏内にくるのを待ちながら黙ってループをえんえん繰り返しているのかと思うと……なんというか、ガルマルさんに熱い期待のまなざしで朝から晩までじーっと見られてるみたいで、非常に落ち着かないです。


さてそんなわけで、レイクビュー邸の外に出た時点で、多数のSpawnerが「OnLoad」イベントを開始してるわけなんですが、レイクビュー邸の建物の近くに居る限りではプレイヤーの周囲4000以内に存在していて、Critter生成のGOサインが出るSpawnerは、たった二つしかなかったりします。
一つは西棟の周辺にある皮なめしの棚の近くに配置されているSpawner(000479EF)で、こちらからは昼間は蝶類、夜間はホタルやルナ・モスといった昆虫が生成されます。
もう一つは養蜂場(03012A55)で、こちらからはハチが生成されます。
レイクビュー邸のエクステリアに存在するSpawner達。

それではここで、皮なめしの棚近くの蝶のSpawner(000479EF)に焦点をあてて、ゲーム上ではどのような感じで処理が流れているのか、ログを見てみたいと思います。
「000479EF」が含まれる行をピックアップしてみると、このSpawnerが「OnLoad」されてから順調に処理を行って、無事にCritter生成まで辿りついていることが確認できます。

皮なめしの棚近くのSpawner(000479EF)関連のログの抜粋
01:18:50AM ◎[critterspawn < (000479EF)>]OnLoad開始
01:18:50AM ◎[critterspawn < (000479EF)>]ShouldSpawn開始
01:18:50AM ◎[critterSpawn01 < (000479EF)>]OnLoad開始
01:18:50AM ◎[critterSpawn01 < (000479EF)>]ShouldSpawn開始
01:18:50AM ◎[critterspawn < (000479EF)>]IsActiveTime開始
01:18:50AM ◎[critterspawn < (000479EF)>]OnLoad…条件不適合のため生成不可、待機します...
01:18:50AM ◎[critterSpawn01 < (000479EF)>]IsActiveTime開始
01:18:50AM ◎[critterSpawn01 < (000479EF)>]OnLoad中…Critter生成開始
01:18:50AM ◎[critterSpawn01 < (000479EF)>]SpawnInitialCritterBatch開始
01:18:50AM ◎[critterSpawn01 < (000479EF)>]SpawnCritter開始
01:18:50AM ◎[critterSpawn01 < (000479EF)>]SpawnCritterAtRef開始
01:18:50AM ◎[critterSpawn01 < (000479EF)>]Critter[critterMoth < (FF001AB1)>]を生成しました
01:18:50AM ◎[critterSpawn01 < (000479EF)>]SpawnCritter開始
01:18:50AM ◎[critterSpawn01 < (000479EF)>]SpawnCritterAtRef開始
01:18:50AM ◎[critterSpawn01 < (000479EF)>]Critter[critterMoth < (FF001AE3)>]を生成しました
01:18:50AM ◎[critterSpawn01 < (000479EF)>]SpawnCritter開始
01:18:50AM ◎[critterSpawn01 < (000479EF)>]SpawnCritterAtRef開始
01:18:50AM ◎[critterSpawn01 < (000479EF)>]Critter[critterMoth < (FF000FFF)>]を生成しました
ちなみに「critterspawn」や「critterSpawn01」といったRefIDの前にあるワードは、そのオブジェクトのRefに付いているスクリプトの名称を示しています。
実は「000479EF」というSpawnerには、「critterspawn」というスクリプトと、「critterSpawn01」というスクリプトの二つのスクリプトが付いていて、同時に稼動しているんですね。
これは1つのSpawnerからタイプの違うCritterをセットで生成する場合(たとえばトンボ&魚のコンビとか)に、それぞれの特性によってプロパティの設定を変える必要があるため、同じスクリプトを2つ付けてるんです。
(※「critterSpawn01」という、お尻に数字のついているタイプはダミー用のスクリプトで、「critterspawn」のスクリプトとまったく同じ働きをしています)

この皮なめし棚近くの「000479EF」のSpawnerの場合、「critterspawn」というスクリプトの方には、ホタルやルナ・モスといった夜間に活動する虫たちの設定がセットされてまして、「critterSpawn01」の方には日中用の蝶たちの設定がセットされています。
ですから上記のログでは、夜間用のCritterを扱う[critterspawn < (000479EF)>]の方からは、活動時間外で条件不適合ということになり何も生成されていないのです。
(※テストプレイは昼間に行なっています)
critterSpawn01」スクリプトの方から生成されたCritterは、それぞれ「FF001AB1」「FF001AE3」「FF000FFF」というRefIDの3匹で、「CritterMoth」というスクリプトが付いていることから察しがつくように、青い蝶か黄色い蝶のどちらかとして生を受けています。
また、CKでSpawnerの「critterSpawn01」スクリプトのプロパティを見てみると、最大何匹まで生成するかという「iMaxCritterCount」のプロパティの値が「3」になっています。
なので今回は、前回生成されたCritterの残りはいない、という判定のもと、指定通りの3匹がすべて生成されたことがわかります。


さて、この皮なめしの棚近くのSpawnerから生まれた3匹の蝶Critterたちですが、実はこいつらが「パターンA」のタイプのエラーを出していたりします。
3匹全員のログを追うのは大変なので、ここでは最初に生まれた長男(FF001AB1)に関連する行だけを抜粋して拾ってみたいと思います。
蝶Critter(FF001AB1)関連のログの抜粋
01:18:50AM ◎[critterSpawn01 < (000479EF)>]Critter[critterMoth < (FF001AB1)>]を生成しました
01:18:50AM ◎[critterMoth < (FF001AB1)>]OnInit開始
01:18:50AM ◎[critterMoth < (FF001AB1)>]CheckStateAndStart開始
01:18:50AM ◎[critterMoth < (FF001AB1)>]SetInitialSpawnerProperties開始
01:18:50AM ◎[critterMoth < (FF001AB1)>]CheckStateAndStart開始
01:18:50AM ◎[critterMoth < (FF001AB1)>]SetSpawnerProperties開始
01:18:50AM ◎[critterMoth < (FF001AB1)>]【KickOffOnStart】OnUpdate開始
01:18:50AM ○[critterMoth < (FF001AB1)>]【KickOffOnStart】OnStart開始
01:18:50AM ○[critterMoth < (FF001AB1)>]WarpToNewPlant開始
01:18:50AM ○[critterMoth < (FF001AB1)>]PickNextPlant開始
01:18:50AM ○[critterMoth < (FF001AB1)>]PickNextPlant…対象は[ObjectReference < (03006912)>]
01:18:50AM error: Cannot check if the reference has a named node because it has no 3D
stack:
 [ (03006912)].ObjectReference.HasNode() - "<native>" Line ?
 [ (FF001AB1)].critterMoth.WarpToNewPlant() - "critterMoth.psc" Line 334
 [ (FF001AB1)].critterMoth.OnStart() - "critterMoth.psc" Line 74
 [ (FF001AB1)].critterMoth.OnUpdate() - "Critter.psc" Line 276

01:18:50AM ○[critterMoth < (FF001AB1)>]WarpToNewPlant…[ObjectReference < (03006912)>]にLandingSmall03が見つかりません
01:18:50AM error: Cannot check if the reference has a named node because it has no 3D
stack:
 [ (03006912)].ObjectReference.HasNode() - "<native>" Line ?
 [ (FF001AB1)].critterMoth.WarpToNewPlant() - "critterMoth.psc" Line 340
 [ (FF001AB1)].critterMoth.OnStart() - "critterMoth.psc" Line 74
 [ (FF001AB1)].critterMoth.OnUpdate() - "Critter.psc" Line 276

01:18:50AM ○[critterMoth < (FF001AB1)>]WarpToNewPlant…[ObjectReference < (03006912)>]にLandingSmall01が見つかりません
01:18:50AM ◎[critterMoth < (FF001AB1)>]PlaceLandingMarker開始
01:18:51AM ○[critterMoth < (FF001AB1)>]【AtPlant】OnUpdate開始
字が細かいので非常に見にくいですが……とりあえずエラーが起こるまでの過程をざっと見てみましょう。
蝶Critterが生成されると、まずは「OnInit」というイベントが起こって、親Spawnerから貰った諸々のプロパティを装着する処理に入ります。
その準備が無事に完了しますと、今度は【KickOffOnStart】というStateに移行します。
これは初回のみに移行するモードで、その名の通り、蝶Critterが親元からキックオフするスタート処理ためのモードです。
この【KickOffOnStart】のStateで蝶Critterは最初の着地点(出現地点)にワープしてから、植物と植物の間を飛び回る【AtPlant】の通常モードに移行することになります。

ログを見てみますと、【KickOffOnStart】のStateで「OnUpdate」のイベント処理が始まった後、そこで「OnStart」というイベント処理が行なわれ、続いて「WarpToNewPlant」という処理が始まったことがわかります。
…と、こう書いても何がなにやらわかりませんね。
おばちゃんも書きながら、頭がぐるぐるしてきましたよ。
しかし、ここでエラーの「stack:」の部分を改めて見てみて下さい。
エラーが起こる前までの過程をふまえながら見てみると……おや?と思いませんか。
01:18:50AM error: Cannot check if the reference has a named node because it has no 3D
stack:
 [ (03006912)].ObjectReference.HasNode() - "<native>" Line ?
 [ (FF001AB1)].critterMoth.WarpToNewPlant() - "critterMoth.psc" Line 334
 [ (FF001AB1)].critterMoth.OnStart() - "critterMoth.psc" Line 74
 [ (FF001AB1)].critterMoth.OnUpdate() - "Critter.psc" Line 276

どうやら「stack:」以下のログはエラーが起こるまでの処理の道筋をさかのぼって示してくれているようです。
つまり、蝶Critter(FF001AB1)の「OnUpdate」処理が始まって、その処理中に「OnStart」という処理が起こり、さらにその処理中に「WarpToNewPlant」という処理が行なわれて、さらにその処理の最中に「03006912」というObjectReferenceの「HasNode」というネイティブな処理を行なおうとして……そこでエラーとなったのです。
何が原因でエラーが起きたのか、ということまではわかりませんが、少なくとも「Cannot check if the reference has a named node because it has no 3D(そのリファレンスには3Dが無いのでノードがあるかどうかのチェックはできません)」というエラーが出るに至るまでの経緯だけはわかるようになっているんですね。

ちなみにエラーになってしまった「HasNode」という処理についてですが、これは「stack:」の直前の行が示す通り、「WarpToNewPlant」という処理中に行なわれているものです。
「WarpToNewPlant」という処理は、蝶Critterが最初の着地点(出現地点)にワープする処理でして、まず最初に着地点となる植物をピックアップする作業に入ります。
それが上記のログの「WarpToNewPlant開始」というコメントの直後にある「PickNextPlant」という処理です。
ログを見ると、どうやら蝶Critterはその「PickNextPlant」の処理の結果、無事に (03006912)というRefIDの対象を見つけてきたことがわかります。
先頭の2ケタが「03」ということから察するに、Hearthfire関連で配置された付近の植物のどれかでしょうかね。
蝶Critterは、この対象の植物に対し、今度は「LandingSmall」という接頭語のつくノードが、3Dデータ上に存在していないか、チェックを始めます。
前回の蝶Critterの説明では、あまりにも話がややこしくなるので割愛したのですが、実は蝶Critterが着地するポイントは、対象に選ばれた植物のデータに、着地用の特別なノードがないかどうか探して、もしそのノードがあるようでしたら、その位置に優先的に着地するようになっているのです。
NifSkopeでランディングマーカーのmeshデータを覗いてみると、
「LandingSmall」や「ApproachSmall」といった接頭辞のつくノード名が確認できます。
しかもこの着地用ノードは物によっては「LandingSmall01」から「LandingSmall03」まであるようでして、蝶の着地するポイントが均等にばらけるように、最初は01番から03番までのランダムの抽選結果から選ばれたノードを、そのノードが存在しなかった場合は次は01番を探す、といった具合に二段階に分けてチェックをかけています。
つまり、「WarpToNewPlant」の最中に「HasNode」が実行される機会は二回あるわけです。
ログを見るとわかるんですが、この蝶Critterは続けざまに二回、「HasNode」という処理を行なって、それでコケてエラー吐いてます。
エラーの後のコメントを見ると、最初にコケた「HasNode」の処理では、「LandingSmall03」のノードをチェックしようとしており、次の「HasNode」では、「LandingSmall01」のノードをチェックしようとしていたことがわかります。

ちなみに長男(FF001AB1)の蝶Critterだけでなく、次男(FF001AE3)も三男(FF000FFF)もレイクビュー邸の外周りで生成された蝶Critterはことごとく、この「HasNode」の処理で失敗してコケてます。
いったい何が原因で失敗するのかはわかりませんが……問題はこの「HasNode」という処理が、「WarpToNewPlant」の処理中だけでなく、通常モードの【AtPlant】の中でもひんぱんに呼び出されている処理だということです。
なにしろ蝶が移動しようとするたびに、着地点となる植物をピックアップして、その植物のノードをチェックするという作業をするわけですから、蝶が飛び回っている間は当然、「HasNode」しまくりなわけですよ。
3匹の蝶がそうやって対象となる植物にしょっちゅう「HasNode」しようとするので、それでパターンAタイプのエラーがばんばん多発してたというわけです。
今まではエラーの部分だけしか見えていなかったので、そこで処理が詰まってリピートしてるような印象を受けてたんですが、スクリプトの処理自体は、エラーを吐きながらもそのまま流れていってたんですね。

ところで今回のこの「HasNode」のエラーに関しては、幸いなことに、たとえエラーが起きて「HasNode」の結果が得られなくても、スクリプトの処理自体が次の過程に進んでくれているのであれば、全然問題なかったりします。
(問題ない、と言っても、あくまでおばちゃんの私見ですが)
…というのも、もともと蝶CritterのCritterMothスクリプトでは、「HasNode」で着地用のノードを取得できなかった場合における処理というものがあらかじめ用意されているからです。
着地用のノードが見つからなかった場合は、対象植物の周囲のランダムなポイントに新規で着地ポイントを作成して、そちらに飛び移るようになっています。
ですからまあ、エラーでコケても蝶は何事もなく辺りを飛び回っているものと思われます。
もちろん、「HasNode」という処理でことごとくコケてしまう根源的な原因については憂慮しなくてはいけないんですけどね。


さてお次は、パターンBのエラーの方です。
こちらのエラーはパターンAのエラーと比べると、かなり状況が深刻といいますか…そもそも最初はいったい何が起こっているのか、おばちゃんにはわけがわかりませんでした。
なぜならエラーを起こしているCritterが、いつどこで生成されたものなのか、出生の記録が一切不明だったからです。
前述した通り、レイクビュー邸の家の外周から離れずにいれば、たとえどんなに多数のSpawnerが「OnLoad」されていたとしても、皮なめし棚近くのSpawnerと養蜂場以外は、決してCritterを生成することはありません。
ですから昼間の時間帯であれば、蝶とハチ以外は存在しない筈ですし、そもそもログには他のCritterが生成されたという形跡は一切残っていないのです。
それなのに、いきなりエラーの中に姿を見せるトンボや魚達……何なんでしょう、これ。

パターンBのエラーはすべて、生成された記録がログに残っていない
突然現れた謎のCritter達が引き起こしています。
09:15:36PM error: Unable to call Delete - no native object bound to the script object, or object is of incorrect type
stack:
 [None].ObjectReference.Delete() - "<native>" Line ?
 [ (FF0017BE)].critterdragonfly.disableAndDelete() - "Critter.psc" Line 315
 [ (FF0017BE)].critterdragonfly.OnCellDetach() - "critterDragonFly.psc" Line 224
09:15:36PM error: Unable to call Delete - no native object bound to the script object, or object is of incorrect type
stack:
 [None].ObjectReference.Delete() - "<native>" Line ?
 [ (FF0017C0)].critterFish.disableAndDelete() - "Critter.psc" Line 311
 [ (FF0017C0)].critterFish.OnCellDetach() - "critterFish.psc" Line 292
この、いつ生成されたのかわからない謎のCritter達は、どうやらレイクビュー邸の外から家の中に入った時……プレイヤーがセル移動して「OnCellDetach」というイベントが発生するタイミングで怪しげな活動を始めるようです。
それまではまったく動かず、ログ上では完全に沈黙していたのに、セル移動した瞬間、わらわらとボウフラのように沸いてきて活動を始めるのです。
今までいったい、どこで何してたんですかね、こいつら。

「OnCellDetach」のタイミングで突然ログに登場する謎のCritter達……計11匹!  
[00/00/2013 - 09:15:36PM] #[critterdragonfly < (FF0017BE)>]onCellDetach開始
[00/00/2013 - 09:15:36PM] #[critterdragonfly < (FF0017B0)>]onCellDetach開始
[00/00/2013 - 09:15:36PM] ○[critterMoth < (FF0016EC)>]onCellDetach開始
[00/00/2013 - 09:15:36PM] ○[critterMoth < (FF0017C1)>]onCellDetach開始
[00/00/2013 - 09:15:36PM] ○[critterMoth < (FF0017BA)>]onCellDetach開始
[00/00/2013 - 09:15:36PM] ○[critterMoth < (FF001733)>]onCellDetach開始
[00/00/2013 - 09:15:36PM] ○[critterMoth < (FF001710)>]onCellDetach開始
[00/00/2013 - 09:15:36PM] ○[critterMoth < (FF0017B7)>]onCellDetach開始
[00/00/2013 - 09:15:36PM] ●[critterFish < (FF0017AF)>]DisableAndDelete開始
[00/00/2013 - 09:15:36PM] ●[critterFish < (FF0011A1)>]DisableAndDelete開始
[00/00/2013 - 09:15:36PM] ●[critterFish < (FF0017C0)>]DisableAndDelete開始
この11匹のうち、「critterMoth」のCritterは例の皮なめし棚のSpawnerから産まれた蝶Critterなのでは?と思ったんですが、RefIDを確認してみると、直前の「OnLoad」で生成された3匹とはIDがまったく異なります。(しかも6匹もいるし!)
あるいは、この蝶Critter達は以前レイクビュー邸に訪れた時に生成されたもので、たまたま何らかの理由でレイクビュー邸の周囲に残されていたのかしら…とか思ったのですが、よくよく考えてみると、前回に生成されたCritterが今の今まで生き残っている、ということは通常ではありえないのですよね。
しかも6匹(スポーン2回分?)も残っているなんて……明らかにおかしいです。

前編の記事でもご紹介した通り、Critterというものはプレイヤーがそのセルから出ていく際に、必ず消えゆくさだめにあります。
(※後で気づいたのですが、実はハチだけはプレイヤーがセルを離れる時も消えたりしないようです。ですからハチは以前に生成されたものが残っている…という場合があります)
「プレイヤーがセルを出て行った」という状況によって「onCellDetach」というイベントが始まり、そのイベントをキャッチすると、Critterは問答無用で自分自身を削除する「DisableAndDelete」という処理を始めるからです。
プレイヤーとの距離が4000以上離れてしまった場合も、即座に削除処理に入るようになってますが、プレイヤーが自分の居るセルからいなくなった……という状況は、さらに決定的なもので、この削除命令は絶対にさからえません。
それなのに、いったいどうしてレイクビュー邸の外回りのセルには、いつ生成されたのかわからない謎のCritter達が11匹もいたのでしょう。 しかもこいつらは、レイクビュー邸の外と家の中を何度往復しても消えないのです。
そして家の出入りをするたびに、パターンBのエラーを鬼のように吐きます。
普通だったらセル移動した瞬間に消えるはずなのに……なぜいつまでも生き残っていていられるのか。
奴らが吐いているエラーは、いったい何が原因で起きているのでしょう?


ログを改めて見てみますと、この正体不明のCritter達はイレギュラーな存在のくせに、スクリプトで定められた通り「onCellDetach」イベントのタイミングで、律儀にも「DisableAndDelete」の削除処理を開始しています。
この処理では、自分自身をまず「disable」して姿を見えなくし、それから自分が生まれた時に一緒に用意した、着地用のマーカーを削除します。
エラーを見ると、このCritter達は全員、その段階でつまづいてしまっているようです。
09:15:36PM error: Unable to call Delete - no native object bound to the script object, or object is of incorrect type
stack:
 [None].ObjectReference.Delete() - "<native>" Line ?
 [ (FF0017BE)].critterdragonfly.disableAndDelete() - "Critter.psc" Line 315
 [ (FF0017BE)].critterdragonfly.OnCellDetach() - "critterDragonFly.psc" Line 224
stackを見ると、「OnCellDetach」イベントが開始されて、「disableAndDelete」という処理が始まり、そして「何か」をDelete(削除)しようとして、エラーに「削除できません」と言われてしまっていますね。
その「何か」というのは、stack行には[None]となっていますが……おそらくは着地用のマーカーのオブジェクトではないかと思われます。
着地用のマーカーは2種類(landingMarkerとdummyMarker)あるんですが、エラーを見るとやはり、各Critterに計二回ずつ、「delete」失敗のエラーが出ているんですね。
つまり、11匹全員分だと計22回のエラーがダダーっとログに吐き出されているわけです。
それが家の出入りのたびに発生するんだから……そりゃ膨大なログになるわ。

不思議なことに、よく見てみると今回はパターンAのエラーの時のように「HasNode」という処理が行われるたびにかならずコケる、というような現象ではなかったりします。
「delete」という処理は、着地用のマーカー2種を消し去った後にも、もう一度行われる機会があるんですが、その最後の一回についてはエラーは特に出ていないのです。
その最後の削除の機会とは……Critter自身の削除の機会です。
Critterは着地用のマーカーを始末した後、お母さんに「さようなら」と挨拶してから、自分自身を削除して死にます。
でもその最後の自分の「delete」については、特に何もエラーは出ていません。
ですから、「delete」という処理自体ができなくなってしまって、それでいつまでも生き残っている…というわけでは無さそうです。
ログを見る限りでは、謎のCritter11匹は全員、自分自身のdeleteで完全に死亡していますし、自分が死んだということを親Spawnerにきっちり報告にいってます。

そういえば……この段階で、この出所不明の11匹の親が誰だかわかるんですよね。
デバッグ用のコメントに親の名前を出すように細工しましたので……これでこいつらがいつ生成されたのか、という問題はともかく、どの親Spawnrから生成されたCritterなのか、ということだけは判明します。
[00:00:36PM] #[critterdragonfly < (FF0017BE)>]死亡。親は[critterspawn < (000EA3E2)>]
[00:00:36PM] #[critterdragonfly < (FF0017B0)>]死亡。親は[critterspawn < (000EA3E2)>]
[00:00:36PM] ○[critterMoth < (FF0016EC)>]死亡。親は[critterspawn01 < (000479EF)>]
[00:00:36PM] ○[critterMoth < (FF0017C1)>]死亡。親は[critterspawn01 < (000479EF)>]
[00:00:36PM] ○[critterMoth < (FF0017BA)>]死亡。親は[critterspawn01 < (000479EF)>]
[00:00:36PM] ○[critterMoth < (FF001733)>]死亡。親は[critterspawn01 < (000479EF)>]
[00:00:36PM] ○[critterMoth < (FF001710)>]死亡。親は[critterspawn01 < (000479EF)>]
[00:00:36PM] ○[critterMoth < (FF0017B7)>]死亡。親は[critterspawn01 < (000479EF)>]
[00:00:36PM] ●[critterFish < (FF0017AF)>]死亡。親は[critterspawn01 < (000EA3E2)>]
[00:00:36PM] ●[critterFish < (FF0011A1)>]死亡。親は[critterspawn01 < (000EA3E2)>]
[00:00:36PM] ●[critterFish < (FF0017C0)>]死亡。親は[critterspawn01 < (000EA3E2)>]
RefIDを見てみると、どうやら6匹の蝶(critterMoth)たちについては、親Spawnerはやはり先程の皮なめしの棚近くのSpawner(000479EF)だったようです。
まあ、とんでもなく遠くのセルのSpawnerの子とかじゃなくて良かったですが……それにしてもいったいいつ、6匹の蝶が生成されていたというのでしょう。
そもそも、この6匹が皮なめしの棚近くのSpawnerから生成されたCritterとして生き残っていたのだとしたら、最初の「OnLoad」の時に皮なめしの棚のSpawnerから新たな蝶が3匹生成されるのはおかしいです。
だって必ず前回の生き残りはいないかどうか、という判定をした後で、Critterを生成するかどうか決めてるんですから。
つまり、こいつらは親に「死んだ」と見せかけて、こっそり生きているということになります。
いったい何なの。親に隠れて何をしようっての?

一方、トンボ(critterdragonfly)や魚(critterFish)の親は、パインウォッチ近くにある水溜りに配置されたSpawner(000EA3E2)だったようです。
こちらもレイクビュー邸の近くといえば近くですが……いつ生成されたのか謎なのは蝶Critterの場合と同様です。
馬車の停留場の少し先にある水たまり。ここにはマッドクラブの夫婦が棲んでます。
ところで……この謎のCritter11匹なんですが、ゲーム上でコンソールを使って彼らのRefIDを「Prid」してみると、ちゃんと選択できるんですよね。
どうやら奴らは実体のない幽霊というわけではなく、ObjectRefrenceとして、このセル上にちゃんと「実在」している存在のようです。
ですので、いったいこの謎のCritter達がどこにいやがるのか、コンソールで「moveto」して居場所を突き止めてみることにしました。
いつ生成されたわからない謎の魚Critter(CritterFish)たち
謎の魚のCritter3匹(FF0017AF)((FF0011A1)(FF0017C0)は、親Spawnerの配置された水たまりの中にいました。
ただ、最初は姿が見えない状態になっていまして……コンソールで「enable」と打ったら、姿が見えるようになりました。
ちなみにこの3匹以外に画面に写っている魚は、プレイヤーが至近距離に近づいたために水たまりのSpawnerから新たに生成された魚Critterです。
この新規で生成された魚Critterの方は、ピチピチと跳ねる音がしそうなくらいちょこまかと泳ぎ回っているのですが、コンソールで出現した謎の魚Critter3匹は、まったく動いておりません。(静止画だと全然わかりませんが)
まあ、元気よく泳いでいるようなら、ログにその足跡が出ていなければおかしいわけで……動いていないのが当然といえば当然なんですけどね。
トンボのCritter(critterdragonfly)の方は、こんなところにいました。
まさかこんな上空にいるとは思わなかったものだから、「moveto」でいきなり飛んだら落下死してしまいました。(おのれ…)
Spawnerのある水たまりからこんなに離れたところにいるなんて……いったいトンボCritterに何が起きたというのでしょう。
遊んでいるのではありませんよ。落下中のポーズのまま「moveto」しただけです。
このはぐれトンボのCritterも、最初は外観が見えない状態なんですが、「enable」をすることで姿を見ることができるようになります。
しかもこんなに異常事態な状況であっても、きちんとオブジェクトとしての当たり判定は持っているらしく、一瞬ではありますが、落下中のウルフリック首長を支えるほどの怪力っぷりを発揮しておりました。
おそらく、こんな変なところにさえいなければ、普通にアクティベートして採取できるのではないかと思われます。
ちなみに魚Critterの方は、アクティベートしてちゃんと素材を取ることができました。
死んだみたいに動かない状態でしたが、どうやらスクリプトはまだ生きていて、アクティベート時や攻撃時の処理といったものは正常に稼動している様子です。

さて、このように「いったい何故そこに?」という場所に隠れていた謎のCritterたちなんですが、おばちゃんはそれでもまだ、最初はそこまで大変な問題が起きていると思わずに、事態を楽観視しておりました。
この謎のCritterたちがどうしてこんな異常な状態になってしまったのかはわかりませんが……「まあ、スカイリムだったら、そんなこともあるよね」と思ったりして(笑)
このゲームを長時間やっていたら、けったいなことの十や二十は平気で起きますからね。
こんなことでいちいち目くじら立てて騒いでいたら、白髪が増えてしょうがないです。
とりあえずこの目障りな11匹については、コンソールコマンドで「MarkForDelete」して強制的に削除しちゃえばさすがに復活はしないだろうし、エラーを吐いてるCritterさえ始末してしまえば問題なかろう、と思ったのでした。
だから11匹まとめて、コンソールでえいっと削除することにしたわけですよ。

ところで、「MarkForDelete」というオブジェクトを削除するコンソールコマンドですが……あれってすぐには消えてくれない仕様ですよね……
はっきりとは確認していませんが、「MarkForDelete」とコマンドを打って、オブジェクトに削除の印(RefIdの横に[D]と出ます)をつけても、何度かロード画面を挟まないと、対象はセルデータ上から完全には消えてくれなかったりします。
外観自体はロードを挟むとすぐに見えなくなるんですが、「prid」してみると、しばらくはまだRefIDが残っているんですよね。
それで、おばちゃんは完全に謎の11匹のRefIDを消すために、レイクビュー邸の外と家の中の出入りを繰り返して、ついでに家の中でセーブ&ロードして、これでもかとばかりにしつこくロード画面を挟んで……「prid」しても「そんなIDのオブジェクトは存在しません!」と言われるまで、頑張ったのです。
はあ~これでやっと駆除できたわ~、これでもう、パターンBの方のエラーは出なくなるに違いない、と思ってゲームを中断してログを見てみたら……いったいどうなっていたと思います?

確かに、コンソールで駆除した謎のCritter11匹については……「MarkForDelete」をしたことで始末できたらしく、ログには現れなくなっておりました。
しかし、今度は別のRefIDのCritterが、またパターンBタイプのエラーを吐いてるんです。
しかも家の出入りを繰り返すたびに、3匹ずつ増えていってる……!!!!
ログ上に存在するCritterのRefID一覧
※回数はレイクビュー邸の中に入ったカウントです
  種類 1回目2回目3回目
正規で生成された
Critter
FF001AFDFF001ADFFF001AE9
FF001B01FF001AEBFF001AF0
FF001B0AFF001B00FF001B07
ハチ FF001B00FF001AE0FF001AEF
FF001B03FF00AEFFF001AE3
FF001B0BFF00B03FF001B08
FF001B11FF001B0BFF001AE3
パターンBの
エラーを起こす
Critter
トンボ FF0017BE××
FF0017B0××
FF0017AF××
FF0011A1××
FF0017C0××
FF0016EC××
FF0017C1××
FF0017BA××
FF001733××
FF001710××
FF0017B7××
 FF001AFDFF001AFD
 FF001B01FF001B01
 FF001B0AFF001B0A
  FF001ADF
  FF001AEB
  FF001B00
こんな風に表にまとめて整理してみると、あんまりたいしたことないように見えますが、エラーを吐く見知らぬRefIDをログの中に新たに発見した時は、一瞬めまいがして気が遠くなりかけました。
この状況、いったいどういうことかお分かりになりますか?
これ……よく見ると、二回目、三回目のターンで新たにエラーを出すようになった蝶Critterというのは、その直前の回の「OnLoad」の時に発生した蝶Critterたちなんです。
たとえば最初の「OnLoad」で蝶Critterは正規の生成ルートから(FF001AFD)(FF001B01)(FF001B0A)の3匹が産まれてるんですが、一回目の「OnCellDetach」のタイミングでエラーを出さずに問題なく消えているにもかかわらず、次に外に出て、再び家の中に入る「OnCellDetach」の際に、こんどはエラーを出すCritterとして復活してるんです。
(ああ、こんな説明でわかるでしょうか……うまく文章にできない……)
こいつらは3回目の出入りの時の「OnCellDetach」の時にも、相変わらずエラーを出し続けてます。つまり最初の謎Critter達と同じように、家を出入りするたびにエラーを吐きまくる個体に変化してしまったのです。
せっかく始めの11匹を退治したのに……どうして前の回に生成されて、しっかり消滅したはずの蝶Critterがまた復活して、最初の11匹みたいな物の怪になってしまうんでしょう。
ほんとに……何なんでしょうか、これは。
養子がパパに嫌がらせするためにこっそり集めているコレクションか何かでしょうか。
こんなやっかいな虫たちが、気づかないうちにどんどん増殖してしまったら……レイクビュー邸はいつの日かパンクしてしまいます。

ちなみに、謎Critter達のエラーを調べるために、単に家の出入りだけを繰り返していたプレイの時には、最初の11匹から新たに別のCritterが増える気配はありませんでした。
また、「OnLoad」の際に正規のルートで生成されるCritterは蝶の他にもハチがいるわけですが、不思議なことにこいつらは一匹も復活したりしていません。
もっとも養蜂場から産まれるハチのCritterは、ログの足跡を追ってみると、どうやら生成されても付近にワープするための植物自体が見つからないようでして、しばらくすると自動的に消滅してしまっています。
せっかくスクリプトがついているのに、Critter着地用の植物やマーカーを周囲に配置するのを忘れてしまったんでしょうかね?
Hearthfireの家はこういった設定ミス(?)が実に多いですよねえ……
まあ、そんなわけで、ハチは誕生してまもなく消えてしまってるんですが、蝶だって「OnCellDetach」のタイミングで消えているのは同じといえば同じです。
どちらも「disableAndDelete」という処理が呼ばれて、エラーも出さずにちゃんと削除されています。(※delete時にエラーを出すのは物の怪になってからです)
それなのに、なぜハチは復活しなくて、蝶だけが蘇ってしまうのか。
う~ん、どういった法則で、このような現象が起こるんでしょうかねえ……


そんなわけで、いったいどういう状況下になると、生成されたCritterが消えずにデータ上に残り、パターンBのようなエラーを吐くようになってしまうのか、いろいろ試しまくって、あれこれ調べてみました。
最初はその最終的な結論に辿りつくまでの検証の経緯を、ダラダラと長文を重ねて書いていたのですが……あまりにも長すぎて、何よりおばちゃん自身がしんどくなってきたので、やめました。(というか今でも充分長いですよね。すみません)
なので、途中のデータの詳細も書かず、いきなり結論だけを書いてしまいますが、もし以下の内容をおかしいと思ったり、疑わしいと思った場合は、ぜひご自分でもテストしてみて同じ現象が再現されるかどうか、確認してみてください。
そしてその検証の結果を、ぜひおばちゃんに教えて下さい。
おばちゃんも何度か試してはいるのですが……全部勘違い、ということもありえますので、他の方の環境でもそうなるのか、切実に知りたいです。


さて、いろいろテストした結果、おばちゃんがようやく確認できたCritterの法則は、プレイヤーがCritterのいるセルから別のセルに移動する時、移動先や移動手段によってはCritterが消えずに残る場合がある…ということでした。
通常だったら「OnCellDetach」イベントを受け取ったCritterは、自分の着地マーカーと自分自身を消し去る処理に移り、終了時にはCritterのRefIDは完全に消去されます。
しかしその挙動はどうも、プレイヤーが歩いたり馬で移動したり、とにかく地続きの隣のセルへそのまま移動する場合と、特定の場所以外の建物やダンジョンや街の中などに直接入った場合、だけのようなのです。
通常のセル移動では、Critterのすべてがそこで完全に消滅します。
ちなみにこういった通常のセル移動を行うと、なんと、その時に生成されたCritter達だけでなく、パターンBのエラーを吐く悪質なCritterまでもが、一網打尽に消滅します。
これまでにさんざん、「どうして消えてくれないの!」とレイクビュー邸の家の出入りを繰り返していたわけですが、なんのことはない、レイクビュー邸からちょろっと、近くのタロス像のあたりまでお散歩にいって、タロス様に「どうかCritter病を直してください」とお願いするだけで良かったのです。
こんな簡単なことで、あれほどしつこかった害虫たちが消えるとは……まったく、やってられません。おばちゃんの睡眠時間を返せ。
移動手段や移動先によっては、Critterの処理の残骸?がセルに残ります。
では、反対にCritterが消えずに残ってしまうパターンはどんな状況なのかといいますと……それはCritterの居るセルから、ファストトラベルで移動したり、またレイクビュー邸のような特定の場所に入ったりする場合です。
えっ、ファストトラベルするだけでCritterがエラーを吐くようになるの?とお思いでしょうが……もし良かったら、どこか蝶々やトンボや魚のいっぱいいる場所に行って、その場所からファストトラベルでどこか遠くの場所に飛んでみてください。
そしてそこでセーブ&ロードしてから、再び先ほどのCritterの居場所に戻って、そのポイントでもう一度ファストトラベルすると……その移動の際には、Critter達がパターンBのエラーを吐くようになっていると思います。(ただしハチ(Firefly)を除く)

これはあくまで推測なのですが……ファストトラベルした時や特定の場所へ入ったりする場合には、移動時に移動元のセルの状態をキャッシュのようなものに保持しているような気がするのです。
特にCritterの「onCellDetach」イベントの一連の処理は、セル移動時に何かのスクリプト処理をやってる途中だった…みたいな情報として残ってしまうんじゃないかと思うんです。
で、そのCritterの処理の残骸のようなものが、残された処理の続きを行うために、削除された自分の体を復活させているんじゃないかと思うんですよね。
実は「セーブ&ロード」を挟まなくても、再びCritterの居たセルに戻って、RefIDを「prid」してみると、すでにその時点で消えた筈のCritterが存在していることが確認できたりします。
また、着地マーカーなども削除マーク[D]付きではあるのですが、完全に削除されてはおらず、RefIDなどの情報はしっかり残っています。
それを見ると、やはりファストトラベルや特定の場所への移動は、通常のセル移動の場合とは異なる仕組みのような気がするんです。
通常のセル移動を行うと、Critterや着地マーカーの情報はいっぺんでスパーンと消えてなくなりますからね。

ちなみに「特定の場所」への移動と書きましたが、実はこの「特定の場所」の条件とは何なのか、ということは今のところおばちゃんにはまったく解明できていません。
ただ、Critterの出没するめぼしい箇所で確認してみたところ、「レイクビュー邸」の家の中だけでなく「ホニングブリューハチミツ酒醸造所」や「パインウォッチ」「マルカルス」といった建物や街の中に出入りをする際にも、Critterが消えずに残る現象を発見しました。
これらの場所に共通する特徴は何か……いろいろ考えてみたのですけれど、どうもわからないのですよねえ。
もちろんこの4箇所以外にも、こういった場所は沢山あるのだろうと思います。

ちなみに、こういったCritterが消えずに残ってしまうようなロケーションであっても、Critterをすべてアクティベートして採取してから移動すれば、こういった問題は起きません。
ようするに特定の場所へ入室する時、またはファストトラベルをする際の「OnCellDetach」イベントが起こるタイミングで、Critterが残存していなければ、Critterが消えずに残る……ということはないわけです。
そういえばレイクビュー邸の養蜂場のハチは、あれは元々、着地点となる植物が無いせいですぐに消滅してしまい、「onCellDetach」イベントを迎えることなかったわけですが、よくよくハチの「FireFly」スクリプトを見てみましたら、ハチにはなぜか「onCellDetach」そのものが設置されていませんでした。
なのでハチはセル移動時に生き残っていても、復活はしなさそうです。
ただ、もしハチに異常事態が発生して、物の怪として復活してしまった場合は……非常に困ったことになるかとは思います。
「onCellDetach」イベントで削除されるというルートがなければ、通常のセル移動で消えてくれることもないわけですからね。


では、こういった中途半端に残されたCritterの情報を含んだセルデータが、「セーブ」されてしまった時のことを考えてみたいと思います。
消えずに残ってしまったCritterはおそらく、何かの処理の途中だった…という情報として、その処理のスクリプトが付いていたObjectRefarenceの情報と共にセーブデータに記録されるのではないかと思います。
このゲームの現状を維持しようという力は非常に強力なので……特にObjectReferenceやセルのデータに紐付いているスクリプトは、何が何でも最後まで責任取らせようとするはずです。
しかしCritterが用意した着地マーカーのような副産物の情報については、マーカー自体には処理途中のスクリプトがついているわけではないので、あっさり切り捨てられている可能性が高いです。
しかも着地マーカーのRefにはしっかり削除マークがついているわけですしね。
そしてこのセーブされたデータを「ロード」すると、再びCritterは蘇って処理の続きが行われる機会を待つんだと思うんですが、この時点ではもう、お供の着地マーカー達はどこにもいなくなってしまっています。
つまり「セーブ&ロード」の後からエラーが発生するのは、着地マーカー2種が消えるためじゃないかと思うのです。
Critterが着地マーカーのRefIDを覚えていたとしても、マーカーそれ自体がデータから消えてしまっているんじゃ、削除しようがないですからね。
ちなみに「セーブ&ロード」の後に、着地マーカーのRefIDが「prid」できるか調べてみたんですが、やっぱり消えてしまっておりました。

ちなみにおばちゃんはこういったパターンBのdeleteのエラーを吐き続けるCritterを180匹ほどわざと発生させてみましたが、そのせいでCTDするようなことはありませんでした。
ですから、このようなCritterが増えてしまうことを、そこまで神経質に気にする必要はないんじゃないかと思っています。
それに無駄に増えてしまっても、その辺をちょっと歩けば消えてくれるわけですしね。

とりあえず、我が家のCritter問題については、自宅である以上、何度も出入りをせざるをえない場所なので……家の外に出た時は、Critterを一匹残らず採取することで対応したいと思います。


15 件のコメント:

  1. 今回も興味深く読ませて頂きました。
    内容について自分の理解力と知識では及ばないところも多々あるのですが、おばちゃんが噛み砕いて説明してくださっているので、なんとなく分かったような気分になることができました(´ω`)

    ところで、
    Critterは着地用のマーカーを始末した後、お母さんに「さようなら」と挨拶してから、自分自身を削除して死にます。
    この一文切なすぎ(´;ω;`)
    不覚にもグッっと来てしまいましたよ。

    返信削除
    返信
    1. 切ないといえば切ないですが、母さんに「さようなら」と言っておきながら、しぶとく生き残ってるわけですからねえ……
      しかし、母さんも母さんですよね。
      3匹しか子供を産んでないのに、9匹から「僕死んだよ」と報告されても、それはおかしいとか、あんたはうちの子じゃない、とか騒いだりしないのは……いかがなものか。
      Critterが増殖していってしまう原因のひとつは、この母さんの大雑把(?)な性格にあるんじゃないかと思えてなりません。

      削除
  2. はじめて書き込みさせて頂きます
    以前より楽しく読ませて頂いてました
    CTDが起こるようになった頃からPapyrusログを読むことを覚え色々調べるうちに
    恐怖のCritterバグとして脳裏に焼き付いていたわけですが
    お蔭様でとうとう解決(の糸口?)の方向が見えたような気がします
    複雑な内容をかみ砕きまとめて頂いて本当にありがとうございました

    この仕様はもう放置するしか無い感じなのでしょうかね?
    Critterバグが出たら出たセルの地繋がりのセルまで行って戻れば正常化されるのであれば
    問題ないのかな…
    着地マーカーを保持するようなスクリプトにしてしまえば…とか考えてしまいますが
    CK使用の経験が殆どない自分には到底無理でしょうね・・・w

    記事まとめ本当にお疲れ様でしたありがとうございました

    返信削除
    返信
    1. 着地マーカーを消さずに残すようにしてしまうと、それはそれでムダなデータが増えていってしまうので……あんまり得策じゃないような気がします。
      単にエラーをなんとかして止めたい、というのであれば、着地マーカーを削除する処理をフラグで管理して、二回目以降は削除する処理は行なわない、というような回避策を設けるとか……そんな感じがいいかもしれないです。

      でも消えずに残ったCritterがこのエラーを吐いてくれるからこそ、このセルにはCritterの残骸がたまっているぞ…ということが判明するわけなので、私はこのエラーはかえって有難い「虫の知らせ」だと思っています。
      このエラーを出さないように改造するなんて……とんでもない!(笑)

      削除
  3. わかりやすい文章と解説で参考になります。
    全然検証してないやっつけのCritterバグ対策スクリプトを配ってたんですが、私がやってたのはどうも見当違いのようですw

    仮説でしかないのですが…
    ファストトラベルなど行動した時に各イベント(onCellDetach)等で行われる処理が一斉にキューを送ってそれを順に処理するんですが、あまりにも多いとスクリプトに遅延が発生します。
    一度に処理が大量に来すぎて画面上と同期が取れなくて、HasNodeを取得できない、残骸を削除しきれなくなってしまうのではないかなと思います。Papyrusちゃんが受けられる仕事量のキャパシティ越えちゃうと仕事のミスが増えまくるというような感じでしょうか。

    Payprusと違って超高速で処理できるScriptDragonで大量に処理送るとスタックエラー出してCTDします。これを止める唯一の方法が処理ごとに逐一Waitを挟んでPapyrus並に速度落とすことです。

    同じ原理ならCritterのバグもUtility.Wait()挟むと直るかもしれません。
    パターンBの方は深刻でパターンAしか効かなそうな気もしますが…

    返信削除
    返信
    1. なるほど……確かにログを見てるといろんな処理が混みあってるような印象を受けますねえ……
      ためしに「HasNode」の前に、waitの処理と、対象の植物のRefがちゃんと「Is3DLoaded」されているかどうかの判定を挟んでみたのですが、仕事をますます増やしただけだったのか、余計にエラーが増える結果になっちゃいました(笑)
      レイクビュー邸のエクステリアはただでさえ大忙しな感じなので……寂しくなりますけど、いっそのこと、CritterのSpawner自体を撤去してしまった方がいいのかもしれませんね。

      削除
  4. ぱたーんBですがマーカーが存在しなかったら生成してから削除させるって可能ですか?

    返信削除
    返信
    1. そんな……せっかく消したものをわざわざもう一度生成するなんて……
      そんなにエラーが出るの、お嫌ですかねえ……;;;
      でしたら、二つ前のコメントの返信にも書かせていただいたのですけれど、Critter.pscに着地マーカーを消したかどうかのフラグを追加して、「disableAndDelete」の処理内の、着地マーカーのdelete処理を二回目以降は回避するようにすれば、エラー自体は出なくなると思います。
      例えば……こんな感じ↓で。

      ; 着地マーカーを削除したかどうかのフラグ
      bool bDeletelandingMarker = false
      bool bDeletedummyMarker = false

      ; フラグがオフなら着地マーカーを削除する
      if !bDeletelandingMarker
       if (landingMarker != none)
        landingMarker.Delete()
        ; landingMarkerを削除後、フラグをオンにする
        bDeletelandingMarker = true
       endIf
      endIf
      if !bDeletedummyMarker
       if (dummyMarker != none)
        dummyMarker.Delete()
        ; dummyMarkerを削除後、フラグをオンにする
        bDeletedummyMarker = true
       endIf
      endIf

      しかしこんな対処法では、エラーが出ない、というだけで、Critterの処理自体は消えてくれませんので……問題解決にはならないと思うんですよねえ。
      まあ、ログは見やすくなりますけども。

      削除
  5. ヤバイ・・・面白すぎる!丁寧な解説本当にお疲れ様です
    これだけの長い文章でも一気に読めてしまいました、むしろまだまだ読みたいぐらいですw
    エラーが出たり出なかったりさっぱり謎だったのがすっきりしました
    私もレイクビュー邸では何故かエラー出まくって、他の部分では出なくて何故?と常々思っていたんですが、錬金アイテム自動採集MODを愛用しているので他のセルだと根こそぎ刈り取ってたからエラー出なかったんですね
    レイクビュー邸は近づく際には見た目を重視したくて蝶や植物残したいのでOFFにして必ず近づいていたんで納得です

    しっかし、前々から思っていたんですけどおばちゃんは常に笑いの神に愛されているような、今回のような真面目な記事でも「落下して死亡」とか必ず何か起きますねw


    返信削除
    返信
    1. こんなまとまりのない下手な長文をそんな風に仰っていただけるなんて……有難うございます。
      自分で読み返しても、意味がわからなかったりするのに……こんな支離滅裂な文章を最後まで読んでいただけて、本当に嬉しいです。

      やはりレイクビュー邸でCritterのエラー出ますか!?
      良かった……私の環境だけだったらどうしようかと思っておりました。
      なるほど、Critterのエラー対策としては、自動採集MODで根こそぎ諸悪の根源をヤっちまう、という方法があるわけですね……
      それならば、私自身が採集Modと化して、やつらを根こそぎ採取してやりたいと思います。
      ドヴァキンの通った後はぺんぺん草も生えない、と恐れられるくらいに狩りつくしてやりますよ!

      削除
  6. 初めまして。いつも楽しく拝見させていただいております。
    この度のCritterエラーの詳細な解説、ありがとうございます。
    記事を参考にしながら素人なりにスクリプトを弄ってみた所、根本的な解決になっているかは不明ですが、少なくともエラーは出難くなりました。
    (拙ブログにて恥ずかしげもなく配布しております)

    本当にありがとうございました。

    返信削除
    返信
    1. なるほど〜、自分自身が消えてるかどうかのチェックで削除エラーを回避するのですね!
      そちらの方がムダなフラグとかいらなくてスッキリしてていいですね。全然思いつきませんでした。
      私も修正版のスクリプト、入れさせていただこうかな……
      いくらエラーが大したことはない……とはいえ、しょっちゅうログを見てると確かに気になるものですね。

      削除
  7. 初めまして。詳細なデータとわかりやすく丁寧な説明文に引き込まれて、思わずコメントしています。
    これまでも、こちらのブログは何度か拝見していますが、その度に「なるほど…」と充血した眼をこすりながら感心していました。
    (まあ、全てを理解できるほど知識はないのですが)
    一先ず、某ブログで配布されているスクリプトファイルを入れてみて、今のところ大丈夫…と思っているだけだったので、その構造や根拠に触れることが出来たのはありがたいです。
    これからも更新を楽しみにしています。

    季節の変わり目、お体にお気をつけて。
    ありがとうございました。

    返信削除
    返信
    1. コメントありがとうございます。
      Critterの挙動はまだよくわからない部分が多いし、私がデータを見誤っていたり思い込みだったりする部分も沢山あると思いますので、あまり信用せず、「おや?」と思うことがあったらぜひ教えて下さいませ。
      特に、特定の場所(レイクビュー邸とか)への出入りで発生するエラーについては、どんな法則があってそれらのセルだけがエラーになってしまうのか、まったく見当もつかない状態ですので、何かお気づきの点があればお知らせくださると有難いです。

      削除
  8. こんないい文章を書いて・・
    大変勉強になりました

    ありがとうございます!

    返信削除