From dbfd214f429da074cd0483d07c63b24529725ecc Mon Sep 17 00:00:00 2001 From: min Date: Mon, 8 Jun 2026 19:16:39 +0300 Subject: [PATCH] =?UTF-8?q?fix(import):=20YXZ=20Euler=20+=20watchdog=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20tight-loop=20=D0=B7=D0=B0=D1=89=D0=B8?= =?UTF-8?q?=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../__pycache__/rbxl_types.cpython-314.pyc | Bin 29959 -> 30332 bytes rbxl-importer/src/rbxl_types.py | 26 ++++++++++++------ src/editor/engine/lua/LuaSharedSandbox.js | 9 ++++++ src/editor/engine/lua/RobloxShim.js | 15 +++++++++- 4 files changed, 41 insertions(+), 9 deletions(-) diff --git a/rbxl-importer/src/__pycache__/rbxl_types.cpython-314.pyc b/rbxl-importer/src/__pycache__/rbxl_types.cpython-314.pyc index 44b6b88d8b7fb8df532f61748dacd956a8795f33..f6db7945a4fb43e679a7f13d04a8804356afa046 100644 GIT binary patch delta 3248 zcmZ`*3s98T72dncLw03hfdyAV{sn>k0eLKHt;P~yAtA~mt2~P(8(bB6?B-{QKp!BM zNU9FDCr&VC0&ynkOq!{hl}UUcw$oHwb>{zz>EO0?Y-^JV?M!IY=3yQ^=Pt_zr`=({ z|K4-H@7#0Gf6rYG%(5^3#q?SFNUaJ#le6i2uGXH`muW){T~bS~cSP%05}6R1_-_@$ zF(*FY%a|l}JcGp8JZn=ZiRfbwZ3_2~aICHk7gcN2{(0c(*447=2rz7k&DE`8exG}3 zlP;jDU9j3U>t9f*b|k1&i7M1YlBR!&sH#;j#EP_sxb+n&~E-V6fDJ#r+eF7okEMN z^I2!7r`6Rd=4iD6YlpLIcMb{2A#4(Aoh=@hRA7;MvIVIZzkQ-0PbmZPJMsm2LY`Dc zaG^nEpE4rIZ=pxNh|7-1XXJD8gn;X5Ej*Z0Xb^Vxw@O`}Y{9eJ*(ub|KO4myLH@Zi zh)JJT4hk6dZOr{V20fr05=y(f!Wk2P%+S%ig94)ld4O#&|_8#1^qpP*kvRcZwihf%! z**uO;t3}GQVgalHM_9j}~@ z$r=uyi8PEfJl}9+?{sAHu=>wB{qWv{kBnyA(3x)=j4v+#>GIL7qqgzXX+!#O@r)tv z`Qm`NWZIBMCO^qPgZGUck(IclsFq?W&vJ0&3osC0$C}}KdMZ4KUm1oyK8GEioUnl% zhVi@<_(8%693?NanVh5{I81}p1S#AEW^*P3xNS1Sm1T|0iDAuPGsU8<-efbi5s3h3 z5Y)AC-7(btiXDR!%S%lzA`l!~%ye<)7|*9-#V8wt_gC0V-9#c7Tg>i8wh5XN4eT}O zOnh7|ZGq%yllQ$u4MWS#q(0*hN$?|reu4pleN+N%9}Nj!gQF|gu|9BQXR?#_U%PwyFMS>%Jkl^ z3VImR!``)(^Y2li7zBE_vsPdVgsz*6{xuml5%3eIz~s(kmf-z(-R_l^Eok0W=D)RX zyB_5)vr-I^`02Yrn)e9)2!l06Y!H4^v!&mO2A{UtURB*(V=J$)@*B#xgd6Nd4ZiT5 z?JkF>!0HQgxZTdq9sFM6_vXB+tf-`PD?e?%2(Bus$2lDsUlgAg+sjKfmsD-3vhy3l zr{h*`D3D;Jc8r~a+`24w2)3Y4r}@!N4u!`@N4`?H0B^`{Nu+SHk3bX;1Z$*B-}tWF+y69 z2O~dhi&+RDQOAo0DGmO!CwYAnWxk!@djxiZ8iIO)tpv3M4FuZ=>IfR4qAgkb2@MHy zz0bB)Ggj}t+-_AzXHfDM0+ApSB3y;W&xlJ2<-*7nYYQ>+J zQ1`dkJ=o+fTXmZhef>0PP>zIAJgzI!KZN3o!?JwD1{J&5rh*g zA&4M|C1NhY8iFzc>I~iu{v+Q_(>{VB0{#=Wa9D!}lOIFg4!j5W?S|J?3QG75#LpYQ z9C&xr$>7zJK7&`ENzyjq9RE~Z>%CW=F*9Q_!~a7N?rKzO-JF`HHs<|O(yIRp7~8tV delta 2815 zcmZ{m4@}e77RP%_K`j;#6(~~pLC2pT|5ghA8K^wukCI`jMJQl5UxgYvOTpU`i*Cco z-llHB%@ZANOJ+8gnI%TFn{$8IoMtn|MA|1W+xxwj1^36Nb9rxWZ+}ddZ1#ORV@rotzI->KT~uT5KDk({4Go7M3w2g%UTLWv#7$%4jd*W>cZ$+s^$c zIDc9yK+XBnvXJeZlJOED;wF=~;pfNO?z^Zp}-=>t`%Nx_=*C?*EhW$awMOIZ2Ch({2QA_g!U+8x+LyZ{<$8QTG& zqI{Sz#KOC#CBdhpU5vd7bFwPfUr9|lLA(mRSs5Z@n3I$AZ2!*M#-0#4$pp7CRoEUk zTJ`%eIN~HWrgO5N{LYZyefNyBGZ2?;)eoTvab~u9h}5H`zL4F|hG>=tbJEx-?8>Rs zA4WN1bhh7Nd=_>dPMNa7n47E#h+#|&{F+q&1Gy>e85qvB3c<}>8v}a27cm0jJh<~R z*^4yuC7St@yv@2{F(RwEhE%kQPI(Et1iR%*{ZTX`E@5N(S&veTD-`3N{5Ix?NAoM$ z6?i|tMt=;Yh%2*wj?v1;Y317r4pCmq*f=aJ%w~JxoN>2i`Ln4k%y z;@2j3`ev~qX+pUCacpf6hY%A`ey@QADHGQz6UXkg#T>-p$RZ7dt$&o}zAfhdKf4W@(g*YxFh1foHl`=K zKoi};CykG5?8QUU>8Y;c&TbccR{A&HJ3=mM0IvmJM>X(lS(^He3Qm^gM>Z$+GL9jH z^-ftDyDUP=P`T91G(o;Jz?cSZEwfDS_;Y(I>(>SO3-Jq-VC zO{t@o<|qbEAU{j<`ZcvSTa{&PEfx1JbF27G<;tR#?Z?hwCqrE8)mL1&CwKr;@PT1wBo4<+=$2LKZinMF*HmtqdKrSfz-GvM& zljrd%_vy%Y+Y+Y&aD2<<1>6V^c`{bC;mX?)n-Eq+6QTvN0kIKbN7xX}h*tQ}lcD_q zn~3b-*B&clmSBa~tWLTu&xkuv%}WwAI7c)h1~CVQc*+2lI$gSSmr`}n zWyS|_fUciNp<;KcPN^!X@~PbEh@vxxYAsS&^bXUzi91bC&ws)wnAXTKnyyL~g*10% scTZ=Z$F-Wb3!(U`e9MC?eVJ_0VkZ8{5cobyrH=hpja^fa`?c!d0HE*!`Tzg` 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)