SuperHQ Motion experiment: locomotion and action checkpoints, runtime, clips, evaluation
6adbbb1 verified Download src/action-controller.js from superhq-ai/motion: direct link, hf CLI and curl.
- Browser
- Download file 8.13 kB
-
https://huggingface.co/superhq-ai/motion/resolve/main/src/action-controller.js
- Command line
-
hf download hf://superhq-ai/motion/src/action-controller.js
-
curl -L -o action-controller.js https://huggingface.co/superhq-ai/motion/resolve/main/src/action-controller.js
8.13 kB
| import { decodeMotionPose } from './controller.js'; | |
| import { worldPositions } from './pose.js'; | |
| import { ActionStabilizer,plantToes } from './action-stabilizer.js'; | |
| const DT=1/60,ANTICIPATION=.24,EXTENSION=.50,LANDING=.55; | |
| const clamp=(x,a,b)=>Math.max(a,Math.min(b,x)); | |
| const vector=(x,n)=>(Array.isArray(x)||ArrayBuffer.isView(x))&&x.length===n&&Array.from(x).every(Number.isFinite); | |
| /** Stationary action pose controller. The host owns gravity and collisions. | |
| * Features require superhq-motion-actions-v2 weights. No custom skills or clips | |
| * are loaded by this runtime. MotionGameController composes these canonical | |
| * poses with locomotion and horizontal game physics. | |
| */ | |
| export class MotionActions { | |
| constructor(network,rig,{contract,phase=0,runtimeCorrections=true}={}) { | |
| if(contract!=='superhq-motion-actions-v2'||!Number.isFinite(phase))throw Error('Expected v2 action weights.'); | |
| this.network=network;this.rig=rig;this.runtimeCorrections=runtimeCorrections;this.reset({phase}); | |
| } | |
| reset({phase=0}={}) { | |
| if(!Number.isFinite(phase))throw Error('Invalid phase.'); | |
| this.phase=((phase%1)+1)%1;this.mode='stance';this.elapsed=0;this.airElapsed=0; | |
| this.depth=0;this.depthVelocity=0;this.goal=0;this.response=.65; | |
| this.wasGrounded=true;this.lastVerticalVelocity=0;this.takeoffSent=false;this.pendingJump=false; | |
| this.stabilizer=new ActionStabilizer();this.plantWeight=1;this.previousMode=null;this.goalChanged=false; | |
| const x=this.features();x[34]=0; | |
| const pose=decodeMotionPose(this.network.forward(x),this.rig); | |
| const world=worldPositions(pose,this.rig); | |
| this.anchors=[world[22].slice(),world[27].slice()];this.jumpAnchorMean=this.anchors[0].map((v,i)=>(v+this.anchors[1][i])/2); | |
| } | |
| setGoal({crouchDepth,responseSeconds=.65}) { | |
| if(!Number.isFinite(crouchDepth)||crouchDepth<0||crouchDepth>1|| | |
| !Number.isFinite(responseSeconds)||responseSeconds<.5||responseSeconds>.9)throw Error('Invalid crouch goal.'); | |
| this.goalChanged ||= crouchDepth!==this.goal||responseSeconds!==this.response; | |
| this.goal=crouchDepth;this.response=responseSeconds; | |
| } | |
| requestJump({horizontalSpeed=0}={}) { | |
| if(!Number.isFinite(horizontalSpeed)||horizontalSpeed<0)throw Error('Invalid movement speed.'); | |
| if(this.mode!=='stance'||!this.wasGrounded||this.depth>.05||this.goal>.05||horizontalSpeed>.05||this.pendingJump)return false; | |
| this.pendingJump=true;return true; | |
| } | |
| features() { | |
| const x=new Float32Array(48);x[3]=1;for(const j of [9,15,21])x[j]=1; | |
| x.set([Math.sin(this.phase*2*Math.PI),Math.cos(this.phase*2*Math.PI),.4],22);x[25]=1;x[26]=1;x[41]=1; | |
| return x; | |
| } | |
| step({position=[0,0,0],verticalVelocity=0,grounded=true}={}) { | |
| if(!vector(position,3)||!Number.isFinite(verticalVelocity)||typeof grounded!=='boolean')throw Error('Invalid host physics state.'); | |
| if(this.pendingJump){this.mode='anticipation';this.elapsed=0;this.takeoffSent=false;this.pendingJump=false;} | |
| if(!grounded&&this.wasGrounded) { | |
| this.mode=this.mode==='anticipation'?'extension':'air';this.airElapsed=0;this.elapsed=0; | |
| } | |
| if(grounded&&!this.wasGrounded) { | |
| this.mode='landing';this.elapsed=0;this.landingEntry=clamp(this.airElapsed/EXTENSION,0,1);this.impactVelocity=this.lastVerticalVelocity; | |
| } | |
| if(this.mode==='extension'&&this.airElapsed>=EXTENSION)this.mode='air'; | |
| if(this.mode==='landing'&&this.elapsed>=LANDING){this.mode='stance';this.depth=0;this.depthVelocity=0;} | |
| const x=this.features();let takeoffRequested=false; | |
| if(this.mode==='stance') { | |
| const omega=3.89/this.response,error=this.depth-this.goal,cross=this.depthVelocity+omega*error,decay=Math.exp(-omega*DT); | |
| this.depth=this.goal+(error+cross*DT)*decay;this.depthVelocity=(this.depthVelocity-omega*cross*DT)*decay; | |
| x[30]=this.depth;x[34]=1;x[38]=this.depthVelocity;x[39]=this.depth;x.set(this.anchors.flat(),42); | |
| } else { | |
| x.set(this.jumpAnchorMean,45); | |
| if(this.mode==='anticipation') { | |
| x[35]=1;x[38]=Math.min(this.elapsed,ANTICIPATION)/(ANTICIPATION+EXTENSION); | |
| if(this.elapsed>=ANTICIPATION&&!this.takeoffSent){takeoffRequested=true;this.takeoffSent=true;} | |
| } else if(this.mode==='extension') { | |
| x[35]=1;x[38]=(ANTICIPATION+this.airElapsed)/(ANTICIPATION+EXTENSION); | |
| } else if(this.mode==='air') { | |
| x[36]=1;x[38]=clamp((this.airElapsed-EXTENSION)/.25,0,1); | |
| } else { | |
| x[37]=1;x[38]=clamp(this.elapsed/LANDING,0,1);x[42]=this.landingEntry;x[43]=.38;x[44]=this.impactVelocity; | |
| } | |
| } | |
| x[40]=verticalVelocity;x[41]=Number(grounded); | |
| const output=this.network.forward(x),raw=decodeMotionPose(output,this.rig); | |
| let canonical=raw,correction={transition:null,feet:[]}; | |
| if(this.runtimeCorrections){ | |
| const changed=this.previousMode!==null&&(this.previousMode!==this.mode||this.wasGrounded!==grounded||this.goalChanged); | |
| let next=raw; | |
| if(changed){ | |
| const preview=x.slice(),phase=this.phase+.4*DT;preview[22]=Math.sin(phase*2*Math.PI);preview[23]=Math.cos(phase*2*Math.PI); | |
| if(this.mode==='stance'){ | |
| const omega=3.89/this.response,error=this.depth-this.goal,cross=this.depthVelocity+omega*error,decay=Math.exp(-omega*DT); | |
| preview[30]=preview[39]=this.goal+(error+cross*DT)*decay;preview[38]=(this.depthVelocity-omega*cross*DT)*decay; | |
| }else if(this.mode==='anticipation'||this.mode==='extension')preview[38]=Math.min(1,x[38]+DT/(ANTICIPATION+EXTENSION)); | |
| else if(this.mode==='air')preview[38]=Math.min(1,x[38]+DT/.25); | |
| else if(this.mode==='landing')preview[38]=Math.min(1,x[38]+DT/LANDING); | |
| next=decodeMotionPose(this.network.forward(preview),this.rig); | |
| } | |
| const filtered=this.stabilizer.apply(raw,next,changed);canonical=filtered.pose;correction.transition=filtered.correction; | |
| this.plantWeight+=(Number(this.mode==='stance'&&grounded)-this.plantWeight)*(1-Math.exp(-DT/.08)); | |
| if(!grounded)this.plantWeight=0; | |
| if(this.plantWeight>.001){const planted=plantToes(canonical,this.rig,this.anchors,this.plantWeight);canonical=planted.pose;correction.feet=planted.details;} | |
| } | |
| const pose={...canonical,rootPosition:canonical.rootPosition.map((v,i)=>v+position[i])}; | |
| const rawPose={...raw,rootPosition:raw.rootPosition.map((v,i)=>v+position[i])}; | |
| const result={...pose,rawPose,correction,input:x,mode:this.mode,takeoffRequested,depth:this.depth,phase:this.phase,hostPosition:Array.from(position)}; | |
| this.phase=(this.phase+.4*DT)%1;this.elapsed+=DT;if(!grounded)this.airElapsed+=DT; | |
| this.previousMode=this.mode;this.goalChanged=false;this.wasGrounded=grounded;this.lastVerticalVelocity=verticalVelocity; | |
| return result; | |
| } | |
| } | |
| /** Example host physics only. Games should supply their own collision state. */ | |
| export class TestPhysics { | |
| constructor({impulse=3.5,gravity=9.81,platformHeight=0}={}) { | |
| if(![impulse,gravity,platformHeight].every(Number.isFinite)||impulse<=0||gravity<=0||platformHeight<0)throw Error('Invalid test physics.'); | |
| this.impulse=impulse;this.gravity=gravity;this.initialPlatform=platformHeight;this.reset(); | |
| } | |
| reset(){this.platformHeight=this.initialPlatform;this.position=[0,0,0];this.velocity=0;this.grounded=true;this.takeoffs=0;this.landings=0;} | |
| state(){return {position:this.position.slice(),verticalVelocity:this.velocity,grounded:this.grounded};} | |
| /** Stand the host on a surface at height (used after a ledge climb). */ | |
| place(height){if(!Number.isFinite(height)||height<0)throw Error('Invalid ground height.');this.position=[0,height,0];this.velocity=0;this.grounded=true;this.platformHeight=height;} | |
| launch(){if(!this.grounded)return false;this.velocity=this.impulse;this.grounded=false;this.takeoffs++;return true;} | |
| step(){if(this.grounded)return;const previous=this.position[1];this.position[1]+=this.velocity*DT-.5*this.gravity*DT*DT;this.velocity-=this.gravity*DT; | |
| const floor=this.velocity<0&&previous>=this.platformHeight?this.platformHeight:0; | |
| if(this.position[1]<=floor&&this.velocity<0){this.position[1]=floor;this.velocity=0;this.grounded=true;this.landings++;}} | |
| } | |