From 7f7b1f7f29ff6bc815af80995ce8075271583522 Mon Sep 17 00:00:00 2001 From: OpenClaw Bot Date: Tue, 24 Feb 2026 04:06:37 +0000 Subject: [PATCH] test: add coverage for clampHorizontalSpeed and projectDirectionOnPlane --- src/movement/__tests__/MovementMath.test.ts | 62 ++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/src/movement/__tests__/MovementMath.test.ts b/src/movement/__tests__/MovementMath.test.ts index 49f9aaa..cfdbc46 100644 --- a/src/movement/__tests__/MovementMath.test.ts +++ b/src/movement/__tests__/MovementMath.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; import { Vector3 } from 'three'; -import { accelerate, applyFriction, clipVelocity } from '../MovementMath'; +import { accelerate, applyFriction, clipVelocity, clampHorizontalSpeed, projectDirectionOnPlane } from '../MovementMath'; describe('MovementMath', () => { it('accelerate increases speed toward wishdir up to addSpeed', () => { @@ -24,3 +24,63 @@ describe('MovementMath', () => { expect(clipped.y).toBeGreaterThanOrEqual(-1e-5); }); }); + +describe('clampHorizontalSpeed', () => { + it('does not modify velocity already under the limit', () => { + const vel = new Vector3(3, 5, 0); // horizontal speed = 3, under limit of 10 + const result = clampHorizontalSpeed(vel, 10); + expect(result.x).toBeCloseTo(3); + expect(result.z).toBeCloseTo(0); + expect(result.y).toBeCloseTo(5); // vertical unchanged + }); + + it('clamps horizontal speed to the limit when over', () => { + const vel = new Vector3(6, 0, 8); // horizontal speed = 10, limit = 5 + const result = clampHorizontalSpeed(vel, 5); + expect(Math.hypot(result.x, result.z)).toBeCloseTo(5); + expect(result.y).toBeCloseTo(0); // vertical unchanged + }); + + it('preserves direction when clamping', () => { + const vel = new Vector3(6, 2, 8); // horizontal speed = 10 + const result = clampHorizontalSpeed(vel, 5); + // Direction ratio x/z should be preserved + expect(result.x / result.z).toBeCloseTo(vel.x / vel.z); + }); + + it('handles zero vector without division by zero', () => { + const vel = new Vector3(0, 0, 0); + const result = clampHorizontalSpeed(vel, 10); + expect(result.x).toBeCloseTo(0); + expect(result.z).toBeCloseTo(0); + }); +}); + +describe('projectDirectionOnPlane', () => { + it('returns a normalized vector perpendicular to the normal', () => { + const dir = new Vector3(1, 1, 0).normalize(); + const normal = new Vector3(0, 1, 0); + const result = projectDirectionOnPlane(dir, normal); + // Result should be unit length + expect(result.length()).toBeCloseTo(1); + // Result should be perpendicular to the normal (dot product ≈ 0) + expect(result.dot(normal)).toBeCloseTo(0); + }); + + it('returns zero vector when direction is parallel to normal', () => { + const dir = new Vector3(0, 1, 0); // same direction as normal + const normal = new Vector3(0, 1, 0); + const result = projectDirectionOnPlane(dir, normal); + expect(result.length()).toBeCloseTo(0); + }); + + it('does not mutate input vectors', () => { + const dir = new Vector3(1, 1, 0).normalize(); + const normal = new Vector3(0, 1, 0); + const dirBefore = dir.clone(); + const normalBefore = normal.clone(); + projectDirectionOnPlane(dir, normal); + expect(dir.equals(dirBefore)).toBe(true); + expect(normal.equals(normalBefore)).toBe(true); + }); +});