Files

42 lines
3.1 KiB
TypeScript

import test from 'node:test';
import assert from 'node:assert/strict';
import R from '@dimforge/rapier3d-compat';
import { CharacterMotor } from '../engine/character.ts';
await R.init();
function setup(floors: {x:number,y:number,z:number,w:number,h:number,d:number}[]) {
const world=new R.World({x:0,y:-24,z:0});world.timestep=1/60;
for(const f of floors){const b=world.createRigidBody(R.RigidBodyDesc.fixed().setTranslation(f.x,f.y,f.z));world.createCollider(R.ColliderDesc.cuboid(f.w/2,f.h/2,f.d/2),b);}
const body=world.createRigidBody(R.RigidBodyDesc.kinematicPositionBased().setTranslation(0,.95,0));
const col=world.createCollider(R.ColliderDesc.capsule(.6,.3),body),c=world.createCharacterController(.025);
c.enableAutostep(.25,.2,true);
const motor=new CharacterMotor(body,col,c,{gravity:24,snapDistance:.12},R);
const tick=(n=1)=>{for(let i=0;i<n;i++){motor.step(1/60);world.step();}};
tick(20);return {world,body,motor,tick};
}
test('Character jump has a ballistic arc and lands; ceiling stops upward velocity',()=>{
const h=setup([{x:0,y:-.5,z:0,w:30,h:1,d:30}]);
try{assert.ok(h.motor.grounded);const y=h.body.translation().y;
h.motor.set({y:10});let peak=y,air=false;
for(let i=0;i<90;i++){h.tick();peak=Math.max(peak,h.body.translation().y);air||=!h.motor.grounded;}
assert.ok(air);assert.ok(peak-y>1.8&&peak-y<2.2,`rise ${peak-y}`);assert.ok(h.motor.grounded);assert.ok(Math.abs(h.body.translation().y-y)<.06);
const b=h.world.createRigidBody(R.RigidBodyDesc.fixed().setTranslation(0,2.6,0));h.world.createCollider(R.ColliderDesc.cuboid(2,.1,2),b);h.world.step();
h.motor.set({y:10});h.tick(10);assert.ok(h.motor.velocity.y<=0);assert.ok(h.body.translation().y<1.7);
}finally{h.world.free();}
});
test('Swept dash cannot pass through a thin wall; reports wall normal',()=>{
const h=setup([{x:0,y:-.5,z:0,w:30,h:1,d:30},{x:0,y:2,z:-3,w:10,h:5,d:.1}]);
try{h.motor.set({z:-24});h.tick(30);assert.ok(h.body.translation().z>-2.7);assert.ok(h.motor.contacts.some(c=>c.normal[2]>.9));
h.motor.teleport([0,4,-1]);assert.equal(h.motor.velocity.z,0);assert.equal(h.motor.grounded,false);assert.equal(h.motor.contacts.length,0);
}finally{h.world.free();}
});
test('A four metre gap requires a jump; low-gravity wall travel preserves clearance',()=>{
const floors=[{x:0,y:-.5,z:3,w:10,h:1,d:10},{x:0,y:-.5,z:-11,w:10,h:1,d:10}];
const h=setup(floors);
try{h.motor.set({z:-10});h.tick(32);assert.ok(h.body.translation().y<.5,'walking must fall into gap');
h.motor.teleport([0,.95,-1]);h.tick(2);h.motor.set({z:-10,y:10});h.tick(60);assert.ok(h.motor.grounded,'jump lands on second roof');assert.ok(h.body.translation().z<-6);
const b=h.world.createRigidBody(R.RigidBodyDesc.fixed().setTranslation(2,3,-15));h.world.createCollider(R.ColliderDesc.cuboid(.2,6,15),b);h.world.step();
h.motor.teleport([1.45,5,-9]);h.motor.set({x:1.4,z:-10,y:-.7,gravityScale:.08});h.tick(45);
assert.ok(h.body.translation().x<1.51);assert.ok(h.body.translation().z<-16);assert.ok(h.body.translation().y>3.8);assert.ok(h.motor.contacts.some(c=>c.normal[0]<-.9));
}finally{h.world.free();}
});