В 3-м лице курсор свободный. _handlePlayClick ВСЕГДА пикал центром
экрана через _pickFromCenter — независимо от того где находится курсор.
Поэтому клик мышкой по мишени не срабатывал — пик шёл из центра.
Фикс: проверяем document.pointerLockElement:
- locked (1-е лицо) → _pickFromCenter (прицел всегда в центре)
- !locked (3-е лицо) → scene.pick(clickX, clickY) по реальной позиции
курсора при клике.
Убрал debug-логи.
Юзер: подсказка в Lua была слева от центра, белая. В JS — точно по
центру внизу, жёлтая.
Корень: я использовал BillboardGui+TextLabel с явной Position, но
GUI-shim позиционирует label некорректно (UDim2 offset плохо
интерпретируется → плашка плыла).
Фикс: используем тот же механизм что JS — game.ui.set (HUD через
React). Добавил хелпер __rbxl_hud_set(id, text, x, y, color, size)
шлющий 'ui.set' cmd, GameRuntime пробрасывает в _onHud → GameHud.jsx
рендерит точно как для JS-скриптов.
В g13_counter/g13_door: при near=true → hud_set с (50, 75, #ffe44a, 20)
(центр по X, ниже центра по Y, жёлтый — точно как JS interact hint).
При выходе из зоны → hud_set(id, nil) убирает.
Куб с физикой отталкивается от игрока (DynamicsManager push) и успевает
отскочить до следующего кадра. Строгий AABB ловил только при защемлении
в углу. Расширение SLACK=1.2 единицы ловит 'почти-контакт' — куб
собирается при подходе на ~1 единицу.
DynamicsManager._applyToMesh обновляет pm.instances[id].x/y/z, но Lua-shim
кэширует Position в part._state.Position на момент создания. AABB-check
видел кубы навечно в небе → касание не ловилось.
Фикс: GameRuntime.tick собирает позиции всех спавненных динамических
примитивов и шлёт в shim через api.updateSpawnedPos(id, x, y, z).
Shim обновляет part._state.Position у соответствующего partById.
BabylonScene._detectTouchEvents работает только для скриптов с явным
target. Спавненные runtime через __rbxl_spawn_part (падающие кубы)
не имеют target — Babylon их не проверяет, Touched молчит.
Решение: shim.fireHeartbeat теперь сам делает AABB игрок↔part для
всех part id >= 800000 (наш range спавненных). При пересечении
фейерит Touched.Fire(hrp); при выходе — TouchEnded.
Корни:
1. task.spawn(function() task.wait() end) → 'attempt to yield across
a C-call boundary' — task.spawn в shim синхронно зовёт fn из JS.
Замена: накопление dt в RunService.Heartbeat → spawnCube() каждые 1.5с.
2. Instance.new('Part', workspace) с последующим .Anchored=false
создавал anchored=true примитив + патч → primitiveManager не пересоздавал
rigid body, куб не падал. Новый хелпер __rbxl_spawn_part(opts) шлёт
sceneCreate с правильным anchored СРАЗУ — куб создаётся динамическим
и падает.
Проблема: 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.
Корень: __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.
Не передаём 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 — где-то теряется.
ИНФРАСТРУКТУРА:
- 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), будут доработаны итеративно.
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 запущен'.
Прошлый коммит 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.
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, искры из персонажа, плавная смена дня/ночи.
Поддержка скриптов проекта 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-движок).
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, остальные продолжают работать.
Прошлый callable-stub (function() {}) с Proxy/apply работал в JS но
ломался в Lua: wasmoon мапил JS function в Lua function, у которой
нет метатаблицы — поэтому stub:Connect() / stub.SomeField падало с
'attempt to index a function value'.
Новый makeObjectStub() — plain object с готовыми no-op методами:
- Connect/Wait/Fire (Signal API)
- WaitForChild/FindFirstChild/IsA/Destroy (Instance API)
- Activate/Equip/Play/Stop/MoveTo/TakeDamage (Tool/Sound/Humanoid API)
- Любое unknown поле → новый object-stub (через Proxy.get)
Это снимает 99% оставшихся 'attempt to index a function value'.
Импортированные скрипты делают obj:Method(arg) где obj — stub. Раньше
stub был просто Folder, его как функцию вызвать нельзя → self2 is not
a function массово.
Фикс: makeCallableStub() — Proxy на function(){} который:
- вызывается как функция (apply trap) → возвращает себя;
- имеет .Connect/.Disconnect/.Wait/.Fire → ведёт себя как сигнал;
- любое .UnknownField → возвращает другой callable-stub;
- .then/.catch/.constructor → undefined (wasmoon не путается).
Этим закрывается основная масса остаточных падений в туториал-скриптах
с цепочками вроде Tool.Handle:WaitForChild('X'):Connect(...) где Handle
у нас отсутствует.
1. require(): в Roblox загружает ModuleScript. У нас модулей нет —
возвращаем mod как есть (если объект) или undefined.
2. Proxy улучшения:
- Object.hasOwnProperty + 'in' checks: методы (WaitForChild,
FindFirstChild и т.д.) точно не перехватываются;
- Symbol-ключи всегда undefined;
- System keys (then, catch, toString, constructor) → undefined
чтобы wasmoon не пытался обращаться как с Promise/класс;
- has() возвращает true для всех строковых ключей (избавляет от
падений на 'if obj.SomeField then ...').