巡回同期(パラメータ圧縮)
VRChat のアバターは同期パラメータのビット数に上限があります。巡回同期(Async Sync)は、複数のパラメータを少数の同期パラメータへ時分割多重することで、この予算を大きく節約する仕組みです。
N 個のパラメータを 8 bit ずつ同期する代わりに、インデックス + 値チャンネルという固定の小さなセットだけを同期し、対象パラメータはそこを順番に「使い回し」ます。
仕組み
- ローカル側の巡回ステートが、スロット i に載っているパラメータの値を値チャンネルへコピーし、インデックスに i を書き込みます(Parameter Driver、localOnly)。
- VRChat が約 0.3 秒ごとに同期パラメータをリモートへ送ります。
- リモート側の AnyState デコーダー(
IsLocal == falseかつ index == i)が、チャンネルの値を本来のパラメータへ書き戻します。
対象のパラメータ自体は非同期のままです。同期されるのはインデックスと値チャンネルだけになります。
1 スロットは Bool / Int を 1 つ、または Float を Float チャンネル数(1〜8)まで同時に運びます。
作成する
- ホーム画面 の Async Sync カード → + New Async Sync
- パラメータパネルの Add メニュー → Async Sync
生成されたレイヤーはレイヤー一覧に SYNC バッジが付き、ウィザードから同じレイヤーを再生成できます。
既定のベース名
新規セットアップのベース名は DD + コントローラー GUID の先頭 6 桁になります。固定名だと、それぞれ巡回同期を持つ配布物が 1 つのアバターで出会ったときに衝突する(どちらも Async/Index を所有してしまう)ためです。
設定項目
| 項目 | 内容 |
|---|---|
| Base Name | 生成される同期パラメータの接頭辞(.../Index、.../Float など) |
| Index Encoding | Int (8 bit) — 同期 Int 1 個で 255 スロットまで / Bool × n — ceil(log2 N) 個の Bool / Auto — 同期ビットが少ないほうを自動選択(同数なら Int) |
| Float Channels | 同期 Float チャンネル数(1〜8)。1 ステップで複数の Float を同時に運べるため、スロット数が減り 1 周が速くなります。1 本増やすごとに同期 8 bit |
| Step Interval (s) | 1 スロットあたりの滞在時間。VRChat の同期周期は約 0.3 秒なので、これより短くするとリモートがスロットを取りこぼす恐れがあります |
| Fill States With The Empty Clip | 生成ステートにコントローラーの Empty クリップを割り当てます(ステップ時間はクリップ長で正規化されます)。未設定なら 1 秒の no-op クリップが作られて登録されます |
| Add Synced Params To Store | 生成されたインデックス・チャンネルのパラメータを、関連付けたパラメータストアへ synced として追加します |
同期の順序とレート
Sync Order & Rates エディタでは、対象を上から下へドラッグして巡回順そのものを指定できます。
各パラメータには ×1〜×4 の同期レートを設定できます。×N のスロットは 1 周に N 回、他のスロットが許すかぎり等間隔に配置されるため、そのパラメータだけ更新頻度を上げられます。
- 全員 ×2 のような共通因数は自動で正規化されます(全部 ×2 は全部 ×1 と同じ巡回です)。
- 他のスロットで間隔を空けきれないレートは、実現できる値まで下げられて警告が出ます。
- 各行に実際の更新間隔(〜秒ごと)が、下部に巡回全体のプレビュー(
F → B → F → I …)が表示されます。
同期リクエスト
「値が変わった瞬間にすぐ送りたい」パラメータのために、同期リクエストを使えます。
リクエスト可能に指定した対象には、ローカル専用(非同期)の Bool フラグ <Base>/Req/<対象> が作られます。このフラグが立つと、巡回はステップの境界で順番を飛ばしてそのスロットへジャンプし、1 周待たずに最大 1 ステップで値がリモートへ届きます。フラグはそのスロットの送信ドライバーが消します。
- 同期ビットを消費しません(フラグはローカル専用です)。
- リクエストは割り込みません。実行中のステップは滞在時間を使い切り、ジャンプは境界で起きます。
- 1 つの境界で処理されるのは 1 件(巡回順で最初のもの)です。取りこぼしたフラグは立ったまま次の境界を待ちます。
- 直前に送ったばかりのスロットへのリクエストは 1 ステップ後に拾われます(連続はデコーダーが判別できないためです)。
ステート側からは、インスペクターの Sync Request で「このステートにいる間はこのパラメータを順番外で同期する」と指定できます。実体は普通の VRCAvatarParameterDriver(localOnly)なので、DaerD が無い環境でもそのまま動作します。
コストのプレビュー
ウィザードは適用前に次を表示します。
- 同期コスト(圧縮後 X bit / 直接同期なら Y bit)
- Auto がどちらのインデックスを選んだか
- 追加コスト 0 で入るスロット数(現在のインデックス符号化の空き)
- ローカル Bool フラグ(同期リクエスト)の本数
- 1 周の所要時間(
N ステップ × Y 秒)
制約と注意点
この手法に内在する制約
- 値の到着は最大で 1 周ぶん遅れます。
- 途中から見に来たリモートは、1 周かけて値が埋まります。
- パペット操作でドラッグ中のパラメータは対象にしないでください。 パペットは独自に値をストリームするため、多重化のセットに混ぜると競合します。ウィザードは該当する対象を警告します。
- 同期 Float はリモート側では -1..1 の 8 bit 固定小数点(約 0.008 刻み)です。ローカルの値はフル精度のままなので、装着者以外からは少し量子化されて見えます。
インデックスと値をペアで防御する必要はありません。VRChat は同期パラメータをまとめて配信するため、リモートが読むインデックスは一緒に読む値と対応しています。
IsLocal と、巡回同期自身が生成したパラメータ(<Base>/… 名前空間)は、多重化の対象に選べません。
ウィザードはこのほか、1 周の所要時間・優先パラメータの更新間隔・圧縮しても減らない構成なども警告として表示します。
C# から設定する
C# Recipe の c.AsyncSync() からは、ウィザードと同じ設定に加えて明示スケジュールまで指定できます。
c.AsyncSync()
.Targets("Hue", "Outfit", "Tail")
.Requestable("Outfit")
.FloatChannels(2)
.Schedule("Hue", "Outfit", "Hue", "Tail");Schedule(...) はレートによる自動配置を使わず、巡回の各ステップを 1 つずつ書き下す指定です。ウィザードにはありません。
Rate(パラメータ, 回数) はコードからは ×8 まで指定できます(ウィザードの選択肢は ×4 までです)。