Было: x:-4+c*2 → плитки на x=[-4,-2,0,2,4,6], правые края до x=6.9.
Платформа grass [-6,5] (центры [-5.5, 5.5]). Плитка x=6 свешивалась.
Стало: x:-5+c*2 → плитки на x=[-5,-3,-1,1,3,5], края [-5.9, 5.9].
Чётко на платформе.
То же по Z.
Плюс лог 'ЯЗЫК СКРИПТОВ: LUA/JS' в GameRuntime — чтобы было видно
сразу что именно запущено.
Heartbeat проверяет расстояние от игрока до кнопки. Управляем
видимостью через label.Visible (BillboardGui в shim не управляет
видимостью children, label.Visible работает напрямую через gui.update).
Проблема: door.Position + Vector3.new(0, 6, 0) возвращало nil потому
что wasmoon не создаёт метаметоды (__add) для JS-классов автоматически.
Фикс:
1. В скрипте кнопки явно считаем Vector3.new(dp.X, dp.Y+6, dp.Z) без +.
2. В prelude добавил метатаблицу Vector3 для будущего использования
с операторами +, -, *, /, унарный -, ==, tostring. Работает между
двумя Vector3-таблицами, созданными через Vector3.new в Lua.
Откат изменения 'наступи на кнопку'. JS-версия использует
game.self.onInteract — нажатие E. Lua-версия должна вести себя так же.
Подход:
- Подсказка [E] Открыть дверь висит над кнопкой постоянно (пока не нажата)
- UserInputService.InputBegan ловит E
- Расстояние до кнопки проверяется ТОЛЬКО в момент нажатия E
(не каждый кадр — это избегает багa с зависанием позиции после Touched)
- Если близко (≤4) → дверь поднимается через TweenService
Проблема:
1. Heartbeat-зов __rbxl_player_x() возвращал константу после первого
касания кнопки (возможно lua-coroutine / state-issue в Heartbeat).
2. Дверь не открывалась — InputBegan E видимо не доходит или фильтр
inRange всегда true.
Решение: упростил — кнопка реагирует на Touched (как кнопка-педаль),
без E и без проверки расстояния. Это и понятнее для урока.
Текст подсказки изменён на 'Наступи на красную кнопку'.
Дверь поднимается через TweenService при касании кнопки.
player.body.position не существует — позиция в PlayerController._pos.
Из-за этого realPos оставался null, api._realPlayerPos не обновлялся,
конфетти вылетали из начальной hrp._position (0, 5, 0).
Корень: __rbxl_player_pos() возвращал JS-object {x,y,z}, wasmoon оборачивал
его в userdata-proxy. В Lua pos.x давал NaN. Конфетти спавнились с NaN.
Фикс: 3 отдельные функции __rbxl_player_x/y/z возвращающие числа.
В скрипте игры 2 используем их напрямую.
Проблема: __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-логи.
BabylonScene._spawnParticleEffect читает payload.type ('confetti') и
payload.position {x,y,z}. Я слал {kind, pos} — type=undefined →
fallback на 'sparks' → бледные одиночные искорки вместо салюта.
После фикса 'confetti' даёт яркий разноцветный салют.
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', ...) на финише.
Симптом: счётчик показывает 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).
Корень бага: 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.
Корень бага 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.
Было: только print в консоль, leaderstats не виден на экране.
Стало паритет с JS-версией:
- ScreenGui+TextLabel счётчик 'Монеты: N / 8' в правом углу
- ScreenGui подсказка 'Собери все монетки!' на 2 сек по центру
- Sound 'coin' при сборе (через Sound:Play, SoundId='coin')
- Sound 'win' + победный TextLabel когда score >= TOTAL
Не передаём JS-обёртку Lua-функции обратно в Lua через
luaDrainHandler — это создавало wasmoon Promise-detection crash
на null.then.
Прямой h.fn(...args) → wasmoon вернёт Promise → .catch ловим.
Корень: 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 не сработает.
Симптом: при 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).
Чтобы увидеть запускается ли handler из очереди и нет ли pcall error.
Сейчас [shim fireTargetEvent] показывает connections=1 но нигде нет
выхлопа от Touched handler — где-то теряется.
Проблема: при касании монетки [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.
Lua не поддерживает кириллицу в именах identifier'ов (только в строках).
stats.Монеты вызывал parser error:
<name> expected near '<\208>' (0xD0 = первый байт UTF-8 кириллицы)
Заменено на безопасный синтаксис:
stats.Монеты → stats['Монеты']
stats.Ключ → stats['Ключ']
stats.Клики → stats['Клики']
Затронуты игры: collect-coins (1), trader (13), key-chest (17),
shop (29), clicker (46). Все 9 случаев исправлены.
Теперь монетки в игре 1 должны нормально увеличивать счётчик.
ИНФРАСТРУКТУРА:
- 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), будут доработаны итеративно.
Раньше в статье "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 слот).