diff --git a/rbxl-importer/src/__pycache__/rbxl_types.cpython-314.pyc b/rbxl-importer/src/__pycache__/rbxl_types.cpython-314.pyc index 44b6b88..f6db794 100644 Binary files a/rbxl-importer/src/__pycache__/rbxl_types.cpython-314.pyc and b/rbxl-importer/src/__pycache__/rbxl_types.cpython-314.pyc differ diff --git a/rbxl-importer/src/rbxl_types.py b/rbxl-importer/src/rbxl_types.py index 4113753..a689b12 100644 --- a/rbxl-importer/src/rbxl_types.py +++ b/rbxl-importer/src/rbxl_types.py @@ -113,18 +113,28 @@ class CFrame: matrix: tuple # (r00, r01, r02, r10, r11, r12, r20, r21, r22) def to_euler_xyz(self) -> tuple: - """Конверт 3x3 rotation matrix в Euler XYZ (radians). + """Конверт 3x3 rotation matrix в Euler YXZ (Babylon convention). - Использует стандартную intrinsic XYZ rotation extraction: - Rx = atan2(r21, r22) - Ry = atan2(-r20, sqrt(r21² + r22²)) - Rz = atan2(r10, r00) + Babylon mesh.rotation = Vector3(rx, ry, rz) применяется в порядке YXZ + (rotate Y first, then X, then Z). Чтобы извлечь Euler из матрицы под + этот convention, используем формулу YXZ-extraction: + Rx = asin(-r12) + Ry = atan2(r02, r22) + Rz = atan2(r10, r11) + (имя метода to_euler_xyz сохраняем для совместимости вызовов.) """ import math r00, r01, r02, r10, r11, r12, r20, r21, r22 = self.matrix - rx = math.atan2(r21, r22) - ry = math.atan2(-r20, math.sqrt(r21*r21 + r22*r22)) - rz = math.atan2(r10, r00) + # Edge case: r12 близко к ±1 (gimbal lock на X = ±90°) + clamped = max(-1.0, min(1.0, -r12)) + rx = math.asin(clamped) + if abs(clamped) > 0.99999: + # Gimbal lock — z = 0, y = atan2(-r20, r00) + ry = math.atan2(-r20, r00) + rz = 0.0 + else: + ry = math.atan2(r02, r22) + rz = math.atan2(r10, r11) return (rx, ry, rz) diff --git a/src/editor/engine/lua/LuaSharedSandbox.js b/src/editor/engine/lua/LuaSharedSandbox.js index 6446408..4906272 100644 --- a/src/editor/engine/lua/LuaSharedSandbox.js +++ b/src/editor/engine/lua/LuaSharedSandbox.js @@ -183,6 +183,15 @@ export class LuaSharedSandbox { Source = nil, } local co = coroutine.create(function() + -- WATCHDOG: каждые 50000 инструкций — yield 1 кадр. + -- Защищает от tight-loop типа: + -- while not parent:FindFirstChild(name) do + -- parent.ChildAdded:wait() + -- end + -- где наш stub :wait() возвращает сразу. + debug.sethook(function() + coroutine.yield(0.016) + end, "", 50000) -- pcall защищает от runtime-ошибок которые иначе крашат -- coroutine и могут повредить WASM-стейт. Возвраты -- handler'а намеренно поглощаются. diff --git a/src/editor/engine/lua/RobloxShim.js b/src/editor/engine/lua/RobloxShim.js index 5bfd169..59db590 100644 --- a/src/editor/engine/lua/RobloxShim.js +++ b/src/editor/engine/lua/RobloxShim.js @@ -58,7 +58,10 @@ function makeSignal() { } }; sig.fire = sig.Fire; - sig.Wait = () => undefined; + // Wait() возвращает -1 как маркер "yield 1 кадр" — наш Lua-prelude + // оборачивает все Signal:Wait через __rbxl_signal_wait который при + // получении -1 делает rbx_wait(0.05) (yield в coroutine). + sig.Wait = () => -1; sig.wait = sig.Wait; return sig; } @@ -1385,6 +1388,12 @@ export function registerRobloxShim(lua, opts) { local ret = coroutine.yield(sec) return ret or sec end + + -- Глобальный безопасный yield для любых stub-сигналов / любых + -- "ждунов". Используется в Lua-обёртках вокруг WaitForChild и т.п. + function __rbxl_yield_frame() + coroutine.yield(0.05) + end if type(task) == 'table' then task.wait = rbx_wait else @@ -1413,6 +1422,10 @@ export function registerRobloxShim(lua, opts) { -- что приводит к wasmoon promise-detection crash). pcall возвращает -- (ok, ret1, ret2, ...) — мы их не используем. local co = coroutine.create(function() + -- Тот же watchdog что и в _startSingleScript + debug.sethook(function() + coroutine.yield(0.016) + end, "", 50000) pcall(fn, a1, a2, a3, a4) end) __rbxl_register_coroutine(handlerId, co)