feat(rbxl): уважать enabled=false из Roblox-метадаты

Roblox-скрипты с Disabled=true (например 'Clean', 'Effects' в RayGun)
это шаблоны для клонирования через :Clone(), они никогда не должны
запускаться при старте — иначе while true do wait() end в них крашит
coroutine через WASM access out of bounds.

parseRobloxLuaMeta(code) парсит JSON-метадату из второй строки
packed-кода (формат '// {"roblox_class":..., "enabled":true}').
Скрипты с enabled=false идут в rbxlSkipped, не запускаются.
This commit is contained in:
min 2026-06-08 13:43:09 +03:00
parent ca92ba1988
commit 3271e53acf
3 changed files with 25 additions and 1 deletions

View File

@ -62,6 +62,11 @@ ContextActionResult, UserInputState, BorderMode, FormFactor.
аргумента раньше делал tight loop без yield → WASM stack overflow
("memory access out of bounds").
- **Уважаем `enabled: false`** в Roblox-метадате. Roblox-скрипты с
`Disabled = true` — это шаблоны для клонирования (`script.Clean:Clone()`),
не должны запускаться при старте. `parseRobloxLuaMeta()` парсит JSON
из второй строки packed-кода, при `enabled=false` скрипт идёт в `rbxlSkipped`.
### Надо ли портировать в JS-движок?
**Да, всё** — это базовый Roblox-совместимый API, который должен работать

View File

@ -19,7 +19,7 @@ import { ScriptSandbox } from './ScriptSandbox';
import { STORYS_addres } from '../../api/API';
import { PhysicsWorld } from './PhysicsWorld';
import { LabelManager } from './LabelManager';
import { handleLuaCommand, unpackRobloxLuaCode } from './rbxl-lua-integration.js';
import { handleLuaCommand, unpackRobloxLuaCode, parseRobloxLuaMeta } from './rbxl-lua-integration.js';
import { LuaSharedSandbox } from './lua/LuaSharedSandbox.js';
export class GameRuntime {
@ -128,6 +128,11 @@ export class GameRuntime {
for (const s of scripts) {
if (s && typeof s.code === 'string' && s.code.startsWith('// @roblox-lua')) {
if (!runImportedRbxl) { rbxlSkipped++; continue; }
// Уважаем поле enabled=false из Roblox-метадаты: такие скрипты
// были disabled-шаблоны (для клонирования через :Clone()), их
// запуск немедленно крашит coroutine (WASM access out of bounds).
const meta = parseRobloxLuaMeta(s.code);
if (meta && meta.enabled === false) { rbxlSkipped++; continue; }
const luaSource = unpackRobloxLuaCode(s.code);
if (luaSource && luaSource.trim()) {
luaUserBatch.push({

View File

@ -20,6 +20,20 @@ export function unpackRobloxLuaCode(code) {
return code.slice(start, closeIdx);
}
/** Парсит JSON-метадату из 2-й строки packed-кода (`// {"roblox_class":..., "enabled": true}`). */
export function parseRobloxLuaMeta(code) {
if (typeof code !== 'string') return null;
const lines = code.split('\n');
if (lines.length < 2) return null;
const metaLine = lines[1];
if (!metaLine.startsWith('// ')) return null;
try {
return JSON.parse(metaLine.slice(3));
} catch (_) {
return null;
}
}
/** Сцена → snap для shim'а (workspace:GetChildren). */
export function buildLuaSceneSnap(primitives) {
const out = { primitives: {} };