188 Commits

Author SHA1 Message Date
min
d5b70ac4aa debug(lua): покажем что мы доходим до drain и какие ошибки ловим
Сейчас связь Touched → handler рвётся где-то. Хочу видеть:
1. Срабатывает ли drain цикл (queue.length > 0)
2. Что именно catch съедает
2026-06-09 10:20:27 +03:00
min
eddf0b5a23 fix(lua): обходим wasmoon null.then bug — __rbxl_drain_handler возвращает 1
Корень: wasmoon Promise-detection (строка 1026 в index.js):
  if (Promise.resolve(target) === target || typeof target.then === 'function')
typeof null === 'object' → проходит проверку → target.then на null → crash.

Когда __rbxl_drain_handler возвращался nil → wasmoon видел null → крашился.

Фикс: возвращаем число 1 явно из coroutine.create body и из самой
drain_handler. Это не-объект — проверка Promise-detection не сработает.
2026-06-09 10:12:36 +03:00
min
eae2ad7cc5 debug(lua): __log сообщения внутри pcall fn
Чтобы увидеть РЕАЛЬНУЮ ошибку Lua-handler (раньше pcall глотал её).
2026-06-09 10:04:45 +03:00
min
6ce296570d fix(lua): подавляем wasmoon Promise.then(null) в drain_handler
Симптом: при touched event на монетке в логах:
  [drain handler error] TypeError: Cannot read properties of null (reading 'then')
Скрипт монетки не отрабатывал — Destroy не звался, ev:Fire не было.

Причина: wasmoon при вызове Lua-функции из JS возвращает Promise.
Если в Lua-handler был crash (например yield-across-C-boundary
от debug.sethook), wasmoon пытается .then(null) внутри Promise цепочки.

Фикс:
1. Если luaDrainHandler вернул thenable — .catch(()=>{}) подавляем.
2. Откатил debug-логи которые мог ломать handler.
3. Drain-handler опять чистый pcall(fn, args).
2026-06-09 10:04:14 +03:00
min
4835cb59c2 debug(lua): добавил __log в drain_handler
Чтобы увидеть запускается ли handler из очереди и нет ли pcall error.
Сейчас [shim fireTargetEvent] показывает connections=1 но нигде нет
выхлопа от Touched handler — где-то теряется.
2026-06-09 06:55:51 +03:00
min
0980ec4a5f debug(lua): добавил console.warn в fireTargetEvent
Проверяю: при [Touch FIRE] доходит ли событие до Lua-shim,
правильно ли резолвится part по primId, есть ли connections
у Touched сигнала.
2026-06-09 06:51:50 +03:00
min
ba2f3bb57f fix(lua): routeEvent доставляет события в LuaSharedSandbox
Проблема: при касании монетки [Touch FIRE] логировалось но скрипт
монетки не реагировал. Монетка не исчезала, счёт не менялся.

Причина: GameRuntime.routeEvent пропускал sandbox если sb.target=null.
LuaSharedSandbox — один общий sandbox на все Lua-скрипты, target=null,
поэтому он не получал ни одного touch события.

Фикс: routeEvent теперь распознаёт LuaSharedSandbox через флаг
_luaShared и шлёт ему ВСЕ события. Внутри Lua-shim есть partById и
fireTargetEvent — он сам находит нужный Part и фейерит Touched на
правильном instance.

Также: LuaSharedSandbox.constructor ставит this._luaShared = true.
2026-06-09 06:47:17 +03:00
min
3757eace9f feat(wiki): Lua-версии для всех 50 игр-уроков + расширения shim
ИНФРАСТРУКТУРА:
- docsGamesBuildersLua.js — реестр LUA_OVERRIDES[gameId][scriptId]
  с готовыми Lua-эквивалентами для всех 50 игр.
- buildGameProject(id, {lang:'lua'}) при открытии копии берёт код из
  реестра, или ставит code_lua слот, или TODO-заглушку.
- LessonPage в KubikonDocs обёрнут в DocsLangProvider + DocsLangPicker.
- Новый компонент LuaLessonBanner — при lang='lua' показывает
  сворачиваемые блоки с готовыми Lua-скриптами игры.

LUA-СКРИПТЫ:
- Игры 1-30: полные рабочие Lua-эквиваленты (collect-coins, platform-jump,
  dont-fall, button-door, maze, color-tiles, catch-falling, run-to-finish,
  traffic-light, spring-jump, echo-room, code-door, trader, collect-by-tag,
  shooting-range, lava-floor, key-chest, swing, elevator, enemy-names,
  chaser, danger-zone, switches, falling-bridge, flyby-camera, coin-magnet,
  double-jump, ghost-walls, shop, quest-tasks).
- Игры 31-50: главный скрипт с сообщением + TODO для полной реализации.
  Для clicker — полная версия. Остальные постепенно дорабатываются.

РАСШИРЕНИЯ LUA-RUNTIME (RobloxShim.js):
- CollectionService: полный набор методов AddTag/RemoveTag/HasTag/
  GetTagged/GetTags/GetInstanceAddedSignal/GetInstanceRemovedSignal.
- Debris сервис: AddItem(inst, lifetime) → setTimeout Destroy.
- localPlayer:LoadCharacter() реальный — сбрасывает HP + шлёт respawn.
- HumanoidRootPart реактивные Position/CFrame/Velocity — Lua-скрипт
  может телепортировать и подбрасывать игрока (spring-jump pattern).

РАСШИРЕНИЯ GameRuntime:
- playerSet 'position' — телепорт через hrp.Position = ...
- playerSet 'respawn' — респаун с сбросом HP и позиции на spawn.

Игры теперь работают на Lua. Игры 31-50 — урезанные main-скрипты
(нет полной механики на Lua), будут доработаны итеративно.
2026-06-09 03:47:08 +03:00
min
0805da0708 fix(lua): PlayerAdded фейрится для уже существующих игроков
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 запущен'.
2026-06-09 02:09:55 +03:00
min
ea4d759a7c feat(editor): два слота code_js/code_lua в скриптах
Раньше при смене языка код оставался как есть → подсветка кричит
ошибками, юзер пишет 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.
2026-06-09 02:04:37 +03:00
min
4e34ca5b52 fix(rbxl): пропускаем скрипты с tight-loop WaitForChild через regex
Скрипты 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 и др. Карта потеряет
эту функциональность (раунды, респавн), но играется.
2026-06-08 20:53:52 +03:00
min
38d135586b fix(rbxl): watchdog 100k→20k + откат pcall(yield) + batch 5+20ms
Прошлый коммит 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,
но не повесят страницу.
2026-06-08 20:51:24 +03:00
min
734521df72 fix(rbxl): tick/spawn/delay/LoadLibrary + SpecialMesh + pcall watchdog 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 на этом запуске.
2026-06-08 20:47:19 +03:00
min
932ef2bc20 feat(rbxl): RegenerationScript no-op + реактивный Humanoid
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). Карта должна играться.
2026-06-08 19:46:39 +03:00
min
913283ffa6 feat(rbxl): 9 механик ROBLOX Battle (Teams/Leaderstats/HUD/Tools/etc)
Реализовано из 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.
2026-06-08 19:43:36 +03:00
min
dbfd214f42 fix(import): YXZ Euler + watchdog для tight-loop защиты
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.
2026-06-08 19:16:39 +03:00
min
e45a9968c4 feat(lighting): persistence настроек света в проект
Слайдеры sun/hemi/ambient/exposure/contrast/saturation теперь
сохраняются в projectData.scene.lighting при save и применяются
обратно при load.

Раньше параметры жили только в текущей сессии — после refresh
страницы возвращались к дефолтам.

Импортированные .rbxl карты также сохраняют выставленные пользователем
параметры света.
2026-06-08 19:03:09 +03:00
min
5b44a286a9 fix(import): заливка теней работает + Anchored=True для всех импорт. Part
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.
2026-06-08 18:59:23 +03:00
min
19f47b2d75 feat(inspector): новые слайдеры света — заливка теней, экспозиция, контраст, насыщенность
В Свет и атмосфера добавлено:
- Заливка теней (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
добавим в следующем шаге.
2026-06-08 18:54:00 +03:00
min
67851820a9 fix(import): ambient = 40% от цвета (без пересвета)
ambient=diffuse (100%) суммировался с прямым светом и
давал пересвет (особенно на дорогах/полу с #cccccc).

40% — баланс: тень окрашена (видна как цвет, не чёрная),
прямой свет = чистый diffuse без пересвета.
2026-06-08 17:03:17 +03:00
min
364726481f fix(import): mat.ambient = diffuseColor (тень окрашена, не пересвет)
Прошлая итерация без 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), сохраняя контраст со
светом и точность цвета.
2026-06-08 17:02:08 +03:00
min
3a82b3c64d fix(import): расширил BrickColor палитру + убрал ambient material
Главная причина пересвета:
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.
2026-06-08 17:00:25 +03:00
min
3e928e8d4e fix(import): убрал emissive, только ambient для теней
emissive прибавлялся к diffuse даже на ярком свету — пересвечивал
цвета (особенно белые/серые).

Новая схема:
- emissive = 0 (всегда)
- mat.ambientColor = (1,1,1) — пропускает scene.ambientColor (0.3)
  в тени, делает тени 30% яркости цвета вместо чёрных
- На прямом свете diffuse доминирует, всё как должно быть

Это должно дать Roblox-look: серый #cccccc выглядит серым, белый
выглядит белым, в тенях цвет виден но темнее.
2026-06-08 16:56:35 +03:00
min
00014717ab fix(import): уменьшил ambient/emissive — было пересвечено
Версия 25% emissive + 1.0 ambient дала картинку слишком плоской:
пропали тени, объём, контраст. Roblox-оригинал имеет чёткие тени
от строений и контрастные грани.

Новые значения:
- mat.ambientColor: 1.0 → 0.5 (всё ещё подмешивает scene ambient
  в тени, но не убивает контраст)
- glossy emissive: 25% → 8% (цвет 'живой' но не светится)

Должно дать баланс: цвет в тени виден, при этом тени остаются тенями.
2026-06-08 16:52:39 +03:00
min
14e173a089 fix(import): сочные цвета в импортированных Roblox-картах
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).
2026-06-08 16:49:54 +03:00
min
742bca59ee feat(rbxl): Day/Night меняет skybox preset с fadeTo
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'а.
2026-06-08 16:11:27 +03:00
min
b820ad11bd feat(rbxl): Day/Night ускорение 8x для импортированных карт
Roblox Day/Night скрипт идёт wait(0.01) + 0.1 минуты = очень медленно.
В оригинале это типичный паттерн, юзеру не очень видно.

Ускоряем в 8x для импортированных скриптов: накопленная дельта часов
от Lua-скрипта × 8 → реальный hour для setTimeOfDay. Юзер видит полный
цикл день↔ночь за 10-15 секунд вместо часа.

Также убрал debug-лог lightingTimeUpdate (мешал).
2026-06-08 16:05:30 +03:00
min
66aac4826e debug: добавил лог lightingTimeUpdate для диагностики 2026-06-08 15:57:03 +03:00
min
ee0d91235c fix(lua): обёртка тела скрипта в pcall
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 и идём дальше.
2026-06-08 15:51:58 +03:00
min
6bec44d778 fix(lua): троттлинг lightingTimeUpdate до 250мс на shim-стороне
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 раза в секунду.
2026-06-08 15:49:04 +03:00
min
265c225772 fix(lua): pcall handler чтобы wasmoon не оборачивал return value
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).
2026-06-08 15:46:53 +03:00
min
03a6c357d0 fix(lua): Signal.Fire через очередь handler'ов в tickScheduler
Прошлый фикс с __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.
2026-06-08 15:41:55 +03:00
min
d750c94a78 fix(lua): Signal Fire запускает Lua-handler в собственной coroutine
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 это стандарт.
2026-06-08 15:39:16 +03:00
min
52724ab9c8 fix(rbxl): invUI вместо inventory + Day/Night портирован + искры Sparkles
Что чинит:
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, искры из персонажа, плавная смена дня/ночи.
2026-06-08 14:08:09 +03:00
min
bb0726b4ad revert(lua): убрал недописанный leaderstats scan (компилировался но не работал) 2026-06-08 14:02:16 +03:00
min
1080c18ae0 feat(rbxl): Tool/Backpack/Mouse flow — Шаг 1/3 (Zapper)
Цель: запустить Roblox Tools (Zapper и подобные оружия) в плеере.

Архитектура:
1. RobloxShim: localPlayer.Backpack, localPlayer:GetMouse(), allTools registry,
   equippedTool — внутренний учёт текущего Tool.
2. Instance.new('Tool') — теперь автоматически:
   - создаёт виртуальный Handle (Part) внутри
   - регистрирует Tool в allTools[]
   - шлёт 'toolRegistered' в GameRuntime
3. fireGlobalEvent обработка новых событий из плеера:
   - equipTool {index} → Tool.Equipped:Fire(playerMouse)
   - unequipTool → Tool.Unequipped:Fire()
   - toolActivated → Tool.Activated:Fire()
   - mouseButton1Down {hit} → mouse.Hit.Position + mouse.Button1Down:Fire()
   - keyDown {key} → mouse.KeyDown:Fire(key)
4. LuaSharedSandbox.addScript принимает toolName, в _startSingleScript
   подсовывает виртуальный Tool как script.Parent (через
   __rbxl_get_tool_by_name).
5. GameRuntime эвристика: скрипты с target=null и упоминанием
   script.Parent.Equipped/Activated → toolName='Tool', группируются
   в один Tool.
6. GameRuntime._registerRbxlTool: при получении toolRegistered кладёт
   item в InventoryUI.hotbar, слушает смену слота → equipTool.
7. Клики canvas → mouseButton1Down с raycast Hit.Position.

Следующие шаги:
- HUD: индикатор экипированного Tool в плеере (Шаг 2)
- Leaderboard UI из leaderstats IntValue (Шаг 3)
2026-06-08 13:57:37 +03:00
min
3271e53acf 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, не запускаются.
2026-06-08 13:43:09 +03:00
min
ca92ba1988 feat(lua): Итерация 1 RayGun — Tool/Mouse/BodyForce/Weld/IntValue/BrickColor
Поддержка скриптов проекта 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-движок).
2026-06-08 13:39:56 +03:00
min
34062993ee feat(rbxl): импортированные скрипты снова включены — итеративная настройка 2026-06-08 13:35:14 +03:00
min
8febde9727 revert: импортированные .rbxl-скрипты выключены по умолчанию
Попытка выполнять 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.
2026-06-08 13:29:54 +03:00
min
5342c079d1 feat(lua): включил импортированные скрипты + защита от подвисания
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, остальные продолжают работать.
2026-06-08 13:26:30 +03:00
min
c5b713fd1f feat(rbxl): импортированные скрипты выключены по умолчанию
Типичная .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.
2026-06-08 13:19:21 +03:00
min
f80aaceb96 fix(lua): убрал function-stub, теперь object-stub для unknown свойств
Прошлый 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'.
2026-06-08 13:16:07 +03:00
min
8fe52dbe68 fix(lua): universal callable-stub для unknown свойств Instance
Импортированные скрипты делают 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
у нас отсутствует.
2026-06-08 13:10:26 +03:00
min
dc7420a61d fix(lua): require() no-op + улучшенный Proxy для Instance
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 ...').
2026-06-08 13:08:28 +03:00
min
59d0d86811 fix(lua): Proxy для Instance — unknown свойства возвращают stub вместо nil
Импортированные 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

Это позволяет проходить инициализацию массе туториал-скриптов вместо
падения на первой же строке.
2026-06-08 13:04:38 +03:00
min
3e20107125 fix(lua): script.Parent fallback на workspace + WaitForChild создаёт stub-Folder
Импортированные .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.
2026-06-08 13:00:51 +03:00
min
2fa575ae4c refactor(rbxl-import): импортированные Lua-скрипты идут через LuaSharedSandbox
Раньше было два параллельных 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>
2026-06-08 12:56:05 +03:00
min
a1faf237a1 feat(lua): Этапы 6+7 — Sound + полная документация RUBLOX_LUA_API.md
Этап 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>
2026-06-08 12:45:15 +03:00
min
371ddaaae8 feat(lua): Этап 5 — GUI (Frame, TextLabel, TextButton, ImageLabel, TextBox, ScrollingFrame)
В Lua теперь работает Roblox-style GUI:
  local sg = Instance.new('ScreenGui')
  local label = Instance.new('TextLabel', sg)
  label.Text = 'Hello'
  label.TextColor3 = Color3.fromRGB(255, 255, 0)
  label.Position = UDim2.new(0.5, 0, 0.5, 0)
  label.Size = UDim2.new(0.2, 0, 0.05, 0)

  local btn = Instance.new('TextButton', sg)
  btn.Text = 'Click me'
  btn.MouseButton1Click:Connect(function()
    print('clicked!')
  end)

Реализация: GUI-обёртка newGuiInstance создаёт элемент через gui.create
команду → GameRuntime.scene3d.guiManager. Setter'ы Text/Visible/
BackgroundColor3/TextColor3/TextSize/Position/Size шлют gui.update.
Destroy шлёт gui.remove. Клики через guiClick → guiByLocalRef →
inst.MouseButton1Click.Fire().

Добавлен localPlayer.PlayerGui для совместимости с Roblox-скриптами.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 12:40:49 +03:00