mcpField
S
scenario-roblox-luau

scenario-roblox-luau

github.com/scenario-labs開発1.2k54 回インストール1 日前に更新
リポジトリ

scenario-roblox-luau について

Roblox Studio で開発する人向けの Luau エンジニアリングスキルで、scenario-roblox-expert toolkit と連携します。Deferred 対応、厳密な型付け、リソース解放、測定に基づく最適化を重視し、監査、型チェック、動作検証、ベンチマークの手順を示します。

主な機能

  1. 01スケジューラーとイベント処理の監査
  2. 02両方の Luau ソルバーで型チェック
  3. 03リソース解放とリークの追跡
  4. 04プレイテストで動作を検証
  5. 05ネイティブコードと並列処理の計測
  6. 06バッファ導入前の測定

活用シーン

  1. 01Deferred イベント処理の問題を特定
  2. 02両ソルバーで厳密な型を確認
  3. 03プレイヤーやオブジェクト削除後の解放を検証
  4. 04最適化の効果を測定

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/scenario-labs/skills --skill scenario-roblox-luau

インストール後、Claude Code で/scenario-roblox-luauを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
critique.md
expert-notes.md
gui-paths.md
procedures.md
sources.md
scripts/
rbx_luau.py
scripts/agentkit/luau/
Bench.luau
BufferCodec.luau
Leak.luau
LuauAudit.luau
lune_luau_audit.luau
Parallel.luau
Probe.luau
scripts/agentkit/luau/templates/
Class.luau
ParallelWorker.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 における落とし穴)。

基本方針(専門的な知見による差分)

  1. task.spawnは並行処理ではありません。 呼び出し元が続行する前に、最初のyieldまで本体を実行します(Crusherfire)。「この処理の後」に実行するには、呼び出し順を保つキューであるtask.deferを使います。task.wait()とtask.delay(0)は次のHeartbeatで再開します。PreSimulationからは同じフレーム、Heartbeatからは次のフレームです(Crusherfire、実測値は0.07 ms対16.4 ms)。
  2. Deferredに安全なコードを書きます。 ハンドラーは次の再開ポイントで実行され、:Fire()やプロパティ設定の中では実行されません(deferredイベントのドキュメント)。ネストした発火はキュー順に実行され、ハンドラー内で接続したリスナーは同じステップ内の発火を受け取りません。また、DestroyingやCharacterRemovingのハンドラーから見ると、Parentはnilです(Crusherfire、すべて実測済み)。状態はハンドラー内で読み取り、:Once()を使い、クリーンアップに必要な情報はDestroyの前に取得します。Deferredだけで、あるゲームのフレーム時間は6~8 msから4~5 msに短縮されました(Drew Hamilton、RDC 2026)。
  3. 合法で低コストな箇所でのみ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が失敗した場合、スレッドはスケジューラーに登録されたままになりました[追加、実測済み]。
  4. 両方の型ソルバーに対応する型を付けます。 新しいモジュールは--!strictで開始します。メソッドはfunction Class.name(self: Class, ...)の形式で定義します。コロン構文のメソッドでは、両方の型ソルバーでフィールド名の誤りが見逃されます[追加、テスト済み]。コンポジションを優先してください。型付きのメタテーブル継承は、両方で誤検出を引き起こします。:: anyではなく、IsAまたはFindFirstChildOfClassで型を絞り込みます。strictスクリプトはWorkspace.UseNewLuauTypeSolverに従いますが、スクリプトからこの値を読み取れないため、旧型ソルバーと新型ソルバーの両方で確認してください。
  5. すべてのオブジェクトが自身のクリーンアップを管理します。 Destroyは何度呼び出しても安全で、自身が作成したすべての接続を切断します(Roblox Learn、OOP)。プレイヤーをキーとするテーブルはPlayerRemovingでクリアします。古いキャラクターは破棄します。Default設定では、リスポーン後のキャラクターは親から外れても接続が残っていました(実測済み)。リークとは、メモリ使用量が元に戻らないことです。使用量が増えるだけではリークではありません(Roblox Learn、memory leaks)。
  6. Parallel Luauは計算を行い、シリアルコードが書き込みます。 独立した単位ごとに1つのActorを用意し、並列フェーズの前にrequireを実行し、task.synchronize()の後に書き込みます(ドキュメント、Suphi Kaner、Stewiepfing)。各Actorは独自のLuau VMを持つため、モジュールの状態は共有されません。SharedTableの読み取り・変更・書き込みには、通常の+= 1ではなくSharedTable.incrementまたはupdateを使います。実測では、8つのActorから通常の+= 1を実行すると、更新の79%が失われました。
  7. ネイティブコードとバッファーは、測定後にのみ使います。 型付き数値コードでは--!nativeが効果を発揮します。数値ループで3.3倍、型付きVector3ループで4.9倍高速化し、Vector3パラメーターに型が付いていない場合は効果がありません(実測済み。ドキュメントでも型注釈が推奨されています)。あらゆる箇所でネイティブコードを使うと、起動コストとグローバルメモリ予算への負担が増します(Roblox Learn、native)。バッファーは、持続的な処理量が約50 KB/sを超えると効果があります(Crusherfire)。整数の書き込みは警告なしにラップアラウンドするため、範囲チェックを行ってください。
  8. 注入されたチャンクはゲーム本体ではありません。 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 なし、バッファーなし。

手順

  1. 現状を監査します。 目的:プレイテスト前にスケジューラー、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 行で正当化すること。
  2. 定型的な書き方を修正します。 目的:現行のスケジューラーとイベントの定型的な書き方にそろえます。方法:task.* のみを使用します。Stepped と RenderStepped の代わりに PreSimulation、PostSimulation、PreRender を使用します。:Once() を使用します。フレームハンドラー内では yield せず(ビジーフラグを使うか、ワーカーのループを 1 つ起動します)、yield しないメタメソッドを使用します(事前計算するか、yield する可能性のある明示的な get を使用します)。ゲート:監査に問題がなく、ステップ 5 の Deferred でも動作が変わらないこと。
  3. 型を確認します。 目的:どちらのソルバーでも、編集時にエラーを検出できるようにします。方法:--!strict、Fields 型と typeof(setmetatable({} :: Fields, Class))、ドット記法で型付けした self、コンポジション、table.create(n, value) を使用します。その後、rbx_luau.typecheck(paths, sourcemap=rbx_luau.sourcemap(project)) を実行します。ゲート:old と new のどちらもエラーが 0 件であること。
  4. 所有権とクリーンアップを確認します。 目的:所有者より長く存続するものがないようにします。方法:クラスのテンプレート(Destroying に接続された Destroy)、PlayerRemoving のクリーンアップ、キャラクターの破棄を使用します。Server のプレイテストで、Leak.retained(make, release, n) と Leak.sample(cycle, 20)、さらに Leak.slope を使って検証します。ゲート:解放後の alive が 0 であること。20 サイクル以上でヒープ増加量が横ばいであること。Destroy 後に接続が 0 件であること。
  5. 順序に依存する動作を調べます。 目的:思い込みではなく、この place での事実を確認します。方法:1 回のプレイテストで rbx_luau.run(..., dm="Server") と dm="Client" を使い、Probe.scheduler()、Probe.delayTiming()、Probe.signals() を実行します(手順 P3)。ゲート:結果を記録し、プローブによって Deferred で変わると判明した順序に依存するコードがないこと。
  6. 数値を根拠に最適化します。 目的:測定で効果が確認できた変更だけを残します。方法:Server データモデルで Bench.run({plain=..., native=...}, 7) を実行します(ラウンドを交互に行い、ウォームアップ後に中央値を算出)。1 つの Actor と N 個の Actor、およびワークモジュールで Parallel.workers を使います。BufferCodec のサイズとテーブルのペイロードを比較します。ゲート:ネイティブ比が 1.2 以上[追加されたしきい値]、並列処理の高速化率が 1.5 以上[追加されたしきい値]、バッファーが帯域幅のゲートを超えること。それ以外は元に戻します。
  7. 根拠を添えて引き継ぎます。 目的:レビュー担当者が再実行しなくても結果を信頼できるようにします。方法:ビルド、データモデル、日付を添えて、型チェックの出力、監査 JSON、プローブとベンチマークの結果を添付します。変更が目に見える場合は、rbx_review でプレイテストを記録します。ゲート:引き継ぎに記載するすべての主張に、数値またはテスト名があること。

数値

値測定値または記載値比較対象
task.delay(0) の再開PreSimulation から 0.07 ms、Heartbeat から 16.42 msStudio 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 の通常の += 1160,000 回の更新のうち 127,107 回が失われるSharedTable.increment では損失 0
Buffer のゲート継続的な受信速度で約 50 KB/sCrusherfire
リモート経由の数値9 bytes。Color3 は 13、パック時は 5Crusherfire
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" のみです。

参考資料

コメント