Contents — 11 sections

VRChatのパラメータが true になったら、現実側のデバイスをONにする。

技術的にはそんなに難しくない。

OSCを受ける。

Boolを見る。

GPIOや別APIへ出す。

でも、現実側の機器を動かすなら、

「イベントを受けたら何をするか」より「壊れたときにどう止まるか」

の方を先に決めた方がいい。

RealityLinkのP0では、実際の物理出力をまだ無効にして、MockAdapterだけでSafetyCoreを先に作った。

P0では物理出力をあえて動かしていない

最初に実装したのは、

  • 型検証
  • edge detection
  • debounce
  • pulse
  • re-fire条件
  • Heartbeat監視
  • state machine
  • STOP latch
  • epoch
  • output queue

だった。

物理デバイスを動かすAdapterはMockだけ。

つまりP0の目的は、

「VRChatから現実を動かせた」

ではなく、

「現実を動かす前に、危ない状態を止められるCoreを作る」

ことだった。

状態は5つに分けた

Coreの状態は、

OFF
READY
ARMED
RUNNING
STOPPED

に分けた。

単純なEnabled Boolだけにしない。

たとえば「Enable=true」でも、Heartbeatが来ていなければARMEDへ進ませない。

STOPへ入ったら、その場でOFFへ戻してすぐ再開、ではなくラッチする。

人間側が安全状態を確認したうえで、再度明示的に有効化する。

「何か異常があったらfalseにする」だけだと、次の受信でまたtrueになり得る。

STOPは状態として残す。

80ms debounce

入力は80ms debounce。

一瞬だけ値が揺れたからといって、現実側へそのまま出さない。

VRChat側のパラメータや通信が、理想的なデジタル接点として振る舞うとは限らない。

「変わった」という事実と、「状態が確定した」を分ける。

300ms pulse

P0の出力は300ms pulse。

ONを受けたら、状態が変わるまで出しっぱなしにはしない。

時間上限を持たせる。

そのうえで、再発火にも条件を置いた。

  • falseを200ms観測
  • 前回終了から500ms経過

など。

入力がtrueに張り付いた場合に、連打し続けないためだ。

Heartbeatは「同じ値が来た」だけでは更新しない

Heartbeat timeoutは1,500ms。

ただし、同じHeartbeat値を何度受けても延命しない。

値が反転したときだけ「生きている」とみなす。

理由は、古い値や同じpacketが繰り返されるだけで接続健全と誤認したくないから。

通信が止まった。

送信側が固まった。

状態更新だけ止まった。

そういうときは安全側へ倒す。

epochで古いpulseを無効化する

非同期処理で怖いのが、過去に予約した処理。

RUNNING中にSTOPした。

でも300ms後に「pulse終了処理」が動く。

その間に別のstateへ遷移しているかもしれない。

そこでepochを持たせた。

状態遷移や停止でepochを進め、古いepochに属する遅延処理は無効扱いにする。

「昔予約した処理」が現在の状態を書き換えないための世代番号。

出力先ごとに一本のqueueへ通す

複数の非同期処理が、同じDeviceへ同時に書かないようにした。

出力先ごとに単一の送信queueを持つ。

順序を決める。

途中でSTOPされたら、その後に残っている古い仕事も扱える。

I/Oそのものより、この順序制御の方が重要だった。

54 testsと5つのmutation

RealityLink Coreには偽時計を使った54 testsを用意した。

時間依存の条件を、本当に500ms待って試すのではなく、時計を進めて検査する。

さらに5種類、意図的に安全機構を壊した。

例:

  • Heartbeatの同値でも期限を延ばす
  • epochによる古い処理破棄を外す
  • 起動時のtrueだけで発火する
  • re-fire間隔をなくす
  • Enable基準値だけでARMEDへ入れる

この5つを入れると、テストは全部検出した。

これはかなり大事だった。

Testが54個PASSしているだけでは、

その安全機構をTestが本当に見ているか

までは分からない。

安全機構を故意に壊してTestが落ちるところまで見る。

VRChat側の受信も「何でも受ける」にしない

VRCOSC module側では、登録済みのParameter受信経路だけを使う設計にした。

PC側の許可toggleは起動時にOFFへ戻す。

必要なParameterが見つからなければARMEDを拒否する。

前回実行時にONだった設定が、次回起動時にも勝手に有効、という状態を避ける。

まだ現実の機器は動かしていない

ここは重要。

P0時点ではMock outputのみ。

VRCOSCへの実ロードや、VRChatを通した実経路にも未確認項目が残っていた。

Intifaceの機能照会も未着手。

なので、

「VRChatから安全に現実機器を操作できた」

という記事ではない。

正確には、

そういうものを作るなら、物理出力より前にどんなSafetyCoreが必要かを実装・検証した

という段階。

いまの結論

VR→現実I/Oは、デモならすぐ作れる。

でも現実側を動かす瞬間から、

  • 通信が止まったら
  • trueが張り付いたら
  • packetが重複したら
  • 古い非同期処理が戻ってきたら
  • STOP後に再度入力が来たら
  • 起動時に前回状態が残っていたら

を考える必要がある。

だからRealityLinkでは、

「trueならON」

より先に、

どうやったらOFFへ倒れるか

を作った。

PicoTowaのReality Bridgeは、派手なI/Oデモより、この部分から積み上げていく。