Зафиксированы все правки сделанные при работе с 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 держит постройки.
282 lines
16 KiB
Markdown
282 lines
16 KiB
Markdown
# 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` добавлен автодетект формата:
|
||
```python
|
||
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`:
|
||
```js
|
||
lighting: {
|
||
sunIntensity, hemiIntensity, sceneAmbient,
|
||
exposure, contrast, saturation,
|
||
}
|
||
```
|
||
|
||
`BabylonScene.loadFromState()` применяет эти параметры через `setLightingProps()`.
|
||
|
||
### Деплой rbxl-importer
|
||
|
||
**ВАЖНО:** rbxl-importer на VM 130 деплоится **напрямую через SSH**, не через CI/CD:
|
||
|
||
```bash
|
||
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` если есть `toolName` — `script.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.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")`
|
||
- `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. **Логировать сюда после каждой итерации** — что было добавлено и из какой игры.
|