reachy-blocks / tests /unit /frames.test.ts
Claude
feat: emotions-library loader, sound effects, WAV pipeline
bac98f7 unverified
Raw
History Blame Contribute Delete
2.54 kB
// Frame sampling over a REAL emotion from the library (committed fixture)
// — proves the recorded-move JSON → puppet trajectory pipeline on
// production data.
import { describe, expect, it } from "vitest";
import { sampleFrames } from "../../src/emotions/loader";
import type { EmotionMotion } from "../../src/robot/adapter";
import cheerful1 from "../fixtures/cheerful1.json";
const motion = cheerful1 as unknown as EmotionMotion;
describe("sampleFrames on cheerful1", () => {
const frames = sampleFrames(motion);
it("keeps every recorded frame with monotonic timestamps", () => {
expect(frames).toHaveLength(motion.time.length);
expect(frames[0]!.t).toBeCloseTo(0, 1);
for (let i = 1; i < frames.length; i++) {
expect(frames[i]!.t).toBeGreaterThanOrEqual(frames[i - 1]!.t);
}
// A real move lasts a few seconds.
expect(frames[frames.length - 1]!.t).toBeGreaterThan(1);
expect(frames[frames.length - 1]!.t).toBeLessThan(30);
});
it("produces plausible logical degrees", () => {
for (const f of frames) {
expect(Math.abs(f.headRoll)).toBeLessThanOrEqual(90);
expect(Math.abs(f.headPitch)).toBeLessThanOrEqual(90);
expect(Math.abs(f.headYaw)).toBeLessThanOrEqual(120);
expect(Math.abs(f.bodyYaw)).toBeLessThanOrEqual(180);
expect(Math.abs(f.antennaRight)).toBeLessThanOrEqual(180);
expect(Math.abs(f.antennaLeft)).toBeLessThanOrEqual(180);
for (const v of Object.values(f)) expect(Number.isFinite(v)).toBe(true);
}
});
it("actually moves (not a degenerate constant trajectory)", () => {
const pitches = frames.map((f) => f.headPitch);
const span = Math.max(...pitches) - Math.min(...pitches);
const antSpan =
Math.max(...frames.map((f) => f.antennaLeft)) - Math.min(...frames.map((f) => f.antennaLeft));
expect(span + antSpan).toBeGreaterThan(3);
});
it("holds previous values when a channel is missing", () => {
const sparse: EmotionMotion = {
time: [0, 0.1, 0.2],
set_target_data: [
{ head: motion.set_target_data[0]!.head, antennas: [0.1, -0.1], body_yaw: 0.2 },
{}, // nothing recorded this tick
{ antennas: [0.3, -0.3] },
],
};
const out = sampleFrames(sparse);
expect(out[1]).toMatchObject({
headRoll: out[0]!.headRoll,
antennaRight: out[0]!.antennaRight,
bodyYaw: out[0]!.bodyYaw,
});
expect(out[2]!.antennaRight).not.toBe(out[1]!.antennaRight);
expect(out[2]!.headPitch).toBe(out[1]!.headPitch);
});
});