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