// 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); }); });