# План: Полная поддержка Lua-скриптов в Рублоксе

**Цель:** Пользователь Рублокс-студии создаёт скрипт → выбирает язык **JS** или **Lua** → пишет код → в плеере оба языка работают параллельно. Lua-скрипты совместимы с Roblox API настолько, чтобы код из Roblox-игр работал без модификаций.

**Зачем:** В Roblox экосистеме сотни тысяч разработчиков, привыкших к Lua + Roblox API (Vector3, CFrame, Instance, RemoteEvent, etc.). Сейчас они не могут перенести свои игры. С этой фичей — могут.

**Срок:** ~6 недель полного времени. Можно делать поэтапно (после каждого этапа есть полезный результат).

**Юр.риск:** "юр риски беру на себя" (МИН, 2026-06-08).

---

## Архитектурное решение

### Текущая ситуация
- Скрипты хранятся как `{id, code, target, name}` в БД
- `GameRuntime` запускает каждый JS-скрипт в Web Worker `ScriptSandboxWorker.js`
- API игре доступен через `game.*` объект (события, scene, player, gui, save, etc.)
- Скриптов в игре могут быть **сотни**, каждый — отдельный Worker

### Целевая
- Скрипты получают поле `language: 'js' | 'lua'` (default `'js'`)
- В редакторе — переключатель в `ScriptEditor.jsx` в шапке: **JS / Lua**
- Monaco-editor подсвечивает Lua (есть встроенный язык в `monaco-editor`)
- В runtime: JS-скрипты идут через старый sandbox, Lua — через новый `LuaSandbox.js`
- Lua-runtime построен поверх **wasmoon** (Lua 5.4 в WebAssembly)
- Lua-скрипты делятся на **один shared VM** на игру (а не Web Worker на скрипт) — иначе OOM при 100+ скриптах
- Lua-API проксирует Roblox API → нашу game.* плюс полную **DataModel** иерархию

### Ключевая идея
**Lua-скрипты не вызывают `game.move(...)`. Они вызывают `script.Parent.Position = Vector3.new(1,2,3)`** — как в Roblox. Lua-shim под капотом переводит это в `partSet` команды для движка Рублокса. Юзер пишет идиоматичный Roblox-Lua, движок Рублокса исполняет.

---

## Этап 1: UI и хранение языка (3 дня)

### 1.1 Миграция БД
- Добавить поле `language VARCHAR(8) DEFAULT 'js' NOT NULL` в таблицу `scripts` (есть на storys API)
- API endpoints (`POST/PATCH /scripts/...`) принимают и сохраняют `language`
- Старые скрипты без поля → `'js'` по умолчанию

### 1.2 ScriptEditor.jsx
- Переключатель в шапке редактора: `[ JS | Lua ]` (segmented control)
- При смене языка — confirm("Сменить язык? Текущий код будет очищен"), затем код сбрасывается на шаблон-заглушку нового языка
- Monaco language switch: `javascript` ↔ `lua`
- Подсветка/автодополнение Lua встроены в Monaco (`monaco-editor/esm/vs/basic-languages/lua`)
- Линтер ошибок Lua через **luaparse** (npm пакет, ~50KB, parse-only) — показываем красные подчёркивания
- Шаблон Lua для нового скрипта на target=Part:
  ```lua
  -- Скрипт на части. script.Parent = эта часть.
  local part = script.Parent
  part.Touched:Connect(function(hit)
      print("Касание:", hit.Name)
  end)
  ```
- Шаблон Lua для глобального скрипта (target=nil):
  ```lua
  local Players = game:GetService("Players")
  Players.PlayerAdded:Connect(function(player)
      print("Игрок зашёл:", player.Name)
  end)
  ```

### 1.3 Иконка языка в HierarchyPanel
- Рядом с именем скрипта — маленький бейдж `JS` или `Lua` (синий / голубой)
- Помогает не путаться при сотне скриптов

### Что готово в конце Этапа 1
- Юзер может **создать Lua-скрипт**, написать код, сохранить
- Код **не исполняется** — пока только хранится и редактируется
- В плеере Lua-скрипты молча игнорируются

---

## Этап 2: Базовый Lua-runtime (5 дней)

### 2.1 LuaSharedSandbox.js — новый sandbox-класс

Полная архитектура шаринг-VM (один wasmoon-state на все Lua-скрипты игры):

```
GameRuntime
├── ScriptSandbox       (JS-скрипт, Web Worker, как сейчас)
├── ScriptSandbox       (JS-скрипт)
├── LuaSharedSandbox    ← НОВЫЙ
│   ├── LuaSharedWorker.js
│   │   └── wasmoon VM (один на всю игру)
│   │       ├── Roblox API shim
│   │       ├── DataModel tree (game.Workspace, Players, ...)
│   │       └── все Lua-скрипты как сопрограммы (coroutines)
│   └── проксирует partSet/sceneCreate/event обратно в main thread
```

Файлы:
- `src/editor/engine/LuaSharedSandbox.js` (main thread): API совместимый с ScriptSandbox (`sendSceneSnapshot`, `sendGlobalEvent`, etc.)
- `src/editor/engine/LuaSharedWorker.js` (Web Worker): держит wasmoon, исполняет скрипты, шлёт командные сообщения
- `src/editor/engine/RobloxLuaShim.js` (worker side): объявление всех Roblox-классов и сервисов

### 2.2 Минимальный Roblox shim в первой итерации
- `Vector3.new(x,y,z)`, `+`, `-`, `*`, `:Magnitude()`, `:Dot()`, `:Cross()`, `:Lerp()`, `:Normalize()`
- `Color3.new(r,g,b)`, `Color3.fromRGB(r,g,b)`, `:Lerp()`
- `CFrame.new(x,y,z)`, `CFrame.lookAt()`, `CFrame.fromEulerAnglesXYZ()`, операторы `*` и `:Inverse()`, `:ToWorldSpace()`
- `UDim2.new(sx,ox,sy,oy)`, `UDim.new(s,o)`
- `Enum.KeyCode.W`, `Enum.UserInputType.MouseButton1`, etc. (через generated table)
- `print()` → console + onLog event в студии
- `wait(secs)` / `task.wait(secs)` — через coroutine + scheduler в main loop
- `tick()`, `os.time()`, `os.clock()`, `math.*`, `string.*`, `table.*` (стандартные Lua)
- `pcall`, `xpcall`, `error`, `assert` (стандартные)

### 2.3 GameRuntime интеграция
- При старте игры — пробежать по `scripts[]`, разделить на `jsScripts` и `luaScripts`
- Для JS — старый путь (по сэндбоксу на скрипт)
- Для Lua — один `LuaSharedSandbox`, в него `addScript(luaSource)` каждым
- LuaSharedSandbox шлёт назад те же команды что JS sandbox: `partSet`, `sceneCreate`, `chatSay`, `guiSet`, etc.

### Что готово в конце Этапа 2
- Lua-скрипты **исполняются**
- Можно использовать `Vector3`, `Color3`, `print()`, `wait()`, `math/string/table`
- Скрипт **ещё не видит** Workspace, Player, GUI

---

## Этап 3: DataModel — game.Workspace и иерархия (5 дней)

### 3.1 Что такое DataModel
В Roblox любая игра — **дерево объектов**. Корень = `game`. У него детки = сервисы: `Workspace`, `Players`, `ReplicatedStorage`, `Lighting`, `StarterGui`, `RunService`, etc. У каждого деток — свои детки.

У нас сейчас сцена плоская: `primitives`, `blocks`, `models`. Нужно **виртуальное дерево DataModel** поверх плоской сцены.

### 3.2 Виртуальное дерево
Файл: `src/editor/engine/datamodel/DataModelTree.js`

При старте Lua-runtime, для текущей сцены строится виртуальное дерево:

```
game (RbxGame)
├── Workspace (RbxWorkspace)
│   ├── Part_0       ← обёртка над primitive id=0
│   ├── Part_1       ← обёртка над primitive id=1
│   ├── Model_5      ← обёртка над model id=5 (с Children)
│   │   └── Part_inner
│   ├── Camera
│   └── Terrain
├── Players (RbxPlayers)
│   └── LocalPlayer (RbxPlayer)
│       ├── Character (RbxCharacter)
│       │   ├── Humanoid (RbxHumanoid)
│       │   └── HumanoidRootPart (RbxPart)
│       └── PlayerGui (RbxScreenGui-контейнер)
│           └── (Lua-скрипты могут спавнить GUI через Instance.new)
├── ReplicatedStorage (RbxFolder)
├── ServerStorage (RbxFolder)
├── Lighting (RbxLighting)
├── StarterGui (RbxFolder)
├── StarterPlayer (RbxFolder)
│   └── StarterCharacter (RbxFolder)
├── RunService (RbxRunService с :Heartbeat, :Stepped, :RenderStepped)
├── UserInputService (события InputBegan/Ended/Changed)
├── TweenService (:Create, :GetService для tweens)
├── HttpService (заглушка либо проксируем через нашего бэка)
├── DataStoreService (проксируем через game.save)
└── MarketplaceService (заглушка)
```

### 3.3 Instance-классы
`src/editor/engine/datamodel/Instance.js`:

```js
class RbxInstance {
    Name = "Instance";
    ClassName = "Instance";
    Parent = null;
    Children = [];

    // Свойства которые юзер может ставить через __newindex (metatable)
    // отслеживаются — при изменении посылается команда в main thread
    // для синхронизации с Babylon-сценой

    GetChildren() { return [...this.Children]; }
    FindFirstChild(name, recursive) { ... }
    WaitForChild(name, timeout) { ... }  // через coroutine + yield
    FindFirstAncestor(name) { ... }
    FindFirstChildOfClass(class) { ... }
    Destroy() { ... }
    Clone() { ... }
    IsA(class) { ... }
    GetFullName() { ... }
    GetAttribute(name) / SetAttribute(name, value)
    GetPropertyChangedSignal(name) → RbxSignal
}

class RbxPart extends RbxInstance {
    Position    // setter: партиклс в Babylon (primitiveManager.setPosition)
    Size        // setter
    Color       // setter
    Material    // setter (mapping Roblox materials → наши)
    Anchored    // setter
    CanCollide  // setter
    Touched     // RbxSignal — Fire когда BabylonScene детектит overlap
    TouchEnded  // RbxSignal
    CFrame      // computed property — Position + rotation
}

class RbxModel extends RbxInstance {
    PrimaryPart
    GetPrimaryPartCFrame() / SetPrimaryPartCFrame()
    PivotTo(cframe)  // MoveTo + Rotate
    GetBoundingBox()
}

class RbxHumanoid extends RbxInstance {
    Health = 100
    MaxHealth = 100
    WalkSpeed = 16
    JumpPower = 50
    Died, HealthChanged, Touched, StateChanged — signals
    TakeDamage(amount)
    MoveTo(pos)  // simulates Roblox NPC pathing
    LoadAnimation(anim) → RbxAnimationTrack
}

class RbxScript extends RbxInstance {
    Source       // источник Lua (read-only обычно)
    Disabled     // bool
    RunContext   // Server / Client / Legacy
}

class RbxRemoteEvent extends RbxInstance {
    OnServerEvent : RbxSignal
    OnClientEvent : RbxSignal
    FireServer(...args)
    FireClient(player, ...args)
    FireAllClients(...args)
}
```

### 3.4 Метатаблицы Lua
Каждая JS-обёртка `RbxPart` экспортируется в Lua как **table с metatable**:
- `__index` — чтение свойства, либо метод
- `__newindex` — запись свойства, триггерит side-effects (синхронизация сцены)
- `__tostring` — для `print(part)` показывает `"Part_0"`

Это **критично** для совместимости с Roblox-скриптами.

### 3.5 script.Parent для каждого Lua-скрипта
- Если скрипт привязан к `target=42` (primitive id 42) — `script.Parent = workspace:FindFirstChild по primId(42)`
- Если глобальный — `script.Parent = nil`
- В скриптовом контексте: `script` это таблица `{Name=..., Parent=..., ClassName="Script"}`

### Что готово в конце Этапа 3
- Lua-скрипт может пройтись по `game.Workspace:GetChildren()`
- `script.Parent.Touched:Connect(...)` работает (KillBrick = реально)
- `local player = game.Players.LocalPlayer; player.Character.Humanoid.Health = 0` убивает игрока
- `Instance.new("Part", workspace)` создаёт примитив

---

## Этап 4: Полный Roblox API (10 дней)

Закрываем "длинный хвост" API. Каждый день — 1-2 сервиса.

### 4.1 Services
- **RunService**: `.Heartbeat`, `.Stepped`, `.RenderStepped` — RbxSignals, фаер в main loop tick
- **UserInputService**: `.InputBegan`, `.InputChanged`, `.InputEnded` — события KeyCode/MouseButton/Touch
- **TweenService**: `:Create(instance, TweenInfo, propertyTable)` → возвращает RbxTween; `Tween:Play()/Pause()/Cancel()`; интерполируется в main loop
- **DataStoreService**: проксируем через наш `game.save` (sync version)
  - `:GetDataStore(name)` → объект с `:GetAsync(key)`, `:SetAsync(key, value)`, `:UpdateAsync(key, fn)`
  - Async-методы через coroutine.yield + наш save API
- **MarketplaceService**: заглушки `PromptPurchase`, `GetProductInfo` (бизнес-логика через наш интерфейс)
- **HttpService**: `:JSONEncode`, `:JSONDecode`, `:GenerateGUID` — простая встроенная реализация; `:GetAsync/PostAsync` проксируем через ограниченный список разрешённых доменов
- **Players**: `LocalPlayer`, `:GetPlayers()`, `PlayerAdded`/`PlayerRemoving` сигналы
- **Lighting**: read-only сейчас (через `Lighting.Ambient`, `Lighting.OutdoorAmbient` ставить значения нашему envManager)
- **Workspace**: `:Raycast(origin, direction, params)` → реальный raycast через PhysicsAABB; `:GetServerTimeNow()`; `CurrentCamera`

### 4.2 GUI (важно!)
- `Instance.new("ScreenGui")` → если `Parent = playerGui`, регистрируется в нашем GuiManager
- `TextLabel`, `TextButton`, `ImageLabel`, `ImageButton`, `Frame` — все мапятся на наш GuiOverlay
- `MouseButton1Click`, `MouseEnter`, `MouseLeave`, `Activated` — сигналы
- `UDim2`, `Vector2` для позиций/размеров
- При установке `gui.Position = UDim2.new(0.5, 0, 0.5, 0)` — пересылается в GuiManager и обновляется DOM

### 4.3 Sound
- `Instance.new("Sound", part)` с `SoundId = "rbxassetid://12345"` или с нашим URL
- `:Play()`, `:Stop()`, `:Pause()`, `Volume`, `Pitch`, `Looped`
- Под капотом — наш SoundManager

### 4.4 Animation
- `Instance.new("Animation")` с `AnimationId` (наши собственные ID анимаций R15)
- `humanoid:LoadAnimation(anim) → AnimationTrack`
- `:Play()`, `:Stop()`, `:AdjustSpeed()`, `:GetMarkerReachedSignal()`
- Связь с нашим R15Animator

### 4.5 Tools / Backpack
- `Tool` Instance: `Activated`, `Equipped`, `Unequipped` сигналы
- `player.Backpack:GetChildren()` — Lua видит инвентарь
- Реализация через наш HotbarManager + InventoryService

### 4.6 ProximityPrompt, ClickDetector
- ProximityPrompt: `Triggered` сигнал, `:Show/:Hide`, ActionText, ObjectText
- ClickDetector: `MouseClick`, `MouseHoverEnter/Leave` сигналы

### 4.7 Networking-эмуляция (single-player)
- RemoteEvent / RemoteFunction работают **локально** (поскольку у нас singleplayer на client-only)
- `FireServer/InvokeServer` запускают handlers в том же VM, но в other-side контексте
- Это позволяет копировать многопользовательские скрипты Roblox без изменений (хоть мультиплеера и нет)
- Когда у Рублокса появится мультиплеер — `RemoteEvent` уже будет работать «по-настоящему» без изменений в скриптах юзера

### Что готово в конце Этапа 4
- **~90% типовых Roblox-скриптов работают** без модификаций
- DataStore сохраняет прогресс
- TweenService плавно двигает объекты
- GUI создаётся скриптом
- Анимации играются

---

## Этап 5: Импорт .rbxl + конвертер юзер-кода (5 дней)

### 5.1 Изменение импортера
Сейчас импортер сохраняет Lua-исходник в JS-комментарии и пытается завернуть в JS-обёртку. **Это устаревает.**

Новый путь:
- Импортер сохраняет Lua-source **как есть** (без обёрток)
- В записи скрипта `language = 'lua'`
- target = primitiveId или null
- В GameRuntime Lua-скрипт идёт сразу в LuaSharedSandbox

### 5.2 Конвертация ассетов
- Roblox MeshId/TextureId через наш ImageProxy → ассеты сохраняются в minio + на CDN
- `rbxassetid://12345` → resolve в наш asset_id
- Сохранение в БД ссылок на ассеты

### 5.3 Поведение при импорте
- При импорте .rbxl карты — все Lua-скрипты сохраняются как Lua-скрипты Рублокса (не пытаемся конвертить в JS)
- Юзер открывает игру → редактирует → видит в Hierarchy `Script (Lua)` рядом с `Script (JS)` — может писать на любом

### Что готово в конце Этапа 5
- Импорт Roblox-карты работает **бесшовно**
- Юзер может править Lua-код в редакторе и видеть результат

---

## Этап 6: Производительность и стабильность (5 дней)

### 6.1 Profiling
- Замерить FPS на картах с 100/500/1000 Lua-скриптов
- Если падает — переезд на **fengari** (pure-JS Lua interp, в 5-10× медленнее wasmoon но без WASM overhead на старте) либо на **собственный Lua-bytecode runtime** для горячих скриптов

### 6.2 Memory
- Каждый wasmoon VM ~10-15MB. Один на игру = ОК. Если придётся разделять на части (server/client), нужно бенчить.

### 6.3 Песочница (security)
- Lua не должен дёргать `io.*`, `os.execute`, `loadstring(внешний код)`, etc.
- Whitelist стандартной библиотеки. Запрещаем всё что может выйти из браузера.

### 6.4 Ошибки
- Lua-ошибки в скрипте — показываются в Output-панели студии (как JS-ошибки)
- Stack trace с правильными номерами строк (не из обёртки)
- Если скрипт зациклился — kill через `debug.sethook` после N инструкций без yield

### 6.5 Тесты
- 50 unit-тестов на Roblox API (Vector3 операции, CFrame, Instance.new, Touched, RunService, TweenService)
- 10 интеграционных: импортировать тест-rbxl, запустить, проверить что нужное случилось
- CI: тесты прогоняются в Gitea Actions при PR

### Что готово в конце Этапа 6
- Lua-runtime production-ready
- Можно объявить публично «теперь в Рублоксе пишут на Lua с Roblox-совместимостью»

---

## Этап 7: Документация (3 дня)

### 7.1 Раздел вики
- `wiki/lua-intro` — введение в Lua для Рублокса (для пользователей, которые приходят с Roblox — короткое)
- `wiki/lua-vs-js` — таблица: «то же самое на JS и на Lua» для типичных задач
- `wiki/roblox-api-supported` — список того что работает / не работает / отличается
- `wiki/lua-examples` — 20 готовых сниппетов (KillBrick, TeleportPad, Checkpoint, Coin, NPCFollower, etc.)

### 7.2 Migration guide
- «Как перенести свою Roblox-игру в Рублокс» — пошаговое
- Список известных несовместимостей и их обходов

### 7.3 PR-материал
- Пост в /developer на team.rublox.pro: «Lua-поддержка теперь GA»
- Если есть бюджет — короткий ролик на YouTube/TikTok

---

## Этапы целиком

| Этап | Длительность | Содержание |
|------|--------------|------------|
| 1    | 3 дня        | UI и хранение языка |
| 2    | 5 дней       | Базовый Lua-runtime + минимальный shim |
| 3    | 5 дней       | DataModel (game.Workspace и иерархия) |
| 4    | 10 дней      | Полный Roblox API (services, GUI, Sound, Animation) |
| 5    | 5 дней       | Импорт .rbxl и асетов |
| 6    | 5 дней       | Производительность, безопасность, тесты |
| 7    | 3 дня        | Документация и публикация |
| **Итого** | **~36 рабочих дней** | **~6 недель полного времени** |

---

## Что-то можно делать раньше, чтобы получить пользу

- **MVP (Этапы 1+2):** через **8 дней** — юзер может писать Lua-скрипты с минимальным API (Vector3, Color3, print, wait). Уже видит что фича есть.
- **Beta (Этапы 1-3):** через **13 дней** — KillBrick'и работают, можно делать простые игры на Lua.
- **GA (все этапы):** через **6 недель** — продакшен.

После каждого этапа можно делать релиз и собирать фидбек.

---

## Решения которые нужны от тебя перед стартом

1. **wasmoon vs fengari** — wasmoon быстрее но WASM-heavy, fengari проще но медленнее. Предлагаю wasmoon (уже используем для импорта).
2. **Один shared VM на игру** — согласен или разделять server/client? Предлагаю один в singleplayer-фазе, разделение — позже когда будет мультиплеер.
3. **Бэкенд изменения** — нужна миграция БД (поле `language`). У нас сейчас S2 + S1 + auto-backup, ничего страшного, но согласовать момент апдейта.
4. **Roblox API trademark/copyright** — мы делаем API-compatible runtime. Названия классов `Workspace`, `Humanoid`, etc. это API names. Юр.риск есть. Ты сказал берёшь — фиксируем.
5. **Приоритет** — этот план делать **вместо** других задач (тогда параллельные фичи стопаются) или **после** текущего бэклога?

---

## Связанные документы

- `RUBLOX_PROJECT.md` — общий план Рублокса
- `RUBLOX_EDITOR_ROADMAP.md` — куда движется редактор
- `INFO_PROCESS.md` — лог реализации (будет апдейтиться по ходу)

---

**Создано:** 2026-06-08, Claude (Opus 4.7) совместно с МИНом.
**Статус:** план готов, ждём решения по 5 вопросам перед стартом.
