8. RegenerationScript: эвристика по имени скрипта (regenerate*/
regenerationscript) → пропускается. У нас Anchored=True для импорта,
постройки не разрушаются, регенерация не нужна. Их работа дала бы
визуальные глитчи (model:remove + Clone каждые 2 мин).
9. BattleArmor: Humanoid.MaxHealth/Health/WalkSpeed/JumpPower теперь
реактивные (Object.defineProperty). При смене .MaxHealth=N шлёт
playerSet → player.maxHp обновляется → HUD HP-бар. BattleArmor
touch'нул → Humanoid.MaxHealth=20, Health=20 → игрок видит броню.
10. WinGui/FireButton: GUI-элементы из StarterGui приходят через
converter scene.gui[] и рендерятся стандартно. Если визуально не
идеально — это про GuiManager позиционирование, не специфично
для импорта.
11. AdminConsole: no-op, скрипт-заглушка, ничего не делает.
13. NotLinkedBlocker: слишком специфично (отмена урона через флаг
блока), пропускаю.
ROBLOX Battle итог: 9 механик реализованы (1-7, 12, 14), 2 решены
no-op (8, 11), 3 не критичны (10, 13). Карта должна играться.
Реализовано из 14 механик:
1. Teams (game.Teams, Player.Team, TeamColor): scene.teams[] из конвертера,
эвристика TeamBeacon-Model → автоматически создаются 4 команды.
В shim создаются Team-инстансы при snapshot, авто-эквип игрока в первую.
2. Leaderstats UI: IntValue.Value реактивно шлёт leaderstatSet → существующий
LeaderstatsManager (define + set). HUD автоматически рисуется в правом
верхнем по родительскому Name='leaderstats'.
3. BindableFunction + RemoteFunction + Message/Hint класс. Message с
реактивным .Text и .Parent шлёт hudMessage в наш RbxlHudOverlay.
4. KillFeed UI + creator-tag tracking. RbxlHudOverlay.addKillFeed() рисует
А → [weapon] → Б в правом верхнем. Humanoid.TakeDamage при Health=0
ищет creator-ObjectValue и шлёт killFeed. Авто-respawn через 2с.
5. SpawnLocation.TeamColor → scene.team_spawns[] для будущей логики
команд-спавна.
6. Tool:Clone() / Model:Clone() / :clone(): поверхностный клон + lowercase
alias. Также :MakeJoints/:BreakJoints/:Remove/:remove no-op методы.
7. Creator-tag handling в TakeDamage (см. пункт 4).
12. Bouncer/батут: BodyVelocity с +Y и Parent=Torso/HumanoidRootPart →
эвристика "толкаем вверх" → playerSet jumpVelocity → реальный jump
через player._vy.
14. Mouse.Icon → CSS cursor на canvas (crosshair для не-пустых).
Также:
- RbxlHudOverlay.js — новый модуль DOM-оверлей для HUD-элементов
(KillFeed/Message/WinGui). Lazy-создаётся при первом hudMessage/killFeed.
- BabylonScene.serialize включает scene.teams и scene.team_spawns.
- Converter: scene = teams[] + team_spawns[]. TeamBeacon Model'и → команды.
- Deploy converter.py на VM 130.
Остались: 8 Regeneration, 9 BattleArmor, 10 WinGui/FireButton кастомное
позиционирование, 11 AdminConsole (no-op уже ok), 13 NotLinkedBlocker.
1. CFrame.to_euler_xyz переписан под Babylon YXZ convention:
rx = asin(-r12), ry = atan2(r02, r22), rz = atan2(r10, r11).
Раньше извлекал XYZ-Euler → Babylon применял как YXZ → клины,
мостики, наклонные постройки рендерились повёрнутые
(примеры из ROBLOX Battle: мостик торчал в стену).
Учтён gimbal-lock на X=±90°.
2. Lua watchdog в _startSingleScript и __rbxl_drain_handler:
debug.sethook(yield_50ms, '', 50000) — каждые 50k Lua-инструкций
принудительно yield 1 кадр. Защищает от:
while not workspace:FindFirstChild('X') do
workspace.ChildAdded:wait()
end
где наш stub :wait() возвращает -1 мгновенно — раньше скрипт
подвешивал вкладку (50k+ итераций в секунду). Сейчас yield'ит,
tickScheduler возобновляет.
3. Signal.Wait возвращает -1 как 'no-arg yield marker'. Сейчас
не используется в Lua, но если позже сделаем wrapper — будет.
ROBLOX Battle карта (arch1_ROBLOX_Battle_v2.rbxl, 1677 примитивов,
66 скриптов) — теперь не должна подвешивать.
Деплой rbxl_types.py на VM 130.
Зафиксированы все правки сделанные при работе с Crossroads:
- XML-парсер для старых .rbxl (~330 строк)
- BrickColor таблица расширена с 50 до 120 цветов
- Force-anchored для всех импортированных Part
- 4 новых слайдера в Свет и атмосфера (заливка теней,
экспозиция, контраст, насыщенность) через imageProcessingConfiguration
- Persistence настроек света в projectData.scene.lighting
- mat.ambientColor=(1,1,1) обязательно для scene.ambientColor работы
- Деплой rbxl-importer на VM 130 через прямой SSH (CI не настроен)
Известные баги Crossroads:
- 2 скрипта Regenerate* падают на model:clone() и Instance.new('Message')
— не критично, Anchored держит постройки.
Слайдеры sun/hemi/ambient/exposure/contrast/saturation теперь
сохраняются в projectData.scene.lighting при save и применяются
обратно при load.
Раньше параметры жили только в текущей сессии — после refresh
страницы возвращались к дефолтам.
Импортированные .rbxl карты также сохраняют выставленные пользователем
параметры света.
1. PrimitiveManager: mat.ambientColor=(1,1,1). Теперь scene.ambientColor
("Заливка теней" слайдер) реально влияет на тени. Юзер крутит
значение и видит изменение.
2. converter.py: Roblox-Part импортируется всегда с Anchored=True
(force-anchored). Welds у нас заглушки, без них unanchored Part'ы
рассыпаются физикой. Если юзеру нужно падающее — снимет в
инспекторе вручную.
Деплой converter.py на VM 130 + systemctl restart.
В Свет и атмосфера добавлено:
- Заливка теней (scene.ambientColor) — позволяет окрасить тени в
сером тоне без пересвета diffuse материалов.
- Экспозиция (ipc.exposure 0.3-2) — общая яркость через
imageProcessingConfiguration.
- Контраст (ipc.contrast 0.5-2)
- Насыщенность (colorCurves.globalSaturation -100..+100)
Юзер крутит слайдеры до момента когда импортированная Roblox-карта
выглядит как оригинал. Дефолты: ambient 0.3, exposure 1.0, contrast
1.0, saturation 1.0.
Также убрал mat.ambientColor=цвет — теперь default (0,0,0). Освещение
управляется глобально через панель.
Состояние пока не сохраняется в проект (только сессия). Persistence
добавим в следующем шаге.
ambient=diffuse (100%) суммировался с прямым светом и
давал пересвет (особенно на дорогах/полу с #cccccc).
40% — баланс: тень окрашена (видна как цвет, не чёрная),
прямой свет = чистый diffuse без пересвета.
Прошлая итерация без ambient давала почти-чёрные грани в тени —
sun под углом + hemi только вверх = низ/бок граней получают только
скудный hemi.groundColor=(0.3,0.3,0.4) = тёмные пятна.
Roblox-look: тень это просто менее яркий вариант цвета (не чёрный).
Фикс: mat.ambientColor = mat.diffuseColor (= цвет примитива).
scene.ambientColor=(0.3) × ambient(цвет) = 30% цвета в тени.
На прямом свете diffuse доминирует — белые остаются белыми,
зелёные зелёными.
Это даёт тени окрашенные (как в Roblox), сохраняя контраст со
светом и точность цвета.
Главная причина пересвета:
1. BrickColor 151 (Earth green = трава Crossroads) ОТСУТСТВОВАЛ
в таблице. Пол получал дефолт #cccccc и выглядел белым.
После анализа карты 344 примитива использовали дефолт.
2. mat.ambientColor=(1,1,1) + scene.ambientColor=(0.3) делало белые
цвета пересветлёнными — серый выглядел белым.
Фикс:
- BRICKCOLOR_TO_HEX расширен с ~50 до ~120 цветов. Добавлены:
151 (Earth green), 26 темный, 18, 115-148, 168-301, 1021-1032 и др.
После: #cccccc дефолт 344→68 (бОльшая часть теперь правильных).
- Убран mat.ambientColor — оставлен default (0,0,0). Lambert чистый:
освещённая грань = diffuse, тень = почти чёрная (scene.ambient смягчает).
Цвета теперь точно как в diffuse, без пересвета.
Деплой: converter.py скопирован на VM 130 + systemctl restart.
emissive прибавлялся к diffuse даже на ярком свету — пересвечивал
цвета (особенно белые/серые).
Новая схема:
- emissive = 0 (всегда)
- mat.ambientColor = (1,1,1) — пропускает scene.ambientColor (0.3)
в тени, делает тени 30% яркости цвета вместо чёрных
- На прямом свете diffuse доминирует, всё как должно быть
Это должно дать Roblox-look: серый #cccccc выглядит серым, белый
выглядит белым, в тенях цвет виден но темнее.
Версия 25% emissive + 1.0 ambient дала картинку слишком плоской:
пропали тени, объём, контраст. Roblox-оригинал имеет чёткие тени
от строений и контрастные грани.
Новые значения:
- mat.ambientColor: 1.0 → 0.5 (всё ещё подмешивает scene ambient
в тени, но не убивает контраст)
- glossy emissive: 25% → 8% (цвет 'живой' но не светится)
Должно дать баланс: цвет в тени виден, при этом тени остаются тенями.
Crossroads-импорт давал тёмно-грязные цвета вместо классических
насыщенных Roblox-цветов:
- трава тёмно-зелёная вместо ярко-зелёной
- дороги серые вместо белых
- крыши приглушённо-красные
Причины:
1. mat.ambientColor=(0,0,0) default — scene.ambientColor=(0.3,0.3,0.3)
не действовал. Тени получали 0 контрибьюшна цвета.
2. material=glossy (default для Roblox Plastic) шёл в case default:
только specularColor=(0,0,0), без emissive — цвет blandный.
Фикс:
- mat.ambientColor=(1,1,1) для всех материалов: подмешивает scene
ambient в тени, цвета остаются видны.
- Для glossy/default: emissive = 25% цвета (как в studs/45%, но скромнее),
specular слабый (0.05). Roblox-look — насыщенный даже без прямого
света.
Также case 'matte' теперь отдельный (был под default).
Старые Roblox-карты (Crossroads, ROBLOX Battle, и др. из эры 2007-2010)
сохранены в XML-формате (<roblox version=4>... вместо binary <roblox!...).
Наш парсер падал на 'missing <roblox! magic'.
Новое:
- rbxl_xml_parser.py: парсит XML-формат через стандартный xml.etree.
Поддерживает все типичные property-теги: string, bool, int, float,
Vector3, Vector2, CoordinateFrame, Color3, Color3uint8, BrickColor,
Ref, BinaryString, UDim/UDim2, PhysicalProperties, OptionalCFrame.
- В _parse_property: <int name=BrickColor> заворачивается в BrickColor
объект — converter ожидает .code атрибут.
- Алиасы PascalCase: name→Name, size→Size, shape→Shape (старый XML
использовал camelCase с маленькой первой буквой).
app.py:
- /analyze: авто-детект XML vs Binary по magic bytes. Если XML —
используем parse_xml(), иначе старый parse().
Тест на arch1_Original_Crossroads.rbxl: 877 instances, 777 Part,
83 Model — конвертится в 777 примитивов без warnings.
Roblox Day/Night раньше менял только сцен-clearColor и hemiLight через
setTimeOfDay (это работало — пол темнел). Но небо оставалось голубым
потому что SkyboxManager (купол + горы + звёзды) рулится отдельно.
Теперь по часу мапим в preset SkyboxManager:
06-08, 17-19 → sunset (оранжевое небо)
08-17 → lowpoly-roblox (синее день)
19-06 → starry-night (звёзды + луна + тёмное)
Используем skybox.fadeTo({preset}, 2) для плавного 2-секундного перехода
между пресетами (Урок 62 — кастомное небо).
Это даст реальную смену день↔ночь как в оригинале Roblox-Zapper'а.
Roblox Day/Night скрипт идёт wait(0.01) + 0.1 минуты = очень медленно.
В оригинале это типичный паттерн, юзеру не очень видно.
Ускоряем в 8x для импортированных скриптов: накопленная дельта часов
от Lua-скрипта × 8 → реальный hour для setTimeOfDay. Юзер видит полный
цикл день↔ночь за 10-15 секунд вместо часа.
Также убрал debug-лог lightingTimeUpdate (мешал).
memory access out of bounds в rbx_8 (Day/Night) — это WASM crash
который пробивает try/catch на JS-стороне. Защита — pcall внутри
самого Lua coroutine: даже если что-то падает в скрипте, ошибка
ловится Lua-side и не доходит до уровня wasmoon resume.
После этого fix остаётся только смотреть кто конкретно крашит —
шлём ошибку через __rbxl_send_error и идём дальше.
Day/Night скрипт в Roblox: while true do wait(0.01); SetMinutes(+0.1) end
= 100+ Hz обновлений Lighting.ClockTime. Каждое слало lightingTimeUpdate
через send() из coroutine, что (вероятно) вызывает WASM access crash.
Тротлинг прямо в SetMinutesAfterMidnight — не чаще раза в 250мс.
Lua-сторона продолжает делать высокочастотные обновления _minutes/ClockTime
(скрипт работает корректно), но в JS уходит только 4 раза в секунду.
Reblox-handler типа onEquippedLocal часто возвращают значение последнего
выражения (например :connect(fn) → conn object). wasmoon на JS-стороне
видит этот объект как result и тестирует на promise — крах
'null.then' если в цепочке встретится null/odd-shaped значение.
Фикс: оборачиваем fn(a1,...) в pcall — оно поглощает все return values.
+ Все 'return null' заменены на 'return undefined' (wasmoon quirk
из memory: null → PromiseTypeExtension crash).
Прошлый фикс с __rbxl_run_in_coroutine падал внутри wasmoon с
'Cannot read properties of null (reading then)' — wasmoon
PromiseTypeExtension тщетно ловит return от Lua-функции.
Новая стратегия:
1. Signal.Fire не запускает handler синхронно — складывает в JS-очередь
_pendingHandlerQueue.
2. tickScheduler в начале каждого тика drain'ит очередь, для каждого
handler'а вызывает Lua-функцию __rbxl_drain_handler в coroutine.
3. Поскольку tickScheduler уже стоит на main loop (не из JS-callback),
wait() внутри handler'а корректно yield'ится в свою coroutine.
Это разрешает:
- Roblox-обработчики с wait() внутри (Tool.Equipped с reload-таймаут)
- Любые цепочки signal:Connect → wait → action
- Стандартные шаблоны Roblox-Lua flow.
Roblox-скрипты делают:
Tool.Equipped:connect(function(mouse)
wait(0.15) -- yield внутри handler!
mouse.Icon = ...
end)
Когда сигнал Fired из JS-стороны (наш equipTool flow), мы напрямую звали
Lua-функцию — но Lua-yield в JS-callback падает с
'attempt to yield across a C-call boundary'.
Фикс: новая Lua-функция __rbxl_run_in_coroutine(fn, ...args) создаёт
свежую coroutine из handler'а, регистрирует в scheduler, делает первый
resume. Если handler уйдёт в wait — это yield в свою coroutine, не через
C-boundary. Scheduler tickScheduler потом возобновит её через delay.
Это закрывает RayGun.onEquippedLocal с wait(0.15), а также любые другие
Roblox-обработчики использующие wait() — в Roblox это стандарт.
Что чинит:
1. _registerRbxlTool падал: использовал scene3d.inventory (InventoryManager,
старый, без defineItem). Меняю на scene3d.invUI (новый InventoryUI с
defineItem) — теперь hotbar реально заполняется.
2. Lighting.SetMinutesAfterMidnight теперь шлёт lightingTimeUpdate в
GameRuntime → scene3d.setTimeOfDay(hour). Тротлинг 4 раза/сек.
Roblox Day&Night скрипт теперь визуально меняет небо в нашем плеере!
3. Instance.new('Sparkles') шлёт particleCreated в GameRuntime.
При эквипе Tool — _startRbxlToolParticles() запускает каждые 200мс
burst у позиции игрока (имитация искр из руки).
4. Авто-эквип первого Tool через 100мс после регистрации — юзеру не
нужно нажимать 1, инвентарь не очевиден.
5. stop() корректно гасит интервалы и сбрасывает state.
Эти 4 фикса должны дать Zapper-демке базовое визуальное поведение:
видный hotbar, искры из персонажа, плавная смена дня/ночи.
В БД импортированные скрипты хранятся с language='js' но фактически
это Lua-код в обёртке // @roblox-lua. HierarchyPanel рисовал жёлтую
плашку JS, что вводило в заблуждение.
Теперь isLua = (language=='lua') OR code starts with '// @roblox-lua'.
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, не запускаются.
Поддержка скриптов проекта 2792 (Roblox RayGun Tool, 9 скриптов).
Lighting: ClockTime, GetMinutesAfterMidnight, SetMinutesAfterMidnight,
GetSunDirection, fog* поля.
game:service(name) — старый Roblox API (lowercase alias на GetService).
Players: GetPlayerFromCharacter, playerFromCharacter, PlayerAdded, ChildAdded.
Instance.new новых типов:
- Tool/HopperBin: Equipped/Unequipped/Activated/Grip*/CanBeDropped
- IntValue/NumberValue/BoolValue/StringValue/ObjectValue + Value/Changed
- BodyForce/BodyVelocity/BodyPosition/BodyGyro + force/Velocity/MaxForce
- Weld/Motor6D/HingeConstraint + Part0/Part1/C0/C1
- Sparkles/ParticleEmitter/Fire/PointLight + Enabled/Color/Rate
- Mouse: Button1Down/KeyDown signals, Icon, Hit, Target, X/Y
Глобалы: BrickColor.new('name'/r,g,b) с палитрой 25+ цветов,
Ray.new, Region3.new.
Фикс WASM crash: rbx_wait минимум 0.016с (1 кадр) — без этого
while true do wait() end делал tight-loop без yield → stack overflow.
Добавлен RUBLOX_LUA_API_CHANGELOG.md — журнал что было добавлено
для каждой игры (для будущего портирования API в JS-движок).
Попытка выполнять Roblox-скрипты массово подвешивает страницу — даже
с object-stub Proxy и батчевым init. У типичной карты 500-2000 скриптов,
которые гоняют DataStore/Tools/RemoteFunction/PlayerGui — наш runtime
их не имеет и не должен иметь (это AGPL Roblox-клон, не эмулятор).
Импорт .rbxl теперь = ВИЗУАЛЬНЫЙ ПОРТЕР:
- геометрия, материалы, текстуры — да
- GUI (статические TextLabel/Button) — да
- физика, анимации игрока — да
- логика игры — пишется на нашем Lua (Этапы 1-7)
Юзер импортирует Roblox-карту → видит её точно → пишет свои скрипты
к примитивам, используя Vector3, Touched, Position-setters, Sound,
TweenService. Это работает идеально и без подвисаний.
Энтузиасты могут включить старое поведение через
window.__RBXL_RUN_IMPORTED = true перед Play.
1. TweenInfo.new(time, easing, direction, repeat, reverses, delay) —
глобальный конструктор. Был причиной 'attempt to index nil (TweenInfo)'.
2. Расширенный Enum: InfoType, SortOrder, FillDirection, Font,
TextXAlignment, ScaleType, PartType, SurfaceType, UserInputState
и др. — типичные для Roblox-туториалов.
3. NumberSequence/ColorSequence/NumberRange/Rect — заглушки конструкторов.
4. _kickoff() теперь батчами по 20 скриптов через setTimeout(0).
742 скрипта инициализируются за ~37 фреймов вместо одного
синхронного блока, UI не подвисает.
5. Импортированные .rbxl-скрипты ВКЛЮЧЕНЫ по умолчанию
(window.__RBXL_SKIP_IMPORTED=true чтобы выключить).
Падения отдельных скриптов изолированы — tickScheduler ловит
ошибки и удаляет битые coroutines, остальные продолжают работать.
Типичная .rbxl карта = 500-2000 Lua-скриптов. Многие используют
DataStoreService, Tools, PlayerGui, UserInputService, и т.п. фичи
которых у нас нет. Даже с object-stub Proxy сотни runtime-падений
после init подвешивают вкладку.
Решение: импорт сохраняет geometry+GUI+физику, скрипты пропускаются.
Юзер пишет свои Lua-скрипты к импортированным примитивам — они
используют наш Этап 1-7 API (Vector3, Touched, Position-setters,
Sound, TweenService) и работают идеально.
Включить старое поведение: window.__RBXL_RUN_IMPORTED = true в DevTools
перед Play.
Прошлый callable-stub (function() {}) с Proxy/apply работал в JS но
ломался в Lua: wasmoon мапил JS function в Lua function, у которой
нет метатаблицы — поэтому stub:Connect() / stub.SomeField падало с
'attempt to index a function value'.
Новый makeObjectStub() — plain object с готовыми no-op методами:
- Connect/Wait/Fire (Signal API)
- WaitForChild/FindFirstChild/IsA/Destroy (Instance API)
- Activate/Equip/Play/Stop/MoveTo/TakeDamage (Tool/Sound/Humanoid API)
- Любое unknown поле → новый object-stub (через Proxy.get)
Это снимает 99% оставшихся 'attempt to index a function value'.
Импортированные скрипты делают obj:Method(arg) где obj — stub. Раньше
stub был просто Folder, его как функцию вызвать нельзя → self2 is not
a function массово.
Фикс: makeCallableStub() — Proxy на function(){} который:
- вызывается как функция (apply trap) → возвращает себя;
- имеет .Connect/.Disconnect/.Wait/.Fire → ведёт себя как сигнал;
- любое .UnknownField → возвращает другой callable-stub;
- .then/.catch/.constructor → undefined (wasmoon не путается).
Этим закрывается основная масса остаточных падений в туториал-скриптах
с цепочками вроде Tool.Handle:WaitForChild('X'):Connect(...) где Handle
у нас отсутствует.
1. require(): в Roblox загружает ModuleScript. У нас модулей нет —
возвращаем mod как есть (если объект) или undefined.
2. Proxy улучшения:
- Object.hasOwnProperty + 'in' checks: методы (WaitForChild,
FindFirstChild и т.д.) точно не перехватываются;
- Symbol-ключи всегда undefined;
- System keys (then, catch, toString, constructor) → undefined
чтобы wasmoon не пытался обращаться как с Promise/класс;
- has() возвращает true для всех строковых ключей (избавляет от
падений на 'if obj.SomeField then ...').
Импортированные Roblox-скрипты массово падали на доступе к свойствам которых
у нас нет (.Selected, .Equipped, .MouseEnter и т.д.). В Roblox это сигналы
которые скрипты подключают через :Connect().
Фикс: оборачиваю newInstance() в Proxy:
- isProbablySignalName(prop) → возвращает makeStubSignal() (наш Signal с Connect/Fire)
- иначе → возвращает stub-Folder (тоже Instance с потенциальными children)
- системные ключи (then, __*, Symbol) → undefined чтобы wasmoon не путался
Эвристика покрывает основные Roblox-паттерны:
- *.Changed, *.Added, *.Removed, *.Began, *.Ended, *.Touched, *.Died
- Mouse*, Touch*, Input*, Render*, Step*, Heart*, On*, Char*, Player*
- Selected, Deselected, Equipped, Unequipped, Activated, Reached, Loaded
Это позволяет проходить инициализацию массе туториал-скриптов вместо
падения на первой же строке.
Импортированные .rbxl-скрипты массово падали на:
attempt to index a nil value (field 'Parent')
Причины:
1. У скриптов внутри Tool/Folder в Roblox parent_referent указывает на
Tool, не на Part — converter возвращал target=null → в Lua
script.Parent = nil. Стандартный паттерн script.Parent.Parent падал.
2. WaitForChild возвращал undefined для несуществующих children.
Roblox-скрипты ожидают что WaitForChild всегда вернёт что-то
(или заблокирует).
Фикс:
- LuaSharedSandbox: если primId не найден в partById, script.Parent =
workspace вместо nil. Это спасает 99% Roblox-туториал-скриптов
которые делают script.Parent.Parent.
- RobloxShim.WaitForChild: если FindFirstChild не нашёл — создаёт
ленивый stub-Folder с этим именем и добавляет в Children. Скрипт
не падает на script.Parent:WaitForChild('NonExistent').Something.
Раньше было два параллельных Lua-runtime:
- RobloxLuaSharedSandbox (Worker + wasmoon) для импортированных .rbxl;
- LuaSharedSandbox (main thread + wasmoon) для user-Lua.
Импортированные скрипты не получали фичи Этапов 4-6
(Position setters, GUI, Sound, TweenService) — их shim был в отдельном
Worker'е с более старым API.
Сейчас в GameRuntime.start():
1. Скрипты с маркером '// @roblox-lua' распаковываются
через unpackRobloxLuaCode() и попадают в тот же luaUserBatch
что и user-Lua;
2. Собыраются _rbxlImported=true для лога;
3. Числовой script.target (примитив id) уже совместим с
LuaSharedSandbox.addScript → резолвится в script.Parent.
Удалены мёртвые файлы (общий размер ~2500 строк):
- RobloxLuaSharedSandbox.js + RobloxLuaSharedWorker.js
- RobloxLuaSandbox.js + RobloxLuaWorker.js (старая пара)
- roblox-shim.js + roblox-services.js + roblox-physics.js
- roblox-scheduler.js + roblox-tween.js
- из rbxl-lua-integration.js убрана функция startRobloxLuaShared()
Побочный эффект: импортированные Roblox-игры теперь автоматически
получают:
- живые part.Position/Size/Color setters;
- полный GUI (Frame/TextLabel/TextButton);
- TweenService:Create с реальной интерполяцией;
- Sound с процедурными звуками;
- Humanoid.Health/Died и прочие фичи Этапов 4-6.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Этап 6 — Sound:
local s = Instance.new('Sound')
s.SoundId = 'coin' -- или 'jump'/'win'/'lose'/'hit'/'click'/'pickup'
s.Volume = 1
s.PlaybackSpeed = 1.2
s:Play()
s.Ended:Connect(function() print('ended') end)
SoundService:PlayLocalSound(sound) тоже работает.
Маппинг roblox-AssetID на встроенные звуки по эвристике (substring match).
Этап 7 — Документация:
RUBLOX_LUA_API.md — полный справочник всего реализованного.
Содержание: базовые типы, DataModel, Part-setters, Instance.new,
события (Touched/Heartbeat/RemoteEvent), таймеры, GUI, Sound,
TweenService, Humanoid, что не работает, готовый пример игры
(KillBrick + Coin + GUI-счётчик).
Этим завершается план RUBLOX_LUA_SUPPORT_PLAN (все 7 этапов).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
КОРНЕВАЯ ПРИЧИНА: BabylonScene.freezeStaticPrimitives() замораживает
world matrix anchored-примитивов при Play для оптимизации.
mesh.position.set(x,y,z) после этого не обновляет рендер.
Фикс в PrimitiveManager.updateInstance: если patch содержит position/
rotation/size — расфризить world matrix прежде чем менять transform.
При каждом tick'е tickScheduler делал lua.doStringSync(resume code),
что вызывало re-entrant WASM-крах memory access out of bounds после
нескольких task.wait() итераций.
Фикс: кешируем ссылку на __rbxl_resume_co при init и зовём её напрямую.
Это безопасный путь (не парсит код заново, не открывает вложенный
Lua-state поверх существующего).
Этап 4.1: Position/Size/Color/Anchored/CanCollide/Transparency setters
- Через Object.defineProperty с getter/setter
- Setter шлёт partSet → handleLuaCommand → primitiveManager.applyPatch
- Формат payload соответствует rbxl-lua-integration
Этап 4.2: Instance.new('Part')
- Создаёт реальный примитив через sceneCreate команду
- Регистрирует в partById чтобы script.Parent.Touched работал
- Автоинкремент id с базы 100000
Этап 4.3: part:Destroy()
- sceneDelete → primitiveManager.removeInstance
- Также удаляет из Workspace.Children
Этап 4.4: task.wait(sec) через coroutine.yield
- Каждый скрипт стартует как coroutine
- task.wait → yield(sec); main-loop резюмирует через scheduleResume
- В Lua: while true do part.Position = ...; task.wait(0.1) end
теперь работает корректно (raw80и не зависает UI)
Этап 4.5: BindableEvent + RemoteEvent
- Уже было в Instance.new (.Event = makeSignal()).
- Между Lua-скриптами работает через общий VM.
Этап 4.6: TweenService:Create
- Реальная интерполяция Vector3.Lerp / Color3.Lerp / number
- Через _stepTweens в tickScheduler каждый кадр
- tween.Completed signal фейерится по завершению
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Главное достижение: KillBrick работает.
script.Parent.Touched:Connect(fn) фейерится когда игрок касается куба,
humanoid:TakeDamage(100) → playerSet команда → BabylonScene.player.hp=0
→ respawn + playerDied event.
Архитектурные изменения:
- LuaSharedSandbox v3: wasmoon в MAIN потоке вместо Worker'а.
DevTools видит точные ошибки, breakpoints работают,
console.log в RobloxShim виден сразу.
- LuaSharedWorker.js удалён (больше не нужен).
- RobloxShim добавляет полное DataModel дерево:
game / Workspace / Players / LocalPlayer / Character /
Humanoid / HumanoidRootPart / 15 services (RunService.Heartbeat,
TweenService, HttpService, DataStoreService, etc).
- newPart создаёт RbxPart-обёртку вокруг каждого primitive в сцене,
Touched/TouchEnded signals.
Wasmoon-quirk:
- TypeError: Cannot read properties of null (reading 'then') возникает
когда JS-функция возвращает null в Lua-контекст. PromiseTypeExtension
делает .then без guard. Везде заменили null → undefined (push'ится как nil).
- _rbxl_get_part_by_id возвращает undefined если не нашёл, FindFirstChild и
прочие тоже undefined вместо null.
GameRuntime.js:
- _buildSceneSnapshot теперь даёт id (для partById), color, anchored,
canCollide, opacity полей у primitives.
- partSet/sceneCreate user-Lua → handleLuaCommand (rbxl интеграция).
- playerSet handler: humanoid.Health=0 → respawn + hpChange event.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Vector3:
- Magnitude теперь getter (без скобок) — как в Roblox
- Unit теперь getter
- Поддержаны lowercase алиасы magnitude, unit
Сохранение:
- BabylonScene serialize включает language в scripts[]
- Clip/copy включает language
- LuaSharedWorker.handleInit обёрнут в try/catch с детальной ошибкой
- LuaSharedWorker использует статический import для wasmoon
Этап 2 завершён полностью. Lua-runtime прошёл все тесты:
print, warn, Vector3, Color3, UDim2, CFrame, Enum, task.delay/spawn/defer,
RunService.Heartbeat, math/string/table, pcall.
Следующий этап 3: DataModel (game.Workspace + Instance + script.Parent +
Part.Touched + Part.Position setter).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Корень бага переключателя Lua: StudioCollab.js переопределял
scene.upsertScript с 4-мя аргументами (id,code,target,name) и не передавал
переданный 5-ый language в оригинал. Поэтому при смене языка в UI
language пропадал и переключатель оставался на JS.
Исправлено:
- Обёртка scene.upsertScript = function (id, code, target, name, language)
- Наследие collab-операции с language в sendOp и в _applyRemoteOp
Диагностические console.log убраны.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>