fix(lua): Vector3.Magnitude/Unit как getters + сохранение language в БД

Vector3:
- Magnitude теперь getter (без скобок) — как в Roblox
- Unit теперь getter
- Поддержаны lowercase алиасы magnitude, unit

Сохранение:
- BabylonScene serialize включает language в scripts[]
- Clip/copy включает language
- LuaSharedWorker.handleInit обёрнут в try/catch с детальной ошибкой
- LuaSharedWorker использует статический import для wasmoon

Этап 2 завершён полностью. Lua-runtime прошёл все тесты:
print, warn, Vector3, Color3, UDim2, CFrame, Enum, task.delay/spawn/defer,
RunService.Heartbeat, math/string/table, pcall.

Следующий этап 3: DataModel (game.Workspace + Instance + script.Parent +
Part.Touched + Part.Position setter).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
min 2026-06-08 10:51:42 +03:00
parent d41051edf9
commit b7a0b083b6
4 changed files with 156 additions and 30 deletions

124
RBXL_SOURCES.md Normal file
View File

@ -0,0 +1,124 @@
# Реестр источников .rbxl / .rbxlx для портирования в Рублокс
Цель: легально добыть Roblox Place-файлы (.rbxl бинарный / .rbxlx XML) по жанрам
для портирования и публикации на Рублоксе.
**Форматы:** `.rbxlx` (XML — предпочтителен, читаемый, легко парсить геометрию/CFrame)
· `.rbxl` (бинарный, конвертировать) · `.rbxm`/`.rbxmx` (модели, не целые места).
> ⚠️ **Главное про публикацию:** «uncopylocked» ≠ свободная лицензия. Для ПУБЛИКАЦИИ
> порта на Рублоксе безопасны только: репо с явной **MIT/Apache/MPL/CC0/CC-BY** +
> официальные ассеты Roblox с разрешением. Архивы чужих игр — только для
> обучения/прототипа парсера, НЕ для публикации. Lua-скрипты не портируются
> автоматом — логику переписываешь сам (это и снижает юр.риски).
---
## ИНСТРУМЕНТЫ (распаковка/парсинг)
| Инструмент | URL | Назначение | Лицензия |
|---|---|---|---|
| Rojo | https://github.com/rojo-rbx/rojo | place ↔ файлы | MPL-2.0 |
| rbxlx-to-rojo | https://github.com/rojo-rbx/rbxlx-to-rojo | .rbxl/.rbxlx → проект | MPL/MIT (проверить) |
| rbxfile (Go) | https://github.com/robloxapi/rbxfile | парсинг rbxl/rbxlx/rbxm | MIT (проверить) |
| remodel | https://github.com/rojo-rbx/remodel | скриптовая обработка | MPL-2.0 |
| RobloxAPI/spec | https://github.com/RobloxAPI/spec/blob/master/formats/rbxl.md | спека бинарного формата | docs |
---
## (А) ОФИЦИАЛЬНЫЕ — самые надёжные
### A1. Roblox/Old-Open-Source-Levels — классика от самой Roblox Corp ⭐
- https://github.com/Roblox/Old-Open-Source-Levels
- Каталог: https://github.com/Roblox/Old-Open-Source-Levels/blob/master/catalog.md
- ~30+ мест 2007-2013 (.rbxl). Жанры: Crossroads (арена/PvP), Castle Warfare,
ROBLOX Battle (бой), Sword Fight in the Dark (PvP), Haunted Mansion (хоррор),
Glass Houses, Pinball Wizards, Happy Home in Robloxia (песочница/мини).
- Лицензия: «free to manipulate however you wish» — **проверь файл LICENSE вручную** перед публикацией.
### A2. Встроенные шаблоны Roblox Studio
- Список: https://create.roblox.com/docs/resources/templates
- В Studio: открыть шаблон → File → Save to File → .rbxlx
- Baseplate, Castle, Suburban, Village, Racing, Classic Obby, Team Deathmatch/Combat,
Capture the Flag, Line Runner, Pirate Island, Modern City и др.
- Серая зона для публикации «как есть» — используй как базу/учёбу, геометрию делай своей.
### A3. creator-docs (документация Roblox, open)
- https://github.com/Roblox/creator-docs
### A4. Internet Archive — Crossroads (все версии 2007-2017)
- https://archive.org/details/roblox_crossroads
- https://archive.org/details/classic-crossroads_202408
---
## (Б) РЕПОЗИТОРИИ С КОДОМ/МЕСТАМИ (URL из поиска)
### С подтверждённой свободной лицензией (можно публиковать)
| Репо | URL | Лицензия | Жанр |
|---|---|---|---|
| Vigilant | https://github.com/IsoLogicGames/Vigilant | **MIT** ✅ | co-op horde-survival (шутер) |
| crossroads-rojo | https://github.com/Dekkonot/crossroads-rojo | наследует Crossroads | арена |
### Open-source игры (лицензию проверить у каждого — файл LICENSE)
| Репо | URL | Жанр |
|---|---|---|
| Miner's Haven | https://github.com/berezaa/minershaven | tycoon/симулятор |
| roblox-gym-tycoon | https://github.com/jason-lee88/roblox-gym-tycoon | tycoon |
| Racing-Kit-Roblox | https://github.com/Astrophsica/Racing-Kit-Roblox | гонки |
| RENTED_old_rbx | https://github.com/ReRand/RENTED_old_rbx | хоррор |
| roblox-rpg | https://github.com/mobyrblx/roblox-rpg | RPG/демо |
| RobloxGames (dwmk) | https://github.com/dwmk/RobloxGames | разное |
| recsObby | https://github.com/Nimblz/recsObby | obby |
| WavyRobloxObby | https://github.com/sammy0127/WavyRobloxObby | obby (.rbxlx) |
| Sight-Obby | https://github.com/TeoJJss/Sight-Obby | obby |
| fps (Anninzy) | https://github.com/Anninzy/fps | FPS |
| roblox-game-example | https://github.com/areshaistg/roblox-game-example | демо-каркас |
### Архивы чужих игр (ТОЛЬКО обучение/прототип, НЕ публикация — смешанные права)
| Репо | URL |
|---|---|
| uncopylocked-game-collection | https://github.com/Kitaske/uncopylocked-game-collection |
| robloxplacearchive | https://github.com/tropicalbananas/robloxplacearchive |
| RobloxRBXLArchive | https://github.com/LuaGunsX/RobloxRBXLArchive |
| Biggest Uncopylocked Library | https://github.com/KH0DIN/Biggest_Uncopylocked_Roblox_Games_Library |
| GitHub topics | https://github.com/topics/rbxlx · /rbxl · /rbxm · /rojo · /uncopylocked |
---
## (В) САЙТЫ ДЛЯ САМОСТОЯТЕЛЬНОГО СКАЧИВАНИЯ
### Прямое скачивание .rbxl/.rbxlx
- **GitHub code search** (вход обязателен): `extension:rbxlx`, `extension:rbxl`,
`filename:default.project.json` (корень Rojo-проекта рядом с местом)
https://github.com/search?q=extension%3Arbxlx&type=code
- **GitHub Topics:** https://github.com/topics/rbxlx · https://github.com/topics/rojo
- **Internet Archive:** https://archive.org/ — поиск «roblox place», «rbxl», «crossroads»
### CC0/CC-BY геометрия для воссоздания (юридически чистейший путь, не .rbxl но low-poly близко к Roblox)
- **Kenney** (CC0): https://kenney.nl/assets — Platformer/Nature/Car/Pirate/City/Prototype Kit, Blocky Characters
- **OpenGameArt** (CC0/CC-BY): https://opengameart.org/ — voxel/low-poly паки
- **itch.io** (фильтр assets+CC0): https://itch.io/game-assets/free/tag-low-poly
- **Poly Pizza** (CC0/CC-BY low-poly): https://poly.pizza/
- **Quaternius** (CC0 low-poly паки): https://quaternius.com/
### Сообщества с открытыми играми (часто прямые ссылки + лицензия)
- DevForum «free & open-sourced games»: https://devforum.roblox.com/t/lots-of-free-open-sourced-games/525670
- DevForum «Open Source Arena FPS»: https://devforum.roblox.com/t/open-source-arena-fps/1034576
- Uplift Games open source: https://www.uplift.games/open-source
---
## ЮРИДИЧЕСКИЕ ПРАВИЛА (коротко)
- ✅ Публиковать можно: **MIT / Apache-2.0 / MPL-2.0 / CC0 / CC-BY** (CC-BY — с атрибуцией).
- ❌ Нельзя: **GPL/AGPL** (заразные), **CC-BY-NC** (некоммерч.), **без лицензии** (= all rights reserved),
чужие игры через game-savers/декомпиляторы (нарушение DMCA/ToS).
- ⚠️ «Uncopylocked» = только разрешение копировать в Studio, НЕ передача прав.
- ⚠️ Официальные шаблоны Studio — учиться ОК, публиковать «как есть» — серая зона.
**Рекомендация для наполнения Рублокса легально:**
1. Геометрия под чистую публикацию → Kenney/OpenGameArt CC0.
2. Классика Roblox-стиля → Roblox/Old-Open-Source-Levels (проверить LICENSE) + Crossroads.
3. Полная игра с кодом → Vigilant (MIT).
4. Масса .rbxl для теста парсера → архивы из (Б) + GitHub topics.

View File

@ -3035,13 +3035,6 @@ export class BabylonScene {
// Без этого onTouch финиша/плитки не срабатывает (игрок встал). // Без этого onTouch финиша/плитки не срабатывает (игрок встал).
const EPS = 0.25; const EPS = 0.25;
// Диагностика раз в секунду через time-based throttle
if (!this._touchDbgT0) this._touchDbgT0 = performance.now();
const _nowDbg = performance.now();
if (_nowDbg - this._touchDbgT0 > 1000) {
this._touchDbgT0 = _nowDbg;
console.warn(`[TouchDbg] pos=(${px.toFixed(2)},${py.toFixed(2)},${pz.toFixed(2)}) scripts=${scripts.length}`);
}
// 1) Касания объектов с target-скриптами (ключ touchState = 's:'+scriptId) // 1) Касания объектов с target-скриптами (ключ touchState = 's:'+scriptId)
let _firedThisFrame = 0; let _firedThisFrame = 0;
for (const s of scripts) { for (const s of scripts) {
@ -5537,7 +5530,7 @@ export class BabylonScene {
}; };
clip.scripts = (this._scripts || []) clip.scripts = (this._scripts || [])
.filter(s => matchTarget(s.target)) .filter(s => matchTarget(s.target))
.map(s => ({ code: s.code, name: s.name || null })); .map(s => ({ code: s.code, name: s.name || null, language: s.language || 'js' }));
} catch (e) { clip.scripts = []; } } catch (e) { clip.scripts = []; }
try { localStorage.setItem('kubikon_clipboard', JSON.stringify(clip)); } try { localStorage.setItem('kubikon_clipboard', JSON.stringify(clip)); }
catch (e) { /* ignore — приватный режим / переполнение */ } catch (e) { /* ignore — приватный режим / переполнение */ }
@ -7769,6 +7762,7 @@ export class BabylonScene {
code: s.code, code: s.code,
target: s.target || null, target: s.target || null,
name: s.name || null, name: s.name || null,
language: s.language === 'lua' ? 'lua' : 'js',
})), })),
}, },
editorCamera: this.camera ? { editorCamera: this.camera ? {

View File

@ -21,7 +21,9 @@
* и вызывает Lua-side Fire по соответствующему signal'у * и вызывает Lua-side Fire по соответствующему signal'у
*/ */
let _wasmoon = null; // Статический импорт — Vite корректно бандлит wasmoon в worker
import { LuaFactory } from 'wasmoon';
import { registerRobloxShim } from './RobloxShim.js';
// Главное состояние VM (на весь life-cycle Worker'а) // Главное состояние VM (на весь life-cycle Worker'а)
const state = { const state = {
@ -72,23 +74,21 @@ function logToMain(level, text) {
async function handleInit(payload) { async function handleInit(payload) {
state.ipcId = payload?.ipcId || 0; state.ipcId = payload?.ipcId || 0;
send('boot', { ipcId: state.ipcId }); send('boot', { ipcId: state.ipcId });
// Загрузить wasmoon try {
_wasmoon = await import(/* @vite-ignore */ 'wasmoon');
const { LuaFactory } = _wasmoon;
const factory = new LuaFactory(); const factory = new LuaFactory();
state.vm = await factory.createEngine({ openStandardLibs: true }); state.vm = await factory.createEngine({ openStandardLibs: true });
// Регистрируем минимальный Roblox shim
const { registerRobloxShim } = await import('./RobloxShim.js');
state.api = registerRobloxShim(state.vm, { state.api = registerRobloxShim(state.vm, {
send, send,
getSceneSnapshot: () => state.scenes, getSceneSnapshot: () => state.scenes,
getGuiTree: () => state.guiTree, getGuiTree: () => state.guiTree,
scheduleWait: (sec) => scheduleWait(sec), scheduleWait: (sec) => scheduleWait(sec),
}); });
state.isReady = true; state.isReady = true;
send('ready', {}); send('ready', {});
} catch (err) {
// Это самое важное — без этого юзер не видит почему ничего не работает
logToMain('error', `[LuaWorker init FATAL] ${err?.message || err}\nstack: ${err?.stack || '?'}`);
}
} }
function handleAddScriptsBatch(payload) { function handleAddScriptsBatch(payload) {

View File

@ -57,13 +57,21 @@ class RbxVector3 {
this.X = +x; this.Y = +y; this.Z = +z; this.X = +x; this.Y = +y; this.Z = +z;
} }
static new(x, y, z) { return new RbxVector3(x, y, z); } static new(x, y, z) { return new RbxVector3(x, y, z); }
Magnitude() { return Math.hypot(this.X, this.Y, this.Z); } // В Roblox Magnitude/Unit это PROPERTY (без скобок), а не методы.
get Magnitude() { return Math.hypot(this.X, this.Y, this.Z); }
get magnitude() { return Math.hypot(this.X, this.Y, this.Z); } get magnitude() { return Math.hypot(this.X, this.Y, this.Z); }
Unit() { get Unit() {
const m = this.Magnitude() || 1; const m = Math.hypot(this.X, this.Y, this.Z) || 1;
return new RbxVector3(this.X / m, this.Y / m, this.Z / m);
}
get unit() {
const m = Math.hypot(this.X, this.Y, this.Z) || 1;
return new RbxVector3(this.X / m, this.Y / m, this.Z / m);
}
Normalize() {
const m = Math.hypot(this.X, this.Y, this.Z) || 1;
return new RbxVector3(this.X / m, this.Y / m, this.Z / m); return new RbxVector3(this.X / m, this.Y / m, this.Z / m);
} }
Normalize() { return this.Unit(); }
Dot(b) { return this.X * b.X + this.Y * b.Y + this.Z * b.Z; } Dot(b) { return this.X * b.X + this.Y * b.Y + this.Z * b.Z; }
Cross(b) { Cross(b) {
return new RbxVector3( return new RbxVector3(