studio/RUBLOX_LUA_API_CHANGELOG.md
min 71d6396d8b docs(lua): итерация 2 Crossroads в CHANGELOG
Зафиксированы все правки сделанные при работе с Crossroads:
- XML-парсер для старых .rbxl (~330 строк)
- BrickColor таблица расширена с 50 до 120 цветов
- Force-anchored для всех импортированных Part
- 4 новых слайдера в Свет и атмосфера (заливка теней,
  экспозиция, контраст, насыщенность) через imageProcessingConfiguration
- Persistence настроек света в projectData.scene.lighting
- mat.ambientColor=(1,1,1) обязательно для scene.ambientColor работы
- Деплой rbxl-importer на VM 130 через прямой SSH (CI не настроен)

Известные баги Crossroads:
- 2 скрипта Regenerate* падают на model:clone() и Instance.new('Message')
  — не критично, Anchored держит постройки.
2026-06-08 19:09:25 +03:00

16 KiB
Raw Blame History

Lua API — журнал изменений

Файл фиксирует что было добавлено в Lua-runtime при работе с реальными Roblox-играми. Цель — потом продублировать тот же API для JS-движка (на будущее, сейчас работаем только с Lua).

Формат: дата + что и почему + куда добавлено + надо ли портировать в JS.


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. Логировать сюда после каждой итерации — что было добавлено и из какой игры.