162 Commits

Author SHA1 Message Date
min
efff54add2 fix(lua): keydown/keyup в нижнем регистре (BabylonScene шлёт 'keydown')
BabylonScene._normalizeKey → routeGlobalEvent('keydown', {key:'e'}) →
sb.sendGlobalEvent({type:'keydown', key:'e'}) → shim.fireGlobalEvent(p).

Shim проверял только p.type === 'keyDown' (camelCase) — keydown
(lowercase) пропускался. UserInputService.InputBegan не фейерился.

Фикс: принимаем оба варианта keyDown/keydown и keyUp/keyup.
2026-06-09 17:07:44 +03:00
min
05a7eaf371 feat(lua-games): полный паритет для игры 4 «Кнопка-открывашка»
JS-версия:
- ui.showText('Подойди и нажми E', 4)
- onMessage 'win' → showText + win sound + confetti
- g4_button: onInteract (E) → click + tween двери (y:8) + showText 'Дверь открывается!'
- g4_finish: onTouch → broadcast 'win'

Lua-версия (паритет):
- __rbxl_show_text подсказка + 'Победа!'
- BindableEvent WinReached
- g4_button: UserInputService.InputBegan + ProximityHint через BillboardGui
  ('[E] Открыть дверь' над кнопкой когда игрок в радиусе 4)
  E → click Sound + TweenService:Create двери (Position +6 по Y, 1.2с) +
  CanCollide=false + showText 'Дверь открывается!'
- g4_finish: Touched → ev:Fire с fired-флагом

Shim фиксы:
- UserInputService.InputBegan/InputEnded теперь фейерятся на keyDown/keyUp.
  Передаётся InputObject с KeyCode = ССЫЛКА на Enum.KeyCode.<KEY>
  (важно для сравнения == Enum.KeyCode.E).
2026-06-09 13:32:42 +03:00
min
f56e9417c9 fix(lua): player._pos вместо body.position
player.body.position не существует — позиция в PlayerController._pos.
Из-за этого realPos оставался null, api._realPlayerPos не обновлялся,
конфетти вылетали из начальной hrp._position (0, 5, 0).
2026-06-09 13:20:51 +03:00
min
96644ede15 fix(lua): __rbxl_player_x/y/z отдельные функции (wasmoon-userdata fix)
Корень: __rbxl_player_pos() возвращал JS-object {x,y,z}, wasmoon оборачивал
его в userdata-proxy. В Lua pos.x давал NaN. Конфетти спавнились с NaN.

Фикс: 3 отдельные функции __rbxl_player_x/y/z возвращающие числа.
В скрипте игры 2 используем их напрямую.
2026-06-09 13:18:30 +03:00
min
8321a526cd fix(lua): __rbxl_player_pos возвращает реальную позицию игрока
Проблема: __rbxl_player_pos() возвращал (0,8,0) — нач. позицию hrp._position,
которая не обновлялась. Конфетти всегда вылетали из стартовой точки.

Фикс:
- api._realPlayerPos обновляется в GameRuntime tick (каждый кадр)
  через api.updatePlayerPos(x, y, z) из player.body.position.
- __rbxl_player_pos() в Lua возвращает api._realPlayerPos если есть.

Убраны debug-логи.
2026-06-09 13:01:45 +03:00
min
6fe249033e debug: лог в _spawnParticles и Lua onCommand для scene.particles 2026-06-09 12:56:20 +03:00
min
7384494c8f fix(lua): scene.particles payload — type вместо kind, payload.position
BabylonScene._spawnParticleEffect читает payload.type ('confetti') и
payload.position {x,y,z}. Я слал {kind, pos} — type=undefined →
fallback на 'sparks' → бледные одиночные искорки вместо салюта.

После фикса 'confetti' даёт яркий разноцветный салют.
2026-06-09 12:53:14 +03:00
min
660d528ad5 feat(lua): __rbxl_show_text + __rbxl_spawn_particles (паритет game.ui/scene)
JS-версия использует game.ui.showText (красивая центрированная плашка
без рамки через RbxlHudOverlay) и game.scene.spawnParticles('confetti').
Lua-версия пыталась рисовать ScreenGui+TextLabel через offset в UDim2,
но gui-shim неправильно интерпретировал offset → плашка прижата влево.
Также конфетти отсутствовали.

Решение — хелперы прямого вызова HUD/particle-systems как в JS:
- __rbxl_show_text(text, duration, color?) → shim шлёт ui.showText →
  GameRuntime → _rbxlHud.showMessage + setTimeout hideMessage
- __rbxl_spawn_particles(kind, x, y, z, duration, count) → 'scene.particles'
- __rbxl_player_pos() → возвращает текущую позицию игрока

Игра 2 переписана: использует __rbxl_show_text для подсказок 'Допрыгай',
'Упал!', 'Победа!' и __rbxl_spawn_particles('confetti', ...) на финише.
2026-06-09 12:47:44 +03:00
min
ad26395e10 fix(lua): убран двойной part.Touched.Fire — счёт +1 за монетку
Симптом: счётчик показывает 10/8 при 5 собранных монетах.

Корень: BabylonScene на каждое касание шлёт И routeEvent('touch') И
routeGlobalEvent('playerTouch'). Lua-shim фейерил part.Touched.Fire(hrp)
в обоих обработчиках (fireTargetEvent + fireGlobalEvent). Handler
монетки срабатывал 2 раза → 2 Fire CoinCollected → score +2.

Фикс: в fireGlobalEvent для playerTouch УБРАЛИ part.Touched.Fire.
Остался только humanoid.Touched.Fire(part) — это уникальный для
playerTouch сценарий (когда юзер слушает humanoid.Touched).
2026-06-09 10:45:06 +03:00
min
598b91bd9e fix(lua): inst.Parent = X авто-добавляет в parent.Children
Корень бага: g1_main делал:
  ev = Instance.new('BindableEvent')
  ev.Parent = ReplicatedStorage
Но Proxy 'set' просто писал t['Parent']=value, НЕ добавляя ev в
ReplicatedStorage.Children. Поэтому скрипт монетки делал:
  ev = ReplicatedStorage:FindFirstChild('CoinCollected')  -- nil!
  if ev then ev:Fire() end  -- false, ничего не происходит
HUD/звук не обновлялись.

Фикс: Proxy.set теперь:
1) удаляет себя из старого parent.Children
2) пушит в новый parent.Children
3) фейерит ChildRemoved/ChildAdded/AncestryChanged

Это паритет с Roblox Instance.Parent setter.
2026-06-09 10:41:49 +03:00
min
701125d17b fix(lua): task.delay/spawn/defer не терялись после prelude
Корень бага HUD/звука в игре 'Собери монетки':
prelude перезаписывал task = { wait = rbx_wait } если type(task)~='table'.
task — JS-объект (userdata) → ветка else → методы delay/spawn/defer
исчезали.

Скрипт g1_main падал на:
  task.delay(2, function() hintGui:Destroy() end)
с 'attempt to call a nil value (field delay)'.

Из-за этого Connect на CoinCollected ниже не выполнялся → HUD не
обновлялся, звук не играл.

Фикс: сохраняем существующие методы task.delay/spawn/defer из shim,
добавляем wait.
2026-06-09 10:37:18 +03:00
min
36b41616b0 fix(lua): прямой вызов h.fn() в JS вместо передачи в Lua
Не передаём JS-обёртку Lua-функции обратно в Lua через
luaDrainHandler — это создавало wasmoon Promise-detection crash
на null.then.

Прямой h.fn(...args) → wasmoon вернёт Promise → .catch ловим.
2026-06-09 10:28:32 +03:00
min
0cbd1d7a82 diag(lua): Touched.Fire() без аргумента — изолируем виновника
Гипотеза: hrp передача в Lua-функцию через wasmoon крашит null.then.
Если без hrp handler отработает — значит точно hrp.
2026-06-09 10:26:35 +03:00
min
c6ba06eea6 debug(lua): log start/end/error в handler 2026-06-09 10:22:06 +03:00
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
3f9f7cd6c7 fix(editor): убрана модалка подтверждения при смене языка скрипта
При переключении JS↔Lua показывалась модалка "Сменить язык?" даже
если код не пустой. Юзер: не нужна, переключай сразу.

Сейчас: код остаётся как есть, меняется только подсветка синтаксиса
Monaco. Если код был пустой шаблон — подставляется новый шаблон языка.
2026-06-09 01:55:54 +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