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{ 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();} });