Vol.7
品質プリセットを 1 つのテーブルにする — そして auto 判定の落とし穴
| 品質 | 粒子 | 追加 |
|---|---|---|
| low | ×0.5 / 500 | Bloom off |
| standard | ×1 / 2,000 | Bloom low |
| high | ×4 / 8,000 | trails |
| ultra | ×10 / 20,000 | smoke + distortion |
テーブルの中身
1export const QUALITY_CONFIGS = {2 low: { particleMultiplier: 0.5, maxParticlesPerEmitter: 500, bloomQuality: 'off' },3 standard: { particleMultiplier: 1, maxParticlesPerEmitter: 2000, bloomQuality: 'low' },4 high: { particleMultiplier: 4, maxParticlesPerEmitter: 8000, bloomQuality: 'high' }, // trails5 ultra: { particleMultiplier: 10, maxParticlesPerEmitter: 20000, bloomQuality: 'ultra' } // trails + smoke + distortion6};low と ultra の差は粒子倍率で 20 倍、1 エミッタあたりの上限で 40 倍あります。これだけ差があると、術ごとに「スマホなら少なめ」を書いていては値が揃いません。ParticleEmitter も各術の VFX もこのテーブルを読み、品質による違いは全部ここに集めています。
同じテーブルの考え方は他の重い処理にも使っています。Vol.10 の人物マスク(Selfie Segmentation)は、low / standard が 4 フレームに 1 回・128px、high が 2 フレームに 1 回・192px、ultra が 2 フレームに 1 回・256px です。術固有の Bloom(Vol.6)も、品質名をキーにした別テーブルで持っています。
selectedQuality と resolvedQuality を分ける理由
ユーザーが選ぶ値(auto を含む)と、テーブルを実際に引く値は別の状態として持っています。auto を選んだ人の設定を localStorage に保存するとき、解決後の low や high を保存してしまうと、別の端末に設定が移ったときや判定ロジックを直したときに auto に戻れなくなるからです。保存するのは selectedQuality だけで、resolvedQuality は起動時と設定変更時に毎回計算し直します。
コードを読み直して見つけた問題: Safari の auto は必ず low
1function resolveAutoQuality(): ResolvedQualityLevel {2 const cores = navigator.hardwareConcurrency ?? 4;3 const memoryGb = navigator.deviceMemory ?? 4; // ← Safari / Firefox には無い4 if (cores <= 4 || memoryGb <= 4) return 'low';5 if (cores >= 10 && memoryGb >= 8) return 'high';6 return 'standard';7}navigator.deviceMemory は Chromium 系ブラウザにしか実装されていません。Safari と Firefox では undefined になり、フォールバックの 4 が入って memoryGb <= 4 が成立するので、端末の性能に関係なく low に解決されます。つまり、既定値が auto である以上、iPhone の Safari では、最新機種でも Bloom なし・粒子 0.5 倍で術を見ていることになります。
コード内のコメントにも「Auto 判定は現時点では簡易実装」「PerformanceMonitor が計測する実 FPS を使った動的降格に置き換える(例: high で数秒間 FPS < 30 が続いたら standard に落とす)」と TODO が残っています。スペックからの推定は、取れない値があるブラウザでは推定にすらならないので、実測 FPS で降格する方式に寄せるのが筋だと考えています。
- メリット
- 起動直後に決まる。実装が単純。
- デメリット
- deviceMemory が無いブラウザでは常に low。同じスペックでも発熱状態で性能が変わることを反映できない。
- メリット
- 実際の描画負荷で決まる。ブラウザの API 差に左右されない。
- デメリット
- 数秒は重い状態を経験させる。降格・昇格がばたつかないよう履歴の扱いが要る。
採用理由: 現状は A のまま。まず「Safari で deviceMemory が無いときは low にしない」修正が先で、B はその後に入れる予定。
実装から得た知見
- 品質差が 20〜40 倍あるなら、術ごとの分岐ではなく 1 つのテーブルにする。品質の議論が「この術は」から「この行は」に変わる。
- ユーザーの選択(auto)と解決後の値は別に持ち、保存するのは選択の方だけにする。
- スペック API はブラウザによって存在しない。フォールバック値がそのまま判定に入ると、特定のブラウザだけ全員が同じ品質になる。