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