studio/RUBLOX_LUA_API_CHANGELOG.md
min f7441b0bd6
Some checks failed
CI / Lint (push) Failing after 1m8s
CI / Build (push) Successful in 1m58s
CI / Secret scan (push) Successful in 23s
CI / PR size check (push) Has been skipped
CI / Deploy to S1 + S2 (push) Successful in 3m28s
feat: 50 ��� �� Lua + ������ Roblox ��� ���� + ������ ����
2026-06-09 21:59:19 +00:00

24 KiB
Raw Permalink Blame History

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:

  1. SpawnLocation +5 вместо +1.5. Если spawn внутри толстого пола — гравитация уронит обратно за 1 кадр, не страшно. Если выше — отлично.
  2. Auto-fallback если SpawnLocation в карте НЕ был (или дефолт остался (0, 2, 0)):
    max_top = max(p['y'] + p['sy']/2 for p in primitives)
    scene['spawnPoint'] = {x: 0, y: max_top + 5, z: 0}
    
    Игрок появляется над самой высокой Part'ой → падает на крышу.

Философия импорта (зафиксировано как принцип)

Цель импорта .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

  1. Teams — game.Teams сервис + Team-инстансы, эвристика TeamBeacon-Model в converter.py → автоматически создаёт 4 команды по имени.
  2. Leaderstats UI — IntValue.Value реактивно через Object.defineProperty, при Parent=leaderstats шлёт leaderstatSet → существующий LeaderstatsManager.
  3. BindableFunction/RemoteFunction + Message/Hint классы с реактивным Text.
  4. KillFeed UI + creator-tag tracking в Humanoid.TakeDamage. DOM-overlay.
  5. SpawnLocation.TeamColor → scene.team_spawns[].
  6. Tool/Model:Clone() + :MakeJoints/:BreakJoints/:Remove no-op.
  7. Creator-tag: ObjectValue.Name='creator' проверяется на Health=0.
  8. RegenerationScript — no-op skip по имени (Anchored=True держит).
  9. BattleArmor — реактивный Humanoid.MaxHealth/Health/WalkSpeed/JumpPower.
  10. WinGui/FireButton через GuiManager.
  11. AdminConsole — no-op.
  12. Bouncer — BodyVelocity.Y > 10 + Parent=Torso → playerSet jumpVelocity.
  13. 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 — пропускаем.

Визуальная настройка света

Главные находки:

  1. mat.ambientColor=(1,1,1) обязательно — иначе scene.ambientColor («Заливка теней» слайдер) не влияет на материалы.
  2. mat.ambientColor=(цвет_от_diffuse) даёт пересвет — не делать.
  3. scene.imageProcessingConfiguration есть готовый в Babylon — даёт exposure/contrast/saturation бесплатно.

В BabylonScene.setLightingProps(patch) добавлено:

  • patch.sceneAmbient (0..1) → scene.ambientColor (заливка теней)
  • patch.exposure (0.3..2) → ipc.exposure
  • patch.contrast (0.5..2) → ipc.contrast
  • patch.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)

  1. 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 держит постройки, регенерация не нужна.
  2. Цвета всё ещё чуть-чуть не такие как в 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), playerFromCharacter alias.
  • PlayerAdded, PlayerRemoving, ChildAdded signals.

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 если есть toolNamescript.Parent = виртуальный Tool.

В GameRuntime.js:

  • Эвристика: скрипты с target=null и содержащие (script.Parent|Tool).(Equipped|Unequipped|Activated|Deactivated) → получают toolName='Tool', группируются в один общий Tool.
  • _registerRbxlTool(payload) — кладёт item в InventoryUI.hotbar, слушает slot event → шлёт equipTool/unequipTool.
  • canvas.mousedownmouseButton1Down + 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")
  • Tool Equipped/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

  1. No-op > Падение. Лучше пустой stub-метод чем nil error.
  2. Сигналы (Connect/Fire) всегда есть на любом объекте.
  3. Coloncall совместимость. Если есть Foo.Bar, обычно делаем и Foo:Bar (lowercase) как alias.
  4. При добавлении нового Instance-типа — давай ему все типичные поля сразу, не только те что нужны прямо сейчас (Equipped + Unequipped + Activated вместе, даже если скрипт юзает только Equipped).
  5. Логировать сюда после каждой итерации — что было добавлено и из какой игры.