import { describe, it, expect } from 'vitest' import { createCamera } from '../src/camera.js' const vp = () => ({ w: 1920, h: 1080 }) describe('camera 镜头数学', () => { it('cut 瞬变状态(验收 6)', () => { const cam = createCamera({ getViewport: vp }) cam.applyCommand({ type: 'cut', target: { x: 100, y: 50 }, zoom: 2 }) expect(cam.cam).toEqual({ x: 100, y: 50, zoom: 2 }) }) it('pan 在 duration 内单调逼近目标,中点介于起止(验收 6)', () => { const cam = createCamera({ getViewport: vp }) cam.applyCommand({ type: 'cut', target: { x: 0, y: 0 }, zoom: 1 }) cam.applyCommand({ type: 'pan', target: { x: 1000, y: 0 }, duration: 1000, easing: 'linear' }) cam.update(500) expect(cam.cam.x).toBeGreaterThan(0) expect(cam.cam.x).toBeLessThan(1000) expect(cam.cam.x).toBeCloseTo(500, 6) // linear 中点 cam.update(500) expect(cam.cam.x).toBeCloseTo(1000, 6) }) it('zoom 补间逼近目标(验收 6)', () => { const cam = createCamera({ getViewport: vp }) cam.applyCommand({ type: 'cut', zoom: 1 }) cam.applyCommand({ type: 'zoom', zoom: 3, duration: 1000, easing: 'linear' }) cam.update(1000) expect(cam.cam.zoom).toBeCloseTo(3, 6) }) it('follow 阻尼跟随帧率无关:不同 dt 切分到同总时长结果一致(验收 6)', () => { const lookup = () => ({ x: 1000, y: 0 }) const makeFollow = () => { const c = createCamera({ getViewport: vp }) c.applyCommand({ type: 'cut', target: { x: 0, y: 0 }, zoom: 1 }) c.applyCommand({ type: 'follow', follow: 'hero' }, lookup) return c } const cA = makeFollow() for (let i = 0; i < 100; i++) cA.update(10) // 100×10ms const cB = makeFollow() for (let i = 0; i < 20; i++) cB.update(50) // 20×50ms(同 1000ms 总时长) expect(cA.cam.x).toBeCloseTo(cB.cam.x, 0) // 帧率无关,容差内一致 }) it('shake 随时间衰减、duration 后归零(验收 6)', () => { let seq = [0.9, 0.9, 0.9, 0.9, 0.9, 0.9] let i = 0 const cam = createCamera({ getViewport: vp, random: () => seq[i++ % seq.length] }) cam.applyCommand({ type: 'shake', magnitude: 20, duration: 100 }) cam.update(10) const s1 = Math.abs(cam.getRenderState().shakeX) cam.update(50) const s2 = Math.abs(cam.getRenderState().shakeX) expect(s2).toBeLessThan(s1) // 衰减 cam.update(100) // 过 duration const rs = cam.getRenderState() expect(rs.shakeX).toBe(0) expect(rs.shakeY).toBe(0) }) it('reset 归零跟随/补间/抖动', () => { const cam = createCamera({ getViewport: vp }) cam.applyCommand({ type: 'pan', target: { x: 500, y: 0 }, duration: 1000 }) cam.reset({ x: 7, y: 8 }) expect(cam.tween).toBeNull() expect(cam.cam).toEqual({ x: 7, y: 8, zoom: 1 }) }) it('getRenderState 只产状态,含视口尺寸(验收 6:只产状态)', () => { const cam = createCamera({ getViewport: vp }) cam.applyCommand({ type: 'cut', target: { x: 3, y: 4 }, zoom: 1.5 }) cam.update(16) const rs = cam.getRenderState() expect(rs).toMatchObject({ x: 3, y: 4, zoom: 1.5, viewW: 1920, viewH: 1080 }) expect(typeof rs.shakeX).toBe('number') }) })