435 lines
25 KiB
Markdown
435 lines
25 KiB
Markdown
# План: Полная поддержка 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 вопросам перед стартом.
|