All checks were successful
Часть тест-фичи импорта Roblox-карт (см. rublox/studio rbxl-importer/). Что добавлено: - wasmoon (Lua 5.4 WASM) как dep. - RobloxLuaWorker.js — Worker-хост Lua-VM. - RobloxLuaSandbox.js — main-side обёртка (по аналогии с ScriptSandbox). - roblox-shim.js — math (Vector3/Color3/CFrame/UDim2), Instance прокси (game/workspace/script/GetService/IsA), Part свойства (Position/Color/Material/Anchored/CanCollide), RBXScriptSignal (Touched/Heartbeat/Stepped/Connect/Wait). - roblox-scheduler.js — корутины + wait/task.wait/task.delay/task.spawn, автоматический fire Heartbeat/Stepped/RenderStepped на tick. - roblox-tween.js — TweenService с 10 easing-функциями (Linear, Quad, Cubic, Quart, Quint, Sine, Bounce, Elastic, Back, Exponential). - roblox-services.js — Players/LocalPlayer/Character/Humanoid (Health, WalkSpeed, JumpPower, TakeDamage, Died, LoadAnimation), UserInputService, RemoteEvent (FireServer/FireClient), RemoteFunction, DataStoreService, HttpService. - roblox-physics.js — BodyVelocity/BodyGyro/BodyPosition/BodyForce/ BodyAngularVelocity/AlignPosition/LinearVelocity. Интеграция в GameRuntime: - В start() проверяется script.kind === 'roblox-lua' → _startRobloxLuaScript() запускает RobloxLuaSandbox. - _handleRobloxLuaCommand() мапит IPC команды (partSet/partVel/playerCmd) на PrimitiveManager и game.player API. - _buildRobloxLuaSceneSnap() готовит snap для workspace:GetChildren. Тесты: **36/36 passed**. - mvp (9): math, Instance proxy, Part, IsA. - wait (5): корутины, wait/task.wait/task.delay. - tween (2): TweenInfo + Linear easing. - services (8): Humanoid, DataStore, HttpService, RemoteEvent. - integration (12): KillBrick, WalkSpeed, Tween-door, BodyVelocity конвейер, leaderstats, Checkpoint, циклы с wait, task.spawn, Color/Material, RemoteEvent client→server, Heartbeat, Vector3. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
90 lines
3.4 KiB
JavaScript
90 lines
3.4 KiB
JavaScript
/**
|
||
* rbxl-lua-tween.test.js — тесты TweenService.
|
||
*/
|
||
import { LuaFactory } from 'wasmoon';
|
||
import { registerRobloxApi } from '../src/engine/roblox-shim.js';
|
||
import { RobloxScheduler } from '../src/engine/roblox-scheduler.js';
|
||
import { RobloxTweenManager } from '../src/engine/roblox-tween.js';
|
||
|
||
const SCENE = {
|
||
primitives: {
|
||
1: { id: 1, type: 'cube', name: 'Movable', x: 0, y: 5, z: 0, sx: 1, sy: 1, sz: 1,
|
||
color: '#ffffff', material: 'glossy', anchored: false, canCollide: true, opacity: 1 },
|
||
},
|
||
};
|
||
|
||
async function run(luaSource, ticks = []) {
|
||
const factory = new LuaFactory();
|
||
const lua = await factory.createEngine();
|
||
const sent = [];
|
||
const send = (cmd, payload) => sent.push({ cmd, payload });
|
||
registerRobloxApi(lua, { getSceneSnap: () => SCENE, targetPrimitiveId: 1, send });
|
||
const sched = new RobloxScheduler(lua);
|
||
sched.install();
|
||
const tweenMgr = new RobloxTweenManager();
|
||
tweenMgr.install(lua);
|
||
|
||
await sched.spawnMain(luaSource);
|
||
for (const dt of ticks) {
|
||
await sched.tick(dt);
|
||
tweenMgr.tick(dt);
|
||
}
|
||
lua.global.close();
|
||
return { logs: sent.filter(s => s.cmd === 'log').map(s => s.payload),
|
||
partSets: sent.filter(s => s.cmd === 'partSet').map(s => s.payload) };
|
||
}
|
||
|
||
const TESTS = [
|
||
{
|
||
name: 'TweenInfo создаётся',
|
||
lua: `
|
||
local info = TweenInfo.new(2, Enum.EasingStyle.Linear, Enum.EasingDirection.Out)
|
||
print("time:", info.Time, "style:", info.EasingStyle)
|
||
`,
|
||
ticks: [],
|
||
expectLogs: [{ level: 'info', text: 'time:\t2\tstyle:\tLinear' }],
|
||
},
|
||
{
|
||
name: 'TweenService:Create + Play (Linear)',
|
||
lua: `
|
||
local TS = game:GetService("TweenService")
|
||
local p = workspace:FindFirstChild("Movable")
|
||
local info = TweenInfo.new(1, Enum.EasingStyle.Linear, Enum.EasingDirection.Out)
|
||
local tw = TS:Create(p, info, { Position = Vector3.new(10, 5, 0) })
|
||
tw:Play()
|
||
print("started")
|
||
`,
|
||
ticks: [0.5, 0.5, 0.1], // больше 1 сек — должен завершиться
|
||
// Ожидаем что хотя бы один partSet с prop=position
|
||
expectPartSet: { primId: 1, prop: 'position' },
|
||
},
|
||
];
|
||
|
||
(async () => {
|
||
let passed = 0, failed = 0;
|
||
for (const t of TESTS) {
|
||
try {
|
||
const r = await run(t.lua, t.ticks);
|
||
let ok = true;
|
||
let reason = '';
|
||
for (const exp of (t.expectLogs || [])) {
|
||
const found = r.logs.find(l => l.level === exp.level && l.text === exp.text);
|
||
if (!found) { ok = false; reason = `missing log: ${exp.text}`; break; }
|
||
}
|
||
if (t.expectPartSet) {
|
||
const found = r.partSets.find(p => p.primId === t.expectPartSet.primId && p.prop === t.expectPartSet.prop);
|
||
if (!found) {
|
||
ok = false; reason = `missing partSet: ${JSON.stringify(t.expectPartSet)}; got: ${JSON.stringify(r.partSets.slice(0,3))}`;
|
||
}
|
||
}
|
||
if (ok) { console.log(`✓ ${t.name}`); passed++; }
|
||
else { console.log(`✗ ${t.name} — ${reason}`); failed++; }
|
||
} catch (e) {
|
||
console.log(`✗ ${t.name} — exception: ${e}`);
|
||
failed++;
|
||
}
|
||
}
|
||
console.log(`\n${passed} passed, ${failed} failed`);
|
||
process.exit(failed > 0 ? 1 : 0);
|
||
})();
|