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

432 lines
24 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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)`):
```python
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.
14. **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 паттерн:
```lua
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` добавлен автодетект формата:
```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. **Логировать сюда после каждой итерации** — что было добавлено и из какой игры.