Files
Mujoco_WASM/web_platform/e2e/lekiwi.physics.spec.ts
T
chenlin 3ad29356c9
web-platform-ci / TypeScript, lint, unit, build (push) Has been cancelled
web-platform-ci / Playwright E2E (push) Has been cancelled
lekiwi-compatibility / cpu-compatibility (push) Has been cancelled
feat(lekiwi): release V0.10.1 初步集成 LeKiwi,优化碰撞模型
集成通用机器人数值接口、本机控制桥、LeRobot 插件和统一键盘遥操作。采用离线 CoACD 全臂碰撞配方 revision 4、局部装配区切分与结构自接触,限制直接关节位姿写入并保留安全看门狗。同步版本号、变更记录、来源许可证和兼容性验证。
2026-09-20 14:42:30 +08:00

98 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { expect, test } from '@playwright/test';
import { resolve } from 'node:path';
import { writeFile } from 'node:fs/promises';
import type {} from '../physics/runner';
test('真实 WASM 3.11:LeKiwi 站稳、全向轮、臂夹爪与障碍阻挡', async ({ page }, info) => {
const errors: string[] = [];
page.on('pageerror', (e) => errors.push(e.message));
page.on('console', (msg) => {
if (msg.type() === 'error') console.log(msg.text());
});
await page.goto('/physics/runner.html');
await page.waitForFunction(() => Boolean(window.lekiwiPhysics));
const assetBase = `/@fs${resolve(process.cwd(), 'build/lekiwi')}`;
const initial = await page.evaluate((path) => window.lekiwiPhysics.boot(path), assetBase);
console.log('initial', initial);
expect(initial.version).toBe('3.11.0');
expect(initial.nu).toBe(9);
expect(initial.njnt).toBe(46);
await writeFile(
info.outputPath('lekiwi-profile.xml'),
await page.evaluate(() => window.lekiwiPhysics.xml()),
);
const stable = await page.evaluate(() => window.lekiwiPhysics.step(30));
console.log('stable', stable);
expect(stable.finite).toBe(true);
expect(Math.abs(stable.roll)).toBeLessThan(0.15);
expect(Math.abs(stable.pitch)).toBeLessThan(0.15);
const traces: unknown[] = [{ initial, stable }];
for (const [x, y, w] of [
[0.1, 0, 0],
[-0.1, 0, 0],
[0, 0.1, 0],
[0, -0.1, 0],
[0, 0, Math.PI / 6],
[0, 0, -Math.PI / 6],
]) {
const start = await page.evaluate(() => window.lekiwiPhysics.reset());
const end = await page.evaluate(
([x, y, w]) => window.lekiwiPhysics.drive(x, y, w, 2),
[x, y, w],
);
console.log('drive', x, y, w, 'start', start.position, start.yaw, 'end', end.position, end.yaw);
traces.push({ command: [x, y, w], start, end });
expect(end.finite).toBe(true);
if (x || y) {
expect(Math.abs(end.position[0] - start.position[0] - x * 2)).toBeLessThan(0.05);
expect(Math.abs(end.position[1] - start.position[1] - y * 2)).toBeLessThan(0.05);
} else expect(Math.abs(end.yaw - start.yaw - w * 2)).toBeLessThan((10 * Math.PI) / 180);
}
await page.evaluate(() => window.lekiwiPhysics.reset());
// Free-space tracking; negative elbow folding now has its own obstruction test.
const targets = [0.1, 0.1, 0.1, 0.1, 0.1, 0.3];
const arm = await page.evaluate((values) => window.lekiwiPhysics.arm(values, 2), targets);
console.log('arm', arm.arm);
traces.push({ targets, arm });
arm.arm.forEach((value, i) => expect(Math.abs(value - targets[i])).toBeLessThan(0.015));
await page.evaluate(() => window.lekiwiPhysics.wall());
const blocked = await page.evaluate(() => window.lekiwiPhysics.drive(0.1, 0, 0, 5));
console.log('blocked', blocked);
const contacts = await page.evaluate(() => window.lekiwiPhysics.contactPairs());
traces.push({ blocked, contacts });
expect(contacts.some((c) => c.geom1 === 'test_wall' || c.geom2 === 'test_wall')).toBe(true);
expect(blocked.maxContacts).toBeGreaterThan(0);
expect(blocked.position[0]).toBeLessThan(0.22);
// The complete arm/accessory outline reaches the wall before the old chassis proxy.
expect(blocked.position[0]).toBeGreaterThan(0.02);
expect(blocked.finite).toBe(true);
await writeFile(info.outputPath('physics-traces.json'), JSON.stringify(traces, null, 2));
expect(errors).toEqual([]);
await page.evaluate(() => window.lekiwiPhysics.dispose());
});
test('真实 Python/WASM:控制权、旧回调隔离、安全保持、reset 与导出重载', async ({ page }) => {
await page.goto('/physics/runner.html');
await page.waitForFunction(() => Boolean(window.lekiwiPhysics));
await page.evaluate(
(path) => window.lekiwiPhysics.boot(path),
`/@fs${resolve(process.cwd(), 'build/lekiwi')}`,
);
const result = await page.evaluate(() => window.lekiwiPhysics.runtimeChecks());
expect(result.pythonTarget).toBe(0.1);
expect(result.accepted.values['arm_shoulder_pan.pos']).toBe(0.3);
expect(result.afterOldDispose).toBe(0.3);
expect(result.manualBlocked).toBe(true);
expect(result.held).toBeCloseTo(result.actual, 10);
expect(result.paused?.enabled).toBe(false);
expect(result.pausedOwner).toBe('manual');
expect(result.cancel).toBe('DISCONNECTED');
expect(result.reset?.modelEpoch).toBe(1);
expect(result.reset?.paused).toBe(true);
const reloaded = await page.evaluate(() => window.lekiwiPhysics.reimport());
expect(reloaded.nu).toBe(9);
expect(reloaded.njnt).toBe(46);
expect(reloaded.robot?.profileId).toBe('lekiwi-v1');
await page.evaluate(() => window.lekiwiPhysics.dispose());
});