Раньше в статье "AI2. Контекст — скопируй в нейросеть" был только
JS-вариант. Юзер пишущий на Lua не мог использовать.
Сейчас:
- Новая константа AI_CONTEXT_LUA — полный API Lua-рантайма Рублокса
(стандартный Roblox-стиль: game:GetService, workspace, Vector3,
Instance.new, TweenService и т.п. + наши особенности).
- LangTabs обёртка над <Code> в AI2 — нейросеть получит контекст
на нужном языке.
- AI1 (как пользоваться) дополнен подсказкой про переключатель.
Также в <Code> добавлен prop plain=true для отключения подсветки —
AI-контекст это текст для копирования, а не код, ему подсветка
не нужна (и она ломала бы тысячи символов API-описаний).
Раньше код был монотонным — все белое на чёрном.
Сейчас цветной syntax highlighting в стиле Dracula:
- розовый (#ff79c6) — ключевые слова (let/const/function/local/end/then)
- голубой (#8be9fd) — встроенные (game/workspace/Math/Vector3)
- жёлтый (#f1fa8c) — строки
- фиолетовый (#bd93f9) — числа
- серый (#6272a4) italic — комментарии
- зелёный (#50fa7b) — имена функций (id с () после)
Реализация: docsLang.highlightCode() — простой regex-токенизатор.
<Code> компонент авто-детектит lang ('js'/'lua') по содержимому
(паттерны local/then/--/:Connect), либо принимает явный prop lang=.
Без внешних библиотек — ~80 строк регулярки, легко поддерживать.
В прошлом коммите добавил border к .docsSectionBody code (для inline-плашек).
Он унаследовался внутри pre.docCode — рамка появилась вокруг каждой строки.
Сбрасываем border:none для .docCode code.
Было: docsLang.jsx ставил тёмные .docTable (background #181b2c, color
#aab0c8) которые перебивали светлые стили вики (#fafbfd, #334155).
Получалось тёмно-синий текст на тёмно-синем — почти невидимо.
Сейчас:
1. Убран override .docTable / td / td:first-child — наследует светлые
стили из KubikonDocs.jsx как остальная вика.
2. Добавлен только .docTable th (его не было) — светло-голубой фон
eef2ff с тёмным текстом 1e3a8a для заголовков колонок.
3. .docsLangTabs переведён со тёмной (#181b2c) на светлую (#fff +
#f4f6fb head, #e0e6f0 рамка) тему. Активная вкладка — синяя.
Теперь все таблицы и LangTabs читаемые в светлом интерфейсе вики.
Каждая из 11 таблиц-разделов H1 теперь имеет JS и Lua колонки:
- Игрок: game.player.* vs humanoid/hrp/player.*
- Объекты сцены: game.scene.* vs Instance.new + workspace.*
- script-носитель: game.self vs script.Parent
- HUD: game.ui.* vs leaderstats + ScreenGui
- GUI: game.gui.* vs MouseButton1Click/FocusLost
- Физика/эффекты: game.physics/fx/constraints vs Raycast/Beam/Trail
- Камера/звук: game.camera/sound vs CurrentCamera + Sound
- События/таймеры: game.onTick/onKey vs Heartbeat/UIS/task.delay
- Утилиты: game.random/distance vs math.*/Vector3.Magnitude
- Мультиплеер: game.players/teams/leaderstats vs Players/Teams/Folder
- Окружение: game.environment/items/modal/menu vs Lighting/Backpack/ScreenGui
G1 NPC: scene.spawnNpc vs Model+Humanoid:MoveTo+BillboardGui
G2 Инвентарь: game.inventory vs Tool в Backpack
G3 Звук: game.sound.play vs Instance.new('Sound') + .Parent=Part для 3D
G4 Камера: camera.cutscene vs CurrentCamera + Scriptable + TweenService
G5 Beam/Trail: fx.beam vs Instance.new('Beam')+Attachment
G6 Мультиплеер: game.players vs Players + Teams сервисы
G7 Лидерборды: game.leaderstats vs leaderstats Folder + IntValue
G8 Damage floaters: game.fx.damageFloater vs BillboardGui+TweenService
G9 Инвентарь: game.items vs Backpack+leaderstats для подсчёта
G10 Небо: game.scene.setSkybox vs Sky+Atmosphere в Lighting
G11 Модалки: game.modal.dialog vs ScreenGui+Frame+TextLabel
G12 Машины: game.scene.spawn('vehicle:car') vs VehicleSeat
C3 кнопка: game.gui.onClick vs MouseButton1Click + PlayerGui:FindFirstChild
C4 поле ввода: onSubmit vs FocusLost + box.Text
C1, C2, C5 — без кода (общая теория), Picker сверху всё равно есть.
F1 HP: damage/heal vs humanoid:TakeDamage / Health
F2 Физика: raycast vs workspace:Raycast (полный пример со стрельбой)
F3 Атрибуты: setData/getData vs :SetAttribute/:GetAttribute
F4 Теги: scene.tag vs CollectionService:AddTag/GetTagged
F5 E-взаимодействие: onInteract vs ProximityPrompt
F6 Billboard: setLabel vs BillboardGui+TextLabel
F7 passThrough: physics.passThrough vs CanCollide=false
F8 Связи: constraints.hinge vs HingeConstraint+Attachment
Lua-примеры по канонам Roblox: Instance.new, Attachment, Vector3.new,
UDim2.new, Color3, BrickColor, math.min.
Переключатель JS/Lua теперь реально влияет на содержимое в каждой
из 8 статей раздела. Для каждой темы дан рабочий код на обоих языках:
- D1 Что такое скрипт: game.log vs print
- D2 Глобальный/на объекте: game.self vs script.Parent
- D3 Переменные: let vs local
- D4 game vs game:GetService/workspace
- D5 game.log vs print
- D6 События: game.onTick/onClick vs RunService.Heartbeat / ClickDetector
- D7 if/else: === vs ==, !== vs ~=, then/end
- D8 Таймеры: game.after/every/cancel vs task.delay/wait/spawn
Также пояснительные плашки <Note> подобраны под язык —
указания специфичные для синтаксиса каждого языка.
Что сделано:
1. docsLang.jsx (НОВЫЙ):
- DocsLangProvider — Context для выбранного языка (localStorage).
- DocsLangPicker — большой переключатель JS/Lua над разделом.
- <LangTabs js={...} lua={...} /> — локальные вкладки внутри
статьи: показывает контент текущего языка.
- useDocsLang() хук.
- Стили для picker / tabs / langChoiceModal / docTable.
2. docsData.jsx:
- Новая статья D0 "Скриптинг: JS или Lua — что выбрать?"
в самом верху раздела D. Сравнение, примеры одного и того же
кода на двух языках, советы новичкам.
- Импорт LangTabs.
3. KubikonDocs.jsx:
- ChapterPage обёрнут в DocsLangProvider + DocsLangPicker сверху.
Юзер может одним кликом переключить весь раздел JS↔Lua.
- LessonPage: при «Открыть мою копию» теперь показывается модалка
LangChoiceModal (JS / Lua). Создаём копию с нужными скриптами.
- convertProjectScriptsToLua() конвертит project_data:
если в скрипте есть code_lua слот — активируем. Иначе ставим
stub с подсказкой.
4. docsGamesBuilders.js:
- buildGameProject(id, opts) принимает opts.lang='lua'.
Та же логика — code_lua или stub.
ОСТАЛОСЬ (постепенно):
- Lua-эквиваленты в существующих 78 статьях. Сейчас Picker уже
показывается, но если в статье нет <LangTabs> — контент одинаковый.
Будем добавлять <LangTabs> в ключевые места по очереди.
- Lua-версии в GAME_BUILDERS для уроков 1-50 (code_lua слот).
Roblox-конвенция: Players.PlayerAdded не срабатывает для игроков уже
на сервере к моменту подключения хендлера. Юзер пишет:
Players.PlayerAdded:Connect(function(p) print(p.Name) end)
и удивляется почему лог пустой — игрок-то уже есть.
В реальном Roblox делают:
for _, p in ipairs(Players:GetPlayers()) do print(p.Name) end
Players.PlayerAdded:Connect(...)
Но мало кто помнит про этот workaround.
Решение: после kickoff всех скриптов (когда все Connect'ы установлены)
из LuaSharedSandbox шлём через api.fireExistingPlayers() →
PlayerAdded.Fire(localPlayer) + CharacterAdded.Fire(character).
Также:
- Добавлены localPlayer.CharacterAdded/CharacterRemoving/AppearanceLoaded
signals (раньше не было).
- Шаблон LUA_TEMPLATE_GLOBAL обновлён: для всех Players:GetPlayers()
делаем print, плюс PlayerAdded:Connect для будущих. Юзер видит
результат сразу при первом Запустить.
- Шаблон LUA_TEMPLATE_PART сразу пишет 'Скрипт детали X запущен'.
Раньше при смене языка код оставался как есть → подсветка кричит
ошибками, юзер пишет JS в Lua-режиме и наоборот.
Сейчас:
- Скрипт хранит code_js и code_lua отдельно (плюс активный code).
- При переключении JS↔Lua: текущий code сохраняется в слот ТЕКУЩЕГО
языка, достаётся слот ЦЕЛЕВОГО языка. Если слот пустой — шаблон.
- При обычном save (печатает юзер) код зеркалится в слот активного
языка чтобы не потерять при swap.
- Никаких модалок: переключение мгновенное, ничего не пропадает.
Сценарии:
- Новый проект: js-шаблон в code_js. Клик Lua → подставляется lua-шаблон,
code_js сохранён.
- Юзер написал JS-код, переключился на Lua: JS улетел в code_js,
показывается пустой Lua-шаблон. Клик обратно JS → код возвращается.
- Старые проекты (без code_js/code_lua): первое переключение
засеет слот текущего языка из code.
При переключении JS↔Lua показывалась модалка "Сменить язык?" даже
если код не пустой. Юзер: не нужна, переключай сразу.
Сейчас: код остаётся как есть, меняется только подсветка синтаксиса
Monaco. Если код был пустой шаблон — подставляется новый шаблон языка.
Robloxity (20402 Part, 278 скриптов, 295 BillboardGui, 0.1 FPS) показал:
1. Большие карты могут зависнуть студию навсегда.
2. BillboardGui/SurfaceGui (вывески, табло) рендерятся в 3D-сцене и
при 200+ штук убивают FPS.
Фиксы:
1. Предупреждение в модалке если parts > 5000 (жёлтое) или > 15000
(красное "может зависнуть"). Подсказка про режимы.
2. Новая опция guiMode (показывается если GUI > 50 элементов):
- 'all' — все, как было.
- 'screen-only' (рекомендуется) — только ScreenGui HUD,
BillboardGui/SurfaceGui удаляются.
- 'skip' — без GUI совсем.
3. converter.py: маркирует элемент полем gui_container_kind:
'screen' / 'billboard' / 'surface'.
4. app.py: _apply_gui_mode() фильтрует scene.gui[] по режиму.
Deploy app.py + converter.py на VM 130.
Robloxity рекомендуем импортировать со screen-only — Карта Robloxity
будет работать в 5-10× быстрее без вывесок города.
3 опции в модалке (только если в карте есть скрипты):
- 'disabled' (default) — скрипты импортируются с enabled=false в метадате
→ GameRuntime их не запускает, но видны в иерархии для чтения
как референс при написании своих Lua-скриптов.
- 'enabled' — скрипты активны (старое поведение). Может вешать игру
на старых Roblox 2007-2010 паттернах.
- 'skip' — scripts[] обнуляется, чистый импорт только геометрии.
Реализация:
- RbxlImportModal.jsx: state scriptsMode + radio-блок над названием игры,
показывается только если report.scripts_total > 0.
- rbxlImporterApi.js: передача scripts_mode в /import/rbxl/create.
- app.py: _apply_scripts_mode() патчит JSON-метадату на 2-й строке
packed-кода скрипта (или удаляет scripts[] для 'skip').
GameRuntime уже умеет уважать meta.enabled === false — пропускает скрипт.
Deploy app.py на VM 130.
Скрипты Roblox 2009 содержат паттерн:
while not parent:FindFirstChild(name) do
parent.ChildAdded:wait()
end
Наш sig.Wait() возвращает -1 синхронно (без yield), цикл крутится
бесконечно без шанса coroutine.yield. debug.sethook не помогает
если код находится в C-call boundary к моменту срабатывания.
Решение: regex-фильтр в GameRuntime.js перед добавлением в batch.
Скрипты с такими паттернами не запускаются — пишется warn в консоль.
В ROBLOX Battle это ~10-15 скриптов: RoundScript, Spawner,
ReEquipLastWeapon, LeaderboardV3, Leaderstats и др. Карта потеряет
эту функциональность (раунды, респавн), но играется.
Прошлый коммит pcall(coroutine.yield) дал бесконечный цикл:
yield внутри C-call падал → pcall ловил → hook возвращался → счётчик
не сбросился → срабатывал опять моментально → вис.
Новая стратегия:
1. Голый coroutine.yield в watchdog: если внутри C-call упадёт с
ошибкой — pcall(fn,...) внутри coroutine её поймает, скрипт
завершится. Лучше чем вис.
2. Frequency 100k→20k инструкций — yield чаще, меньше времени на
tight-loop перед уступкой управления UI.
3. Batch kickoff 20→5 скриптов с delay 20мс (было 0). 55 скриптов
ROBLOX Battle = ~200мс распределено, UI отзывается.
Page-hang при init должен исчезнуть. Скрипты с tight-loop типа
WaitForChild через ChildAdded:wait() упадут с ошибкой про yield,
но не повесят страницу.
Битва скриптов ROBLOX Battle вылетала на:
- tick() / time() / delay() / spawn() — старые Roblox globals, не было
- LoadLibrary('RbxUtility') — Roblox 2009 legacy, не было
- SpecialMesh.MeshType — класс не реализован, доступ к полю крашил
- attempt to yield across C-call boundary — debug.sethook yield без pcall
Фиксы:
1. Lua-prelude: tick=os.time, time=os.clock*1000, delay/spawn через
coroutine, LoadLibrary возвращает proxy-стаб через metatable.
2. Instance.new('SpecialMesh'/'BlockMesh'/'CylinderMesh'/'FileMesh')
стабы с MeshType/MeshId/Scale полями.
3. debug.sethook: pcall(coroutine.yield, ...) вместо голого yield —
если внутри C-call, ошибка молча проглатывается, hook сработает
позже когда Lua вернётся из C. Frequency 50k→100k.
4. script.Parent в Lua-обёртке: setmetatable __index → workspace
fallback для script.Foo:Bar() паттернов. Гарантия что
_scriptParent.Parent ~= nil.
ROBLOX Battle должна показать меньше errors на этом запуске.
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.
Слайдеры 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 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, остальные продолжают работать.