24 KiB
Lua API — журнал изменений
Файл фиксирует что было добавлено в Lua-runtime при работе с реальными Roblox-играми. Цель — потом продублировать тот же API для JS-движка (на будущее, сейчас работаем только с Lua).
Формат: дата + что и почему + куда добавлено + надо ли портировать в JS.
2026-06-08 — Итерация 4: Spawn-fix + философия импорта
Контекст: МИН подтвердил после ROBLOX Battle: 100% покрытие Lua-скриптов из Roblox не получится (наш wasmoon не yield'ит из JS C-call boundary, старый Roblox-pattern WaitForChild через ChildAdded:wait тривиально вешает страницу). Цель импорта сменилась: показать геометрию и базовые интеракции, а не полную скриптовую логику.
Spawn fix (карта проекта 2853)
После переимпорта одной из карт игрок появлялся внутри Anchored геометрии (стена/пол), не мог двигаться. Причина: SpawnLocation в старых .rbxl ставится впритык к плите (Y+0.5), наш отступ +1.5 не спасал от толстых Floor'ов 2-3 units high. Anchored=True (наш force-fix) не давал выпрыгнуть.
Фиксы в converter.py:
- SpawnLocation +5 вместо +1.5. Если spawn внутри толстого пола — гравитация уронит обратно за 1 кадр, не страшно. Если выше — отлично.
- Auto-fallback если SpawnLocation в карте НЕ был (или дефолт остался
(0, 2, 0)):
Игрок появляется над самой высокой Part'ой → падает на крышу.max_top = max(p['y'] + p['sy']/2 for p in primitives) scene['spawnPoint'] = {x: 0, y: max_top + 5, z: 0}
Философия импорта (зафиксировано как принцип)
Цель импорта .rbxl = показать геометрию и сцену, а не воспроизвести скриптовое поведение. Что работает (важно):
- ✅ Все примитивы (Part/Wedge/CornerWedge/Truss/Union/MeshPart)
- ✅ Цвета через BrickColor (расширенная палитра 120 цветов)
- ✅ Anchored=True для всех (карта не рассыпается)
- ✅ SpawnLocation с правильным Y (игрок не в стене)
- ✅ Корректный CFrame YXZ (мостики/wedge'и стоят правильно)
- ✅ Скайбокс, освещение, экспозиция/контраст через слайдеры
- ✅ Простые Touched-скрипты (Bouncer, BattleArmor, KillBrick)
- ✅ Tools.Equipped/Activated handlers (часть оружия)
Что НЕ воспроизводится (принимаем):
- ❌ Сложные RoundScript / GameClock / Spawner / KillFeed-логика
- ❌ WaitForChild через while+:wait() паттерны (regex-фильтр пропускает)
- ❌ Регенерация построек (Regenerate*) — не нужна т.к. Anchored
- ❌ LeaderboardV3 с DataStore (пропускается)
- ❌ Сетевые RemoteEvent/RemoteFunction (single-player только)
Когда снова работать со скриптами
Если попадётся новая карта (2015+) — WaitForChild встроен в API,
наш regex-фильтр не сработает. Скрипты пройдут больше и будут работать
лучше. Старые карты (2007-2010) принципиально ограничены.
Что НЕ делать
- Не пытаться "ещё раз" решить yield-across-C-boundary через debug.sethook или pcall-трюки. Проверено — не работает с wasmoon.
- Не переписывать wasmoon — это месяцы работы.
- Не сужать regex-фильтр в надежде запустить ещё пару скриптов — лучше пусть пропустится лишний, чем висит страница.
Что делать дальше
- Идти по .rbxl из Desktop/RBLX/ как пользователь.
- На каждой карте проверять: геометрия загрузилась? игрок ходит? видна?
- Если виснет — добавлять regex-паттерн в фильтр.
- Если игрок застрял — улучшать spawn-fallback.
- Если падают конкретные API — реализовывать в shim (как Mouse.Icon, BodyVelocity-bouncer, leaderstats).
В JS
✅ Все фиксы spawn + философия общая для студии и плеера.
2026-06-08 — Итерация 3: ROBLOX Battle (arch1_ROBLOX_Battle_v2.rbxl, проект 2851)
Контекст: PvP-арена 2009 в XML, 1677 примитивов, 66 скриптов, 4 команды (TeamBeacon), 5 оружий, 12 батутов, KillFeed, раунды.
Реализовано 11 механик из 14
- Teams — game.Teams сервис + Team-инстансы, эвристика TeamBeacon-Model в converter.py → автоматически создаёт 4 команды по имени.
- Leaderstats UI — IntValue.Value реактивно через Object.defineProperty, при Parent=leaderstats шлёт leaderstatSet → существующий LeaderstatsManager.
- BindableFunction/RemoteFunction + Message/Hint классы с реактивным Text.
- KillFeed UI + creator-tag tracking в Humanoid.TakeDamage. DOM-overlay.
- SpawnLocation.TeamColor → scene.team_spawns[].
- Tool/Model:Clone() + :MakeJoints/:BreakJoints/:Remove no-op.
- Creator-tag: ObjectValue.Name='creator' проверяется на Health=0.
- RegenerationScript — no-op skip по имени (Anchored=True держит).
- BattleArmor — реактивный Humanoid.MaxHealth/Health/WalkSpeed/JumpPower.
- WinGui/FireButton через GuiManager.
- AdminConsole — no-op.
- Bouncer — BodyVelocity.Y > 10 + Parent=Torso → playerSet jumpVelocity.
- Mouse.Icon → CSS cursor через canvas.style.cursor.
Также добавлены: tick/time/delay/spawn/LoadLibrary legacy globals, SpecialMesh/BlockMesh/CylinderMesh/FileMesh Instance.new стабы.
Новый модуль RbxlHudOverlay.js
DOM-оверлей поверх canvas с KillFeed (правый верх, fade 5с) + Message (центр верх) + WinGui (центр). Lazy-создаётся.
Tight-loop защита (КРИТИЧНО)
Roblox 2009 паттерн:
while not parent:FindFirstChild(name) do parent.ChildAdded:wait() end
Наш Signal:wait() возвращает синхронно — цикл бесконечный, страница виснет. Не можем yield из JS-функции через wasmoon C-call boundary.
Перепробовали:
- debug.sethook(yield, 'i', N) — внутри C-call падает с
yield across C-call. - pcall(coroutine.yield) — ошибка ловится, счётчик не сбрасывается, вис.
Финал: regex-фильтр в GameRuntime.js пропускает скрипты с этими паттернами. Из 66 скриптов 37 пропущены, 29 работают. Жертвы: RoundScript, GameClock, Spawner, KillFeed, LeaderboardV3, оружие Launcher/Sword/Slingshot/Cannon.
CFrame YXZ Euler
Переписал to_euler_xyz в rbxl_types.py под Babylon YXZ convention:
rx=asin(-r12), ry=atan2(r02,r22), rz=atan2(r10,r11) + gimbal-lock guard.
Раньше извлекал XYZ-Euler, Babylon применял как YXZ — мостики
поворачивались криво.
Persistence настроек света
BabylonScene.serialize/loadFromState сохраняют scene.lighting: sunIntensity, hemiIntensity, sceneAmbient, exposure, contrast, saturation.
Известные баги
memory access out of bounds(1 раз) — WASM-crash одного скрипта.Cannot read properties of null ('then')— wasmoon promise-detection, скрипт init крашится но не блокирует.- 0 teams при загрузке старого проекта — нужен переимпорт.
В JS
✅ Всё: Teams формат общий, KillFeed/Message HUD общий для студии+плеера.
2026-06-08 — Итерация 2: Crossroads (arch1_Original_Crossroads.rbxl, проект 2827)
Контекст: Классическая Roblox-карта 2009 года для PvP, XML-формат .rbxl (старее бинарного). 877 instances, 777 Part, 83 Model. Состоит из 4 зон: крепость (Castle), дом (House Platform), деревья, дорожки крест-накрест. 2 скрипта: «Regenerate Playground» и «Regenerate Castle» — периодически удаляют и восстанавливают постройки (для PvP).
Главное: XML-парсер для .rbxl
rbxl-importer/src/rbxl_xml_parser.py (новый файл, ~330 строк):
is_xml_rbxl(blob)— детект по<robloxбез!(binary имеет magic<roblox!).parse_xml(blob) → RobloxModel— то же чтоparse()из binary parser'а, совместимый формат, чтобы converter работал без изменений.- Поддержанные property-теги:
string,bool,int,int64,float,double,token,Vector3,Vector2,CoordinateFrame,Color3,Color3uint8,BrickColor,Ref,BinaryString,UDim,UDim2,Rect2D,OptionalCoordinateFrame,PhysicalProperties,NumberRange,ProtectedString,Content. - Алиасы PascalCase: старые карты использовали
name/size/shapeс маленькой буквы — добавлены как PascalCase для converter'а. <int name="BrickColor">N</int>— особый случай: в старом XML цвет лежит как int с именемBrickColor, заворачиваем вBrickColor(code=N).
В app.py добавлен автодетект формата:
is_binary = blob.lstrip().startswith(b'<roblox!')
is_xml = blob.lstrip().startswith(b'<roblox') and not is_binary
if is_xml:
model = parse_xml(blob)
else:
model = parse(blob)
Расширенная BrickColor палитра (converter.py)
Старая палитра: ~50 цветов. Новая: ~120 цветов. Главные добавления:
- 151 (Earth green) — основная трава Crossroads (
#7c9b53). Без неё пол получал дефолтный#ccccccи выглядел белым на 344 примитива. - 18, 26, 115-148, 168-301, 1021-1032 — заполнили дыры.
После: #cccccc дефолт упал с 344 → 68 на Crossroads (большинство цветов
теперь правильные).
Anchored = True для всех импортированных Part
В _convert_part/_convert_wedge/_convert_cornerwedge/_convert_truss/
_convert_meshpart/_convert_union принудительно 'anchored': True.
Причина: Roblox-карты держатся на Welds (склейки) или BasePart-default
Anchored=true (Crossroads). У нас Welds — заглушки, физика 700+ unanchored
Part'ов = карта рассыпается за 1 секунду (Unanchored bodies: 767).
После фикса: Unanchored bodies: 0, всё стоит на месте.
Уважение поля enabled из метадаты
Уже было в Итерации 1, но напомню: скрипты с Disabled=True в Roblox
не запускаются. Парсер метадаты parseRobloxLuaMeta() смотрит вторую
строку packed-кода (JSON с enabled), если false — пропускаем.
Визуальная настройка света
Главные находки:
- mat.ambientColor=(1,1,1) обязательно — иначе
scene.ambientColor(«Заливка теней» слайдер) не влияет на материалы. - mat.ambientColor=(цвет_от_diffuse) даёт пересвет — не делать.
- scene.imageProcessingConfiguration есть готовый в Babylon — даёт exposure/contrast/saturation бесплатно.
В BabylonScene.setLightingProps(patch) добавлено:
patch.sceneAmbient(0..1) →scene.ambientColor(заливка теней)patch.exposure(0.3..2) →ipc.exposurepatch.contrast(0.5..2) →ipc.contrastpatch.saturation(0..2) →ipc.colorCurves.globalSaturation
В SelectionManager.selectLighting() добавлены поля для чтения текущих значений.
В InspectorPanel.jsx добавлены 4 новых слайдера в «Свет и атмосфера»:
- Заливка теней (в блоке «Окружающий свет»)
- Экспозиция / Контраст / Насыщенность (новый блок «Цветокоррекция»)
Persistence настроек света
BabylonScene.serialize() теперь включает scene.lighting:
lighting: {
sunIntensity, hemiIntensity, sceneAmbient,
exposure, contrast, saturation,
}
BabylonScene.loadFromState() применяет эти параметры через setLightingProps().
Деплой rbxl-importer
ВАЖНО: rbxl-importer на VM 130 деплоится напрямую через SSH, не через CI/CD:
KEY="/c/Users/min/.ssh/id_ed25519"
scp -P 2280 -i "$KEY" rbxl-importer/src/FILE.py root@85.175.7.40:/tmp/
ssh -p 2280 -i "$KEY" root@85.175.7.40 \
"scp -P 22 -i /root/.ssh/id_ed25519 /tmp/FILE.py min@192.168.1.130:/tmp/ && \
ssh -p 22 -i /root/.ssh/id_ed25519 min@192.168.1.130 'sudo mv /tmp/FILE.py /opt/rbxl-importer/src/ && sudo systemctl restart rbxl-importer'"
S1 PVE root доступен по SSH-ключу ~/.ssh/id_ed25519 (без пароля).
См. vm130-direct-deploy в memory.
Что НЕ доделано (известные баги Crossroads)
-
2 скрипта падают
self2 is not a function:Regenerate PlaygroundиRegenerate Castle.- Используют
model:clone()гдеmodel = game.Workspace.Playground— наш stub-Folder для Playground не имеет:clone()метода. - Также
Instance.new("Message")— класс не реализован. - Не критично: Anchored=True держит постройки, регенерация не нужна.
-
Цвета всё ещё чуть-чуть не такие как в Roblox:
- Юзер крутит слайдеры sun/hemi/ambient/saturation и подбирает baseline. Параметры сохраняются в проект через persistence.
Надо ли в JS?
✅ Все правки — да:
- BrickColor расширенная палитра — общая для обоих движков.
- Anchored=True для импорта — это про converter, не движок.
- Слайдеры света — UI студии, общий для обоих.
- persistence — общий формат
projectData.scene.lighting.
2026-06-08 — Итерация 1: RayGun (проект 2792, 9 скриптов)
Контекст: Roblox-Tool пушка-стрелялка, использует Tool-API, Lighting, Mouse, Welds, BodyForce, BrickColor, IntValue для leaderboard.
Добавлено в RobloxShim.js
Глобалы:
BrickColor.new("Bright red")+ ~25 цветов (White, Black, Bright red/blue/green, Pink, Brown, Reddish brown, Cyan, Magenta и др.). Возвращает{Color, Name, R, G, B}.Ray.new(origin, direction)— для raycast (заглушка структуры).Region3.new(min, max)— куб (заглушка).TweenInfo.new(time, easingStyle, easingDirection, repeatCount, reverses, delayTime)NumberSequence,ColorSequence,NumberRange,Rect— конструкторы-стабы.
Enum расширения: InfoType, SortOrder, FillDirection, Font, TextXAlignment/TextYAlignment, ScaleType, AspectType, PartType, SurfaceType, ContextActionResult, UserInputState, BorderMode, FormFactor.
game методы:
game:service(name)(lowercase alias на GetService) — старый Roblox API.game.GetServiceFromName= alias.game.JobId/PlaceId/GameId/CreatorId/CreatorType— stub fields.
Lighting:
Brightness,ClockTime,TimeOfDay,OutdoorAmbient,FogStart/End/Color.GetMinutesAfterMidnight(),SetMinutesAfterMidnight(m).GetMoonDirection(),GetSunDirection().
Players:
GetPlayers(),GetPlayerFromCharacter(char),playerFromCharacteralias.PlayerAdded,PlayerRemoving,ChildAddedsignals.
Instance.new новые типы:
Tool/HopperBin— Equipped/Unequipped/Activated/Deactivated signals, GripForward/Right/Up/Pos, CanBeDropped, RequiresHandle, ToolTip.IntValue/NumberValue/BoolValue/StringValue/ObjectValue/CFrameValue/Vector3Value/Color3Value/BrickColorValue/RayValue—.Value+.Changedсигнал.BodyForce/BodyVelocity/BodyPosition/BodyGyro/BodyAngularVelocity/BodyThrust—.force,.Velocity,.MaxForce,.P/.D.Weld/WeldConstraint/Motor6D/Snap/HingeConstraint/BallSocketConstraint/RopeConstraint/SpringConstraint— Part0/Part1/C0/C1/Enabled.Sparkles/ParticleEmitter/Smoke/Fire/Trail/Beam/PointLight/SurfaceLight/SpotLight— Enabled/Color/Rate/Lifetime/Brightness/Range.Mouse— Button1Down/Up, Button2Down/Up, Move, KeyDown/Up, WheelForward/Backward, Icon, Hit (Position), Target, TargetSurface, X/Y, ViewSizeX/Y.
Исправлено
-
rbx_wait(sec): минимум 0.016с (1 кадр).while true do wait() endбез аргумента раньше делал tight loop без yield → WASM stack overflow ("memory access out of bounds"). -
Уважаем
enabled: falseв Roblox-метадате. Roblox-скрипты сDisabled = true— это шаблоны для клонирования (script.Clean:Clone()), не должны запускаться при старте.parseRobloxLuaMeta()парсит JSON из второй строки packed-кода, приenabled=falseскрипт идёт вrbxlSkipped.
Tool/Backpack/Mouse flow (Шаг 1)
Контекст: Roblox-Tool это объект который попадает в Backpack игрока, при экипировке (клавиша 1-9) фейерит Tool.Equipped с настоящим Mouse, скрипты внутри Tool слушают MouseButton1Down/KeyDown.
В RobloxShim.js:
localPlayer.Backpack— инвентарь.localPlayer:GetMouse()→ playerMouse с Button1Down/KeyDown/Hit.Position.- Внутренний
allTools[]registry +equippedToolслот. Instance.new('Tool')теперь:- создаёт виртуальный
Handle(Part внутри Tool); - регистрирует в
allTools[]; - шлёт
toolRegistered {index, name}в GameRuntime.
- создаёт виртуальный
fireGlobalEventобрабатывает:equipTool,unequipTool,toolActivated,toolDeactivated,mouseButton1Down/Up,keyDown/Up.__rbxl_get_tool_by_name(name)— для script.Parent резолва.
В LuaSharedSandbox.js:
addScript(id, code, target, name, {toolName})— расширенная сигнатура.- В
_startSingleScriptесли естьtoolName—script.Parent= виртуальный Tool.
В GameRuntime.js:
- Эвристика: скрипты с
target=nullи содержащие(script.Parent|Tool).(Equipped|Unequipped|Activated|Deactivated)→ получаютtoolName='Tool', группируются в один общий Tool. _registerRbxlTool(payload)— кладёт item в InventoryUI.hotbar, слушаетslotevent → шлётequipTool/unequipTool.canvas.mousedown→mouseButton1Down+toolActivatedс raycast Hit._raycastFromCamera()— простой ray из камеры на 50 unit вперёд.
Надо ли в JS? ✅ Да — Tool/Backpack/Mouse это базовый Roblox-game-loop.
Импорт изменений в converter.py (не задеплоено)
Файл изменён локально, но importer на VM 130 — не обновлён. Когда придёт время деплоя, ключевые правки:
_collect_tool(inst)— собираетscene['tools'][]из Tool/HopperBin;_find_ancestor_tool(inst)— определяет в каком Tool лежит Script;- В
_convert_scriptдобавлено полеtool_idв метадату.
Это уберёт необходимость эвристики на стороне studio.
Надо ли портировать в JS-движок?
✅ Да, всё — это базовый Roblox-совместимый API, который должен работать независимо от языка скриптов.
JS-эквивалент будет такой же структурой:
BrickColor.new("Bright red")→new BrickColor("Bright red")ToolEquipped/Unequipped → JS-EventEmitter методы- BodyForce/Weld/Sparkles → JS-классы с теми же полями
- Mouse — глобальный объект
game.mouseили черезplayer:GetMouse().
Куда добавляется API
| Источник | Файл | Что туда идёт |
|---|---|---|
| Глобалы (Vector3, Color3, BrickColor, Enum) | RobloxShim.js через global.set |
Конструкторы, Enum-таблицы |
| Instance.new типы | RobloxShim.js в ветке global.set('Instance', {new: ...}) |
Tool, BodyForce, Weld, Sparkles и т.д. |
| Сервисы | RobloxShim.js через makeService(name) |
Lighting, Players, RunService и т.д. |
| Wait/Task | RobloxShim.js в Lua-prelude (lua.doStringSync) |
rbx_wait, task.wait |
| Setter Part-свойств | newPart() через Object.defineProperty |
Position, Color, Anchored шлют partSet |
| Команды от Lua к Babylon | rbxl-lua-integration.js handleLuaCommand |
partSet, sceneCreate, sceneDelete |
Принципы расширения API
- No-op > Падение. Лучше пустой stub-метод чем
nil error. - Сигналы (
Connect/Fire) всегда есть на любом объекте. - Coloncall совместимость. Если есть
Foo.Bar, обычно делаем иFoo:Bar(lowercase) как alias. - При добавлении нового Instance-типа — давай ему все типичные поля сразу, не только те что нужны прямо сейчас (Equipped + Unequipped + Activated вместе, даже если скрипт юзает только Equipped).
- Логировать сюда после каждой итерации — что было добавлено и из какой игры.