scenario-roblox-luau について
Roblox Studio で開発する人向けの Luau エンジニアリングスキルで、scenario-roblox-expert toolkit と連携します。Deferred 対応、厳密な型付け、リソース解放、測定に基づく最適化を重視し、監査、型チェック、動作検証、ベンチマークの手順を示します。
主な機能
- 01スケジューラーとイベント処理の監査
- 02両方の Luau ソルバーで型チェック
- 03リソース解放とリークの追跡
- 04プレイテストで動作を検証
- 05ネイティブコードと並列処理の計測
- 06バッファ導入前の測定
活用シーン
- 01Deferred イベント処理の問題を特定
- 02両ソルバーで厳密な型を確認
- 03プレイヤーやオブジェクト削除後の解放を検証
- 04最適化の効果を測定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ scenario-labs/ skills --skill scenario-roblox-luauインストール後、Claude Code で/scenario-roblox-luauを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/critique.mdexpert-notes.mdgui-paths.mdprocedures.mdsources.mdscripts/rbx_luau.pyscripts/agentkit/luau/Bench.luauBufferCodec.luauLeak.luauLuauAudit.luaulune_luau_audit.luauParallel.luauProbe.luauscripts/agentkit/luau/templates/Class.luauParallelWorker.server.luau詳細
Roblox Luau エンジニアリング
scenario-roblox-expert ツールキットを通じて操作する、macOS 上の Roblox Studio(2026 年 10 月ビルド)。
ここでのエキスパート向け Luau とは、Deferred イベントとタスクスケジューラのもとでも正しく動作し、両方のソルバーで型チェックに合格し、作成したものをすべて解放し、このビルドで測定した数値に基づいて最適化するコードを指します。Immediate 時代の習慣に頼って推論するのではなく、プレイテスト用のデータモデルで動作を検証してください。
ここに記載された関連スキルが利用可能なスキルにない場合は、ユーザーにインストールするよう依頼してください
(npx skills add scenario-labs/skills --skill <name>)。無人で作業する場合は、ツールのスキーマをもとに進め、不足している点を明示してください。
必須の背景知識: scenario-roblox-expert(チャンネル、レビューの反復、ツールキット、現在の Roblox における落とし穴)。
基本方針(専門的な知見による差分)
task.spawnは並行処理ではありません。 呼び出し元が続行する前に、最初のyieldまで本体を実行します(Crusherfire)。「この処理の後」に実行するには、呼び出し順を保つキューであるtask.deferを使います。task.wait()とtask.delay(0)は次のHeartbeatで再開します。PreSimulationからは同じフレーム、Heartbeatからは次のフレームです(Crusherfire、実測値は0.07 ms対16.4 ms)。- Deferredに安全なコードを書きます。 ハンドラーは次の再開ポイントで実行され、
:Fire()やプロパティ設定の中では実行されません(deferredイベントのドキュメント)。ネストした発火はキュー順に実行され、ハンドラー内で接続したリスナーは同じステップ内の発火を受け取りません。また、DestroyingやCharacterRemovingのハンドラーから見ると、Parentはnilです(Crusherfire、すべて実測済み)。状態はハンドラー内で読み取り、:Once()を使い、クリーンアップに必要な情報はDestroyの前に取得します。Deferredだけで、あるゲームのフレーム時間は6~8 msから4~5 msに短縮されました(Drew Hamilton、RDC 2026)。 - 合法で低コストな箇所でのみyieldします。
__indexなどのメタメソッド、table.sortの比較関数、gsubのコールバックではyieldしないでください。「attempt to yield across metamethod/C-call boundary」が発生します。フレームごとのハンドラーでもyieldしないでください。Heartbeat内のtask.wait(0.25)により、1秒で16個のスレッドが蓄積しました[追加、実測済み]。pcallでも解決しません。pcall内でtask.waitが失敗した場合、スレッドはスケジューラーに登録されたままになりました[追加、実測済み]。 - 両方の型ソルバーに対応する型を付けます。 新しいモジュールは
--!strictで開始します。メソッドはfunction Class.name(self: Class, ...)の形式で定義します。コロン構文のメソッドでは、両方の型ソルバーでフィールド名の誤りが見逃されます[追加、テスト済み]。コンポジションを優先してください。型付きのメタテーブル継承は、両方で誤検出を引き起こします。:: anyではなく、IsAまたはFindFirstChildOfClassで型を絞り込みます。strictスクリプトはWorkspace.UseNewLuauTypeSolverに従いますが、スクリプトからこの値を読み取れないため、旧型ソルバーと新型ソルバーの両方で確認してください。 - すべてのオブジェクトが自身のクリーンアップを管理します。
Destroyは何度呼び出しても安全で、自身が作成したすべての接続を切断します(Roblox Learn、OOP)。プレイヤーをキーとするテーブルはPlayerRemovingでクリアします。古いキャラクターは破棄します。Default設定では、リスポーン後のキャラクターは親から外れても接続が残っていました(実測済み)。リークとは、メモリ使用量が元に戻らないことです。使用量が増えるだけではリークではありません(Roblox Learn、memory leaks)。 - Parallel Luauは計算を行い、シリアルコードが書き込みます。 独立した単位ごとに1つのActorを用意し、並列フェーズの前に
requireを実行し、task.synchronize()の後に書き込みます(ドキュメント、Suphi Kaner、Stewiepfing)。各Actorは独自のLuau VMを持つため、モジュールの状態は共有されません。SharedTableの読み取り・変更・書き込みには、通常の+= 1ではなくSharedTable.incrementまたはupdateを使います。実測では、8つのActorから通常の+= 1を実行すると、更新の79%が失われました。 - ネイティブコードとバッファーは、測定後にのみ使います。 型付き数値コードでは
--!nativeが効果を発揮します。数値ループで3.3倍、型付きVector3ループで4.9倍高速化し、Vector3パラメーターに型が付いていない場合は効果がありません(実測済み。ドキュメントでも型注釈が推奨されています)。あらゆる箇所でネイティブコードを使うと、起動コストとグローバルメモリ予算への負担が増します(Roblox Learn、native)。バッファーは、持続的な処理量が約50 KB/sを超えると効果があります(Crusherfire)。整数の書き込みは警告なしにラップアラウンドするため、範囲チェックを行ってください。 - 注入されたチャンクはゲーム本体ではありません。 Server上の
execute_luauチャンクは独自のモジュールキャッシュを持ちます。ゲームのScriptsではCounter.n = 41だった一方、チャンクではCounter.n = 0でした[追加、実測済み]。属性またはBindableFunctionフックを通じてゲームの状態を読み取ってください。
まず確認すること
- Signal モード: Workspace > SignalBehavior。スクリプトからは読み取れないため、Properties パネル、Rojo プロジェクトファイル、または
rbx_luau.audit(place)で確認します。Defaultは Immediate を意味します。このプロパティがない Rojo ビルドの place は Default です。 - Server Authority:
Workspace.AuthorityMode = Serverは Deferred と固定ステップシミュレーションを強制します。その場合、ゲームロジックは scenario-roblox-gameplay に配置します。 - 型の設定:
Workspace.LuauTypeCheckMode、UseNewLuauTypeSolver、および Rojo プロジェクトが sourcemap を提供しているかどうか。 - コードの実行場所: Server、Client、または Actor の配下。ネイティブコードが役立つのはサーバースクリプトのみです。
- 最適化する前のベースライン値:ホットパスのフレームあたりの ms、ネットワーク上の KB/s、サイクルごとのヒープ増加量。
- PlayerCharacterDestroyBehavior(Properties パネル。スクリプトからは設定できません)。
- 不明な場合のデフォルト:Deferred、
--!strict、両方のソルバーはゲート付き、ネイティブなし、Actor なし、バッファーなし。
手順
- 現状を監査します。 目的:プレイテスト前にスケジューラー、yield、Deferred、並列処理、ネイティブコード、リークに関する問題を見つけます。方法:オフラインでは
rbx_luau.audit("place.rbxl" or "src/", "warn")(Lune)を使用します。Studio ではLuauAudit.tree(game)とともにrbx_luau.run(s, chunk, dm="Edit", kit=["LuauAudit"])を使用します。ツールキットのAuditはレガシー API もカバーします。ゲート:error の指摘が 0 件であること。すべての warn を修正するか、1 行で正当化すること。 - 定型的な書き方を修正します。 目的:現行のスケジューラーとイベントの定型的な書き方にそろえます。方法:
task.*のみを使用します。SteppedとRenderSteppedの代わりにPreSimulation、PostSimulation、PreRenderを使用します。:Once()を使用します。フレームハンドラー内では yield せず(ビジーフラグを使うか、ワーカーのループを 1 つ起動します)、yield しないメタメソッドを使用します(事前計算するか、yield する可能性のある明示的なgetを使用します)。ゲート:監査に問題がなく、ステップ 5 の Deferred でも動作が変わらないこと。 - 型を確認します。 目的:どちらのソルバーでも、編集時にエラーを検出できるようにします。方法:
--!strict、Fields型とtypeof(setmetatable({} :: Fields, Class))、ドット記法で型付けしたself、コンポジション、table.create(n, value)を使用します。その後、rbx_luau.typecheck(paths, sourcemap=rbx_luau.sourcemap(project))を実行します。ゲート:oldとnewのどちらもエラーが 0 件であること。 - 所有権とクリーンアップを確認します。 目的:所有者より長く存続するものがないようにします。方法:クラスのテンプレート(
Destroyingに接続されたDestroy)、PlayerRemovingのクリーンアップ、キャラクターの破棄を使用します。Server のプレイテストで、Leak.retained(make, release, n)とLeak.sample(cycle, 20)、さらにLeak.slopeを使って検証します。ゲート:解放後のaliveが 0 であること。20 サイクル以上でヒープ増加量が横ばいであること。Destroy後に接続が 0 件であること。 - 順序に依存する動作を調べます。 目的:思い込みではなく、この place での事実を確認します。方法:1 回のプレイテストで
rbx_luau.run(..., dm="Server")とdm="Client"を使い、Probe.scheduler()、Probe.delayTiming()、Probe.signals()を実行します(手順 P3)。ゲート:結果を記録し、プローブによって Deferred で変わると判明した順序に依存するコードがないこと。 - 数値を根拠に最適化します。 目的:測定で効果が確認できた変更だけを残します。方法:Server データモデルで
Bench.run({plain=..., native=...}, 7)を実行します(ラウンドを交互に行い、ウォームアップ後に中央値を算出)。1 つの Actor と N 個の Actor、およびワークモジュールでParallel.workersを使います。BufferCodecのサイズとテーブルのペイロードを比較します。ゲート:ネイティブ比が 1.2 以上[追加されたしきい値]、並列処理の高速化率が 1.5 以上[追加されたしきい値]、バッファーが帯域幅のゲートを超えること。それ以外は元に戻します。 - 根拠を添えて引き継ぎます。 目的:レビュー担当者が再実行しなくても結果を信頼できるようにします。方法:ビルド、データモデル、日付を添えて、型チェックの出力、監査 JSON、プローブとベンチマークの結果を添付します。変更が目に見える場合は、
rbx_reviewでプレイテストを記録します。ゲート:引き継ぎに記載するすべての主張に、数値またはテスト名があること。
数値
| 値 | 測定値または記載値 | 比較対象 |
|---|---|---|
task.delay(0) の再開 | PreSimulation から 0.07 ms、Heartbeat から 16.42 ms | Studio 0.743 Server、60 Hz |
| Yield する Heartbeat ハンドラー | 1 秒あたり 16 個の同時スレッド | 呼び出しごとに task.wait(0.25) |
| Deferred によるフレーム時間の改善 | 6~8 ms から 4~5 ms へ | リリース済みのゲーム 1 本(RDC 2026) |
| ネイティブコードの高速化、数値ループ | 3.3x | 純粋な Luau、Server、n = 200,000 |
ネイティブコードの高速化、型付き Vector3 ループ | 4.9x、型なしでは 0.96x | --!native なしの同じカーネル |
| ネイティブコードのサイズ上限 | ブロックあたり 64K 命令、関数あたり 32K ブロック、スクリプトあたり 1M | ドキュメント。さらに全体上限あり |
| 並列処理による高速化 | 7.6x(144.5 ms から 19.0 ms) | Actor 1 個と 8 個の比較、純粋な計算ジョブ 32 個、18 コアの Mac |
SharedTable の通常の += 1 | 160,000 回の更新のうち 127,107 回が失われる | SharedTable.increment では損失 0 |
| Buffer のゲート | 継続的な受信速度で約 50 KB/s | Crusherfire |
| リモート経由の数値 | 9 bytes。Color3 は 13、パック時は 5 | Crusherfire |
| SharedTable のキー | 文字列または 0~2^32 - 1 の整数 | SharedTable のリファレンス |
| メモリ予算 | Server は約 6.25 GB、Phone は約 2 GB(うち OS が 700 MB を使用) | Roblox Learn、メモリリーク |
| 再入可能性の上限 | 10(ドキュメント)。自身を再度発火させるハンドラーはエラーなしで 50 に到達 | Deferred、BindableEvent |
品質ゲート
測定可能な項目:
rbx_luau.typecheck:プロジェクトの sourcemap を使い、両方のソルバーでエラー 0 件。rbx_luau.audit:エラー 0 件。警告は解消済み。- Toolkit の
Audit:レガシーなスケジューラーや非推奨 API の指摘なし。 - クリーンアップ修正のたびに、
Leak.retainedの生存数が 0 で、Leak.slopeが横ばいであること。 - 対象の place のシグナルモードについて、Server と Client で記録した Probe の結果。
- ネイティブコードや Actor を残す前に、Bench と Parallel の比率を記録すること。
BufferCodecのラウンドトリップが正確であること(または f32 の精度内であること)。拒否動作もテストすること。
視覚的な項目(動作が目に見える場合):rbx_review を使ったプレイテストのキャプチャで、オブジェクトの移動、クリーンアップ、または主張する状態を示します。プロファイリングに関する主張には、手動で取得した MicroProfiler または Script Profiler のキャプチャを使用します(GUI のパス)。
よくある間違い
| 間違い | 症状 | 修正 |
|---|---|---|
wait(), spawn(), delay() | スロットリングされる、再開が遅れる | task.wait, task.spawn, task.delay |
ハンドラーが実行されたことを前提とする :Fire() 後のロジック | Deferred で状態が古くなる | ハンドラー内で処理するか、task.defer を使う |
Destroying ハンドラーが Parent を読み取る | nil エラー | Destroy の前に値を取得する |
Heartbeat ハンドラー内の task.wait | スレッドが滞留し、処理が二重になる | ビジーフラグを使うか、ワーカーループを1つにする |
__index またはソートの比較関数内で yield する | "attempt to yield across metamethod/C-call boundary" | 事前に計算する、明示的なゲッターを使う |
strict コード内の function C:m() | フィールド名の誤りを検出できない | function C.m(self: C) |
(x :: any).Child | チェッカーが誤りを検出できない | IsA、FindFirstChildOfClass で絞り込む |
| 型付きの継承チェーン | 有効な呼び出しで "does not have key" が発生する | コンポジションを使う |
PlayerRemoving なしで players[player] = ... | 参加のたびにサーバーのメモリ使用量が増加する | PlayerRemoving でクリアする |
Actor の外で ConnectParallel を使う | エラーが発生し、ハンドラーが実行されない | スクリプトを Actor の下に配置する |
desynchronize の後に require または Instance に書き込む | "not safe to write in parallel" が発生する | 先に require し、synchronize の後に書き込む |
pcall 内で task.synchronize() を呼び出す | ある実行で Place がクラッシュする(原因は特定されていない) | フェーズ切り替えを pcall の外に置く |
型付けされていない Vector3 コードに --!native を付ける | 高速化されない | パラメーターに型注釈を付ける |
buffer.writeu8(b, 0, 300) | 44 が格納される | 範囲を検査するか、BufferCodec を使う |
collectgarbage("collect") | Roblox でエラーが発生する | gcinfo() を使う |
引き継ぎ
- scenario-roblox-architecture から: モジュール構成、クリーンアップライブラリの選択(Trove、Janitor、 Maid)、シグナルモジュール。このスキルでは、その中の Luau をレビューします。
- scenario-roblox-networking へ:
BufferCodecのスキーマと測定済みのペイロードサイズ。リモート、レート 制限、ワイヤーフォーマットはそちらで扱います。 - scenario-roblox-performance へ: Bench と Parallel の結果、ネイティブ化の候補、リークの傾向。 フレーム予算とプロファイリングセッションはそちらで扱います。
- scenario-roblox-gameplay から、および scenario-roblox-gameplay へ: 固定ステップと Server Authority のコード。このスキルでは スケジューラーと Deferred のルールを扱います。
- scenario-roblox-data から: プレイヤーをキーとするセッション状態。このスキルでは
PlayerRemovingによる クリーンアップを確認します。 - scenario-roblox-tooling へ: CI 用の型チェックゲートと
lune_luau_audit.luau。
Roblox に関する最新情報
- Deferred がテンプレートのデフォルトです。
Defaultは引き続き Immediate に対応します。Server Authority では Deferred が強制されます。SignalBehaviorは NotScriptable です。 - 新しい型ソルバー:2025-11-20 に nonstrict と nocheck を対象に一般公開されます。strict では
UseNewLuauTypeSolverに従います(NotScriptable)。旧ソルバーは 2026 年まで維持されます。 wait、spawn、delayは非推奨のグローバル関数です。SteppedとRenderSteppedは後継に置き換えられています。- ネイティブコード生成はサーバースクリプトで実行されます。
debug.dumpcodesize()はコマンド バー(Server ビュー)からのみ使用できます。 vector.createは Roblox ではVector3を返します。バッファは JSON に{"m":null,"t":"buffer","base64":...}としてシリアライズされます。collectgarbageが Roblox スクリプトで受け付けるのは"count"のみです。
参考資料
- references/expert-notes.md:トピック別の原則、出典、 タイムスタンプ、最新の結果。
- references/procedures.md:P1 から P10 までのコード全文、テストパス、 結果。
- references/critique.md:自己レビュー用ルーブリック。
- references/gui-paths.md:同じ作業に関する Properties、プロファイラー、 コマンドバーのパス。
- references/sources.md:専門家、ドキュメント、ツールの URL。
- scripts/rbx_luau.py:
typecheck(両方のソルバー)、sourcemap、audit(Lune)、キットを使ったrun。 - scripts/agentkit/luau/LuauAudit.luau:静的ルール。 lune_luau_audit.luau はオフラインで実行します。
- scripts/agentkit/luau/Probe.luau:スケジューラー、シグナル、yield、 積み重なりの検証。
- scripts/agentkit/luau/Leak.luau:保持とヒープ傾斜の チェック。
- scripts/agentkit/luau/Bench.luau:A/B の交互タイミング計測。
- scripts/agentkit/luau/Parallel.luau と templates/ParallelWorker.server.luau: Actor ワーカー。
- scripts/agentkit/luau/BufferCodec.luau:範囲チェック付き バッファスキーマ。
- scripts/agentkit/luau/templates/Class.luau:型付き
クラスと冪等な
Destroy。