@newkrok/nape-js 3.31.0 → 3.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1,2 @@
1
- import {aa,ka,Y,ba,I,$,fa,ea,da,sa,ra,oa,pa,ma,na,qa,ga,ja,ia,ca,ha,U as U$1}from'../chunk-ZNBQE3PX.js';var at=1;function b(t){return {x:t.x,y:t.y}}function ut(t){if(t==null)return null;try{let o=JSON.stringify(t);return o==="{}"?null:JSON.parse(o)}catch{return null}}function mt(t){return {elasticity:t.elasticity,dynamicFriction:t.dynamicFriction,staticFriction:t.staticFriction,density:t.density,rollingFriction:t.rollingFriction}}function yt(t){return {collisionGroup:t.collisionGroup,collisionMask:t.collisionMask,sensorGroup:t.sensorGroup,sensorMask:t.sensorMask,fluidGroup:t.fluidGroup,fluidMask:t.fluidMask}}function ft(t){if(t==null)return null;let o=t.gravity;return {density:t.density,viscosity:t.viscosity,gravity:o!=null?b(o):null}}function bt(t){let o=mt(t.material),n=yt(t.filter),e=t.fluidEnabled,r=e?ft(t.fluidProperties):null,a=t.sensorEnabled;if(t.isCircle()){let i=t,s=t.localCOM;return {type:"circle",radius:i.radius,localCOM:b(s),material:o,filter:n,sensorEnabled:a,fluidEnabled:e,fluidProperties:r}}else if(t.isCapsule()){let i=t,s=t.localCOM;return {type:"capsule",width:i.width,height:i.height,localCOM:b(s),material:o,filter:n,sensorEnabled:a,fluidEnabled:e,fluidProperties:r}}else {let s=t.localVerts,l=[],u=s.length;for(let m=0;m<u;m++)l.push(b(s.at(m)));return {type:"polygon",localVerts:l,material:o,filter:n,sensorEnabled:a,fluidEnabled:e,fluidProperties:r}}}var Ft={1:"STATIC",2:"DYNAMIC",3:"KINEMATIC"},Dt={0:"DEFAULT",1:"FIXED",2:"FIXED_GROUP"},ht={0:"DEFAULT",1:"FIXED",2:"FIXED_GROUP"},Mt={0:"DEFAULT",1:"FIXED",2:"SCALED"};function gt(t,o){let n=t.zpp_inner,e=Dt[n.massMode]??"DEFAULT",r=ht[n.inertiaMode]??"DEFAULT",a=Mt[n.gravMassMode]??"DEFAULT",i=[],s=t.shapes,l=s.length;for(let u=0;u<l;u++)i.push(bt(s.at(u)));return {id:o,type:Ft[n.type]??"DYNAMIC",position:b(t.position),rotation:t.rotation,velocity:b(t.velocity),angularVel:t.angularVel,kinematicVel:b(t.kinematicVel),kinAngVel:t.kinAngVel,surfaceVel:b(t.surfaceVel),force:b(t.force),torque:n.type===2?t.torque:0,massMode:e,mass:e==="FIXED"?n.cmass:null,inertiaMode:r,inertia:r==="FIXED"?n.cinertia:null,gravMassMode:a,gravMassScale:n.gravMassScale,allowMovement:t.allowMovement,allowRotation:t.allowRotation,bullet:t.isBullet,shapes:i,userData:ut(n.userData)}}function B(t,o,n,e){let r=t.zpp_inner,a=n!=null?o.get(n.zpp_inner.id)??null:null,i=e!=null?o.get(e.zpp_inner.id)??null:null;return {body1Id:a,body2Id:i,active:r.active,ignore:r.ignore,stiff:r.stiff,frequency:r.frequency,damping:r.damping,maxForce:r.maxForce,maxError:r.maxError,breakUnderForce:r.breakUnderForce,breakUnderError:r.breakUnderError,removeOnBreak:r.removeOnBreak,userData:ut(r.userData)}}function Ct(t,o){switch(t.constructor?.name??""){case "PivotJoint":{let e=t;return {...B(t,o,e.body1,e.body2),type:"PivotJoint",anchor1:b(e.anchor1),anchor2:b(e.anchor2)}}case "DistanceJoint":{let e=t;return {...B(t,o,e.body1,e.body2),type:"DistanceJoint",anchor1:b(e.anchor1),anchor2:b(e.anchor2),jointMin:e.jointMin,jointMax:e.jointMax}}case "AngleJoint":{let e=t;return {...B(t,o,e.body1,e.body2),type:"AngleJoint",jointMin:e.jointMin,jointMax:e.jointMax,ratio:e.ratio}}case "MotorJoint":{let e=t;return {...B(t,o,e.body1,e.body2),type:"MotorJoint",rate:e.rate,ratio:e.ratio}}case "LineJoint":{let e=t;return {...B(t,o,e.body1,e.body2),type:"LineJoint",anchor1:b(e.anchor1),anchor2:b(e.anchor2),direction:b(e.direction),jointMin:e.jointMin,jointMax:e.jointMax}}case "PulleyJoint":{let e=t;return {...B(t,o,e.body1,e.body2),type:"PulleyJoint",anchor1:b(e.anchor1),anchor2:b(e.anchor2),anchor3:b(e.anchor3),anchor4:b(e.anchor4),jointMin:e.jointMin,jointMax:e.jointMax,ratio:e.ratio}}case "WeldJoint":{let e=t;return {...B(t,o,e.body1,e.body2),type:"WeldJoint",anchor1:b(e.anchor1),anchor2:b(e.anchor2),phase:e.phase}}default:return null}}function wt(t){let o=[],n=new Map,e=new Map;function r(d){let f=d.zpp_inner.id;if(e.has(f))return;let p=o.length;n.set(d,p),e.set(f,p),o.push(gt(d,p));}let a=t.bodies,i=a.length;for(let d=0;d<i;d++)r(a.at(d));let s=t.compounds,l=s.length;for(let d=0;d<l;d++){let p=s.at(d).bodies,g=p.length;for(let I=0;I<g;I++)r(p.at(I));}let u=[],m=new Map;function D(d){if(m.has(d))return;let f=Ct(d,e);f!=null&&(m.set(d,u.length),u.push(f));}let M=t.constraints,S=M.length;for(let d=0;d<S;d++)D(M.at(d));for(let d=0;d<l;d++){let p=s.at(d).constraints,g=p.length;for(let I=0;I<g;I++)D(p.at(I));}let A=[];for(let d=0;d<l;d++){let f=s.at(d),p=[],g=f.bodies,I=g.length;for(let J=0;J<I;J++){let st=g.at(J),O=n.get(st);O!=null&&p.push(O);}let C=[],x=f.constraints,w=x.length;for(let J=0;J<w;J++){let st=x.at(J),O=m.get(st);O!=null&&C.push(O);}A.push({bodyIds:p,constraintIndices:C,childIndices:[]});}let c=t.zpp_inner.bphase.is_sweep?"SWEEP_AND_PRUNE":"DYNAMIC_AABB_TREE",F=t.gravity;return {version:1,gravity:b(F),worldLinearDrag:t.worldLinearDrag,worldAngularDrag:t.worldAngularDrag,sortContacts:t.sortContacts,deterministic:t.deterministic,broadphase:c,bodies:o,constraints:u,compounds:A}}function P(t){return I.get(t.x,t.y)}function v(t){return I.weak(t.x,t.y)}function vt(t){return new ca(t.elasticity,t.dynamicFriction,t.staticFriction,t.density,t.rollingFriction)}function At(t){let o=new ha;return o.collisionGroup=t.collisionGroup,o.collisionMask=t.collisionMask,o.sensorGroup=t.sensorGroup,o.sensorMask=t.sensorMask,o.fluidGroup=t.fluidGroup,o.fluidMask=t.fluidMask,o}function It(t){let o=new U$1(t.density,t.viscosity);return t.gravity!=null&&(o.gravity=P(t.gravity)),o}function Jt(t){let o=vt(t.material),n=At(t.filter),e;if(t.type==="circle"){let r=P(t.localCOM);e=new ga(t.radius,r,o,n);}else if(t.type==="capsule"){let r=P(t.localCOM);e=new ja(t.width,t.height,r,o,n);}else {let r=t.localVerts.map(a=>P(a));e=new ia(r,o,n);}return e.sensorEnabled=t.sensorEnabled,e.fluidEnabled=t.fluidEnabled,t.fluidEnabled&&t.fluidProperties!=null&&(e.fluidProperties=It(t.fluidProperties)),e}function St(t){let o=t.type==="STATIC"?aa.STATIC:t.type==="KINEMATIC"?aa.KINEMATIC:aa.DYNAMIC,n=new $(o,v(t.position));n.rotation=t.rotation,t.type!=="STATIC"&&(n.velocity=P(t.velocity),n.angularVel=t.angularVel),n.kinematicVel=P(t.kinematicVel),n.kinAngVel=t.kinAngVel,n.surfaceVel=P(t.surfaceVel),t.type==="DYNAMIC"&&(n.force=P(t.force),n.torque=t.torque),t.massMode==="FIXED"&&t.mass!=null?n.mass=t.mass:t.massMode==="DEFAULT"&&(n.massMode=fa.DEFAULT),t.inertiaMode==="FIXED"&&t.inertia!=null?n.inertia=t.inertia:t.inertiaMode==="DEFAULT"&&(n.inertiaMode=ea.DEFAULT),t.gravMassMode==="SCALED"?(n.gravMassMode=da.SCALED,n.gravMassScale=t.gravMassScale):t.gravMassMode==="FIXED"&&(n.gravMassMode=da.FIXED),n.allowMovement=t.allowMovement,n.allowRotation=t.allowRotation,n.isBullet=t.bullet;for(let e of t.shapes)Jt(e).body=n;return t.userData!=null&&Object.assign(n.userData,t.userData),n}function U(t,o){t.active=o.active,t.ignore=o.ignore,t.stiff=o.stiff,t.frequency=o.frequency,t.damping=o.damping,t.maxForce=o.maxForce,t.maxError=o.maxError,t.breakUnderForce=o.breakUnderForce,t.breakUnderError=o.breakUnderError,t.removeOnBreak=o.removeOnBreak,o.userData!=null&&Object.assign(t.userData,o.userData);}function xt(t,o){let n=t.body1Id!=null?o[t.body1Id]??null:null,e=t.body2Id!=null?o[t.body2Id]??null:null;switch(t.type){case "PivotJoint":{let r=new qa(n,e,v(t.anchor1),v(t.anchor2));return U(r,t),r}case "DistanceJoint":{let r=new na(n,e,v(t.anchor1),v(t.anchor2),t.jointMin,t.jointMax);return U(r,t),r}case "AngleJoint":{let r=new ma(n,e,t.jointMin,t.jointMax,t.ratio);return U(r,t),r}case "MotorJoint":{let r=new pa(n,e,t.rate,t.ratio);return U(r,t),r}case "LineJoint":{let r=new oa(n,e,v(t.anchor1),v(t.anchor2),v(t.direction),t.jointMin,t.jointMax);return U(r,t),r}case "PulleyJoint":{let r=new ra(n,e,null,null,v(t.anchor1),v(t.anchor2),v(t.anchor3),v(t.anchor4),t.jointMin,t.jointMax,t.ratio);return U(r,t),r}case "WeldJoint":{let r=new sa(n,e,v(t.anchor1),v(t.anchor2),t.phase);return U(r,t),r}}}function Et(t){if(t.version!==1)throw new Error(`nape-js serialization: unsupported snapshot version ${t.version} (expected ${1})`);let o=t.broadphase==="SWEEP_AND_PRUNE"?ka.SWEEP_AND_PRUNE:ka.DYNAMIC_AABB_TREE,n=new Y(v(t.gravity),o);n.worldLinearDrag=t.worldLinearDrag,n.worldAngularDrag=t.worldAngularDrag,n.sortContacts=t.sortContacts,n.deterministic=t.deterministic??false;let e=t.bodies.map(St),r=t.constraints.map(s=>xt(s,e)),a=new Set,i=new Set;for(let s of t.compounds){let l=new ba;for(let u of s.bodyIds)e[u].compound=l,a.add(u);for(let u of s.constraintIndices)r[u].compound=l,i.add(u);l.space=n;}for(let s=0;s<e.length;s++)a.has(s)||(e[s].space=n);for(let s=0;s<r.length;s++)i.has(s)||(r[s].space=n);return n}var rt=class{constructor(o=4096){this.pos=0;this.buf=new ArrayBuffer(o),this.view=new DataView(this.buf);}ensure(o){let n=this.pos+o;if(n<=this.buf.byteLength)return;let e=this.buf.byteLength;for(;e<n;)e*=2;let r=new ArrayBuffer(e);new Uint8Array(r).set(new Uint8Array(this.buf)),this.buf=r,this.view=new DataView(this.buf);}writeUint8(o){this.ensure(1),this.view.setUint8(this.pos,o),this.pos+=1;}writeUint16(o){this.ensure(2),this.view.setUint16(this.pos,o,true),this.pos+=2;}writeUint32(o){this.ensure(4),this.view.setUint32(this.pos,o,true),this.pos+=4;}writeInt32(o){this.ensure(4),this.view.setInt32(this.pos,o,true),this.pos+=4;}writeFloat64(o){this.ensure(8),this.view.setFloat64(this.pos,o,true),this.pos+=8;}writeBool(o){this.writeUint8(o?1:0);}finish(){return new Uint8Array(this.buf,0,this.pos)}};var Pt=1312903237,_=2,Bt=0,Ut=1,kt=2,Tt=3,Nt=4,Vt=5,Ot=6;function _t(t,o){t.writeFloat64(o.elasticity),t.writeFloat64(o.dynamicFriction),t.writeFloat64(o.staticFriction),t.writeFloat64(o.density),t.writeFloat64(o.rollingFriction);}function Lt(t,o){t.writeInt32(o.collisionGroup),t.writeInt32(o.collisionMask),t.writeInt32(o.sensorGroup),t.writeInt32(o.sensorMask),t.writeInt32(o.fluidGroup),t.writeInt32(o.fluidMask);}function Rt(t,o){if(o==null)return;t.writeFloat64(o.density),t.writeFloat64(o.viscosity);let n=o.gravity;t.writeBool(n!=null),n!=null&&(t.writeFloat64(n.x),t.writeFloat64(n.y));}function jt(t,o){if(o.isCircle()){t.writeUint8(0);let a=o;t.writeFloat64(a.radius);let i=o.localCOM;t.writeFloat64(i.x),t.writeFloat64(i.y);}else if(o.isCapsule()){t.writeUint8(2);let a=o;t.writeFloat64(a.width),t.writeFloat64(a.height);let i=o.localCOM;t.writeFloat64(i.x),t.writeFloat64(i.y);}else {t.writeUint8(1);let i=o.localVerts,s=i.length;t.writeUint16(s);for(let l=0;l<s;l++){let u=i.at(l);t.writeFloat64(u.x),t.writeFloat64(u.y);}}_t(t,o.material),Lt(t,o.filter);let n=o.fluidEnabled,e=n&&o.fluidProperties!=null,r=(o.sensorEnabled?1:0)|(n?2:0)|(e?4:0);t.writeUint8(r),e&&Rt(t,o.fluidProperties);}function Gt(t,o){let n=o.zpp_inner;t.writeUint8(n.type),t.writeFloat64(o.position.x),t.writeFloat64(o.position.y),t.writeFloat64(o.rotation),t.writeFloat64(o.velocity.x),t.writeFloat64(o.velocity.y),t.writeFloat64(o.angularVel),t.writeFloat64(o.kinematicVel.x),t.writeFloat64(o.kinematicVel.y),t.writeFloat64(o.kinAngVel),t.writeFloat64(o.surfaceVel.x),t.writeFloat64(o.surfaceVel.y),t.writeFloat64(o.force.x),t.writeFloat64(o.force.y),t.writeFloat64(n.type===2?o.torque:0),t.writeUint8(n.massMode),t.writeFloat64(n.massMode===1?n.cmass:0),t.writeUint8(n.inertiaMode),t.writeFloat64(n.inertiaMode===1?n.cinertia:0),t.writeUint8(n.gravMassMode),t.writeFloat64(n.gravMassScale);let e=(o.allowMovement?1:0)|(o.allowRotation?2:0)|(o.isBullet?4:0);t.writeUint8(e);let r=o.shapes,a=r.length;t.writeUint16(a);for(let i=0;i<a;i++)jt(t,r.at(i));}var pt={PivotJoint:Bt,DistanceJoint:Ut,AngleJoint:kt,MotorJoint:Tt,LineJoint:Nt,PulleyJoint:Vt,WeldJoint:Ot};function k(t,o,n,e,r){let a=e!=null?n.get(e.zpp_inner.id)??-1:-1,i=r!=null?n.get(r.zpp_inner.id)??-1:-1;t.writeInt32(a),t.writeInt32(i);let s=o.zpp_inner,l=(s.active?1:0)|(s.ignore?2:0)|(s.stiff?4:0)|(s.breakUnderForce?8:0)|(s.breakUnderError?16:0)|(s.removeOnBreak?32:0);t.writeUint8(l),t.writeFloat64(s.frequency),t.writeFloat64(s.damping),t.writeFloat64(s.maxForce),t.writeFloat64(s.maxError);}function Wt(t,o,n){let e=o.constructor?.name??"",r=pt[e];if(r===void 0)return false;switch(t.writeUint8(r),e){case "PivotJoint":{let a=o;k(t,o,n,a.body1,a.body2),t.writeFloat64(a.anchor1.x),t.writeFloat64(a.anchor1.y),t.writeFloat64(a.anchor2.x),t.writeFloat64(a.anchor2.y);break}case "DistanceJoint":{let a=o;k(t,o,n,a.body1,a.body2),t.writeFloat64(a.anchor1.x),t.writeFloat64(a.anchor1.y),t.writeFloat64(a.anchor2.x),t.writeFloat64(a.anchor2.y),t.writeFloat64(a.jointMin),t.writeFloat64(a.jointMax);break}case "AngleJoint":{let a=o;k(t,o,n,a.body1,a.body2),t.writeFloat64(a.jointMin),t.writeFloat64(a.jointMax),t.writeFloat64(a.ratio);break}case "MotorJoint":{let a=o;k(t,o,n,a.body1,a.body2),t.writeFloat64(a.rate),t.writeFloat64(a.ratio);break}case "LineJoint":{let a=o;k(t,o,n,a.body1,a.body2),t.writeFloat64(a.anchor1.x),t.writeFloat64(a.anchor1.y),t.writeFloat64(a.anchor2.x),t.writeFloat64(a.anchor2.y),t.writeFloat64(a.direction.x),t.writeFloat64(a.direction.y),t.writeFloat64(a.jointMin),t.writeFloat64(a.jointMax);break}case "PulleyJoint":{let a=o;k(t,o,n,a.body1,a.body2),t.writeFloat64(a.anchor1.x),t.writeFloat64(a.anchor1.y),t.writeFloat64(a.anchor2.x),t.writeFloat64(a.anchor2.y),t.writeFloat64(a.anchor3.x),t.writeFloat64(a.anchor3.y),t.writeFloat64(a.anchor4.x),t.writeFloat64(a.anchor4.y),t.writeFloat64(a.jointMin),t.writeFloat64(a.jointMax),t.writeFloat64(a.ratio);break}case "WeldJoint":{let a=o;k(t,o,n,a.body1,a.body2),t.writeFloat64(a.anchor1.x),t.writeFloat64(a.anchor1.y),t.writeFloat64(a.anchor2.x),t.writeFloat64(a.anchor2.y),t.writeFloat64(a.phase);break}}return true}function Yt(t){let o=new rt,n=[],e=new Map,r=new Map;function a(c){let F=c.zpp_inner.id;if(e.has(F))return;let d=n.length;r.set(c,d),e.set(F,d),n.push(c);}let i=t.bodies,s=i.length;for(let c=0;c<s;c++)a(i.at(c));let l=t.compounds,u=l.length;for(let c=0;c<u;c++){let d=l.at(c).bodies,f=d.length;for(let p=0;p<f;p++)a(d.at(p));}let m=[],D=new Map;function M(c){if(D.has(c))return;let F=c.constructor?.name??"";pt[F]!==void 0&&(D.set(c,m.length),m.push(c));}let S=t.constraints,A=S.length;for(let c=0;c<A;c++)M(S.at(c));for(let c=0;c<u;c++){let d=l.at(c).constraints,f=d.length;for(let p=0;p<f;p++)M(d.at(p));}o.writeUint32(Pt),o.writeUint16(_),o.writeUint32(n.length),o.writeUint32(m.length),o.writeUint32(u);let h=t.gravity;o.writeFloat64(h.x),o.writeFloat64(h.y),o.writeFloat64(t.worldLinearDrag),o.writeFloat64(t.worldAngularDrag),o.writeBool(t.sortContacts),o.writeBool(t.deterministic),o.writeUint8(t.zpp_inner.bphase.is_sweep?0:1);for(let c=0;c<n.length;c++)Gt(o,n[c]);for(let c=0;c<m.length;c++)Wt(o,m[c],e);for(let c=0;c<u;c++){let F=l.at(c),d=F.bodies,f=d.length;o.writeUint16(f);for(let C=0;C<f;C++){let x=d.at(C),w=r.get(x)??0;o.writeUint32(w);}let p=F.constraints,g=p.length,I=0;for(let C=0;C<g;C++){let x=p.at(C);D.has(x)&&I++;}o.writeUint16(I);for(let C=0;C<g;C++){let x=p.at(C),w=D.get(x);w!=null&&o.writeUint32(w);}o.writeUint16(0);}return o.finish()}var it=class{constructor(o){this.pos=0;this.view=new DataView(o.buffer,o.byteOffset,o.byteLength);}readUint8(){let o=this.view.getUint8(this.pos);return this.pos+=1,o}readUint16(){let o=this.view.getUint16(this.pos,true);return this.pos+=2,o}readUint32(){let o=this.view.getUint32(this.pos,true);return this.pos+=4,o}readInt32(){let o=this.view.getInt32(this.pos,true);return this.pos+=4,o}readFloat64(){let o=this.view.getFloat64(this.pos,true);return this.pos+=8,o}readBool(){return this.readUint8()!==0}};var dt=1312903237,Xt=0,qt=1,zt=2,Ht=3,$t=4,Kt=5,Qt=6,Zt={1:aa.STATIC,2:aa.DYNAMIC,3:aa.KINEMATIC};function lt(t){let o=t.readFloat64(),n=t.readFloat64(),e=t.readFloat64(),r=t.readFloat64(),a=t.readFloat64();return new ca(o,n,e,r,a)}function ct(t){let o=new ha;return o.collisionGroup=t.readInt32(),o.collisionMask=t.readInt32(),o.sensorGroup=t.readInt32(),o.sensorMask=t.readInt32(),o.fluidGroup=t.readInt32(),o.fluidMask=t.readInt32(),o}function to(t){let o=t.readFloat64(),n=t.readFloat64(),e=new U$1(o,n);return t.readBool()&&(e.gravity=I.get(t.readFloat64(),t.readFloat64())),e}function oo(t){let o=t.readUint8(),n;if(o===0){let a=t.readFloat64(),i=t.readFloat64(),s=t.readFloat64(),l=lt(t),u=ct(t);n=new ga(a,I.weak(i,s),l,u);}else if(o===2){let a=t.readFloat64(),i=t.readFloat64(),s=t.readFloat64(),l=t.readFloat64(),u=lt(t),m=ct(t);n=new ja(a,i,I.weak(s,l),u,m);}else {let a=t.readUint16(),i=[];for(let u=0;u<a;u++)i.push(I.get(t.readFloat64(),t.readFloat64()));let s=lt(t),l=ct(t);n=new ia(i,s,l);}let e=t.readUint8();return n.sensorEnabled=(e&1)!==0,n.fluidEnabled=(e&2)!==0,(e&4)!==0&&(n.fluidProperties=to(t)),n}function eo(t){let o=t.readUint8(),n=Zt[o]??aa.DYNAMIC,e=t.readFloat64(),r=t.readFloat64(),a=t.readFloat64(),i=new $(n,I.weak(e,r));i.rotation=a;let s=t.readFloat64(),l=t.readFloat64(),u=t.readFloat64();o!==1&&(i.velocity=I.get(s,l),i.angularVel=u),i.kinematicVel=I.get(t.readFloat64(),t.readFloat64()),i.kinAngVel=t.readFloat64(),i.surfaceVel=I.get(t.readFloat64(),t.readFloat64());let m=t.readFloat64(),D=t.readFloat64(),M=t.readFloat64();o===2&&(i.force=I.get(m,D),i.torque=M);let S=t.readUint8(),A=t.readFloat64();S===1&&A!==0?i.mass=A:S===0&&(i.massMode=fa.DEFAULT);let h=t.readUint8(),c=t.readFloat64();h===1&&c!==0?i.inertia=c:h===0&&(i.inertiaMode=ea.DEFAULT);let F=t.readUint8(),d=t.readFloat64();F===2?(i.gravMassMode=da.SCALED,i.gravMassScale=d):F===1&&(i.gravMassMode=da.FIXED);let f=t.readUint8();i.allowMovement=(f&1)!==0,i.allowRotation=(f&2)!==0,i.isBullet=(f&4)!==0;let p=t.readUint16();for(let g=0;g<p;g++)oo(t).body=i;return i}function no(t){let o=t.readInt32(),n=t.readInt32(),e=t.readUint8(),r=t.readFloat64(),a=t.readFloat64(),i=t.readFloat64(),s=t.readFloat64();return {body1Id:o,body2Id:n,active:(e&1)!==0,ignore:(e&2)!==0,stiff:(e&4)!==0,breakUnderForce:(e&8)!==0,breakUnderError:(e&16)!==0,removeOnBreak:(e&32)!==0,frequency:r,damping:a,maxForce:i,maxError:s}}function T(t,o){t.active=o.active,t.ignore=o.ignore,t.stiff=o.stiff,t.frequency=o.frequency,t.damping=o.damping,t.maxForce=o.maxForce,t.maxError=o.maxError,t.breakUnderForce=o.breakUnderForce,t.breakUnderError=o.breakUnderError,t.removeOnBreak=o.removeOnBreak;}function ao(t,o){let n=t.readUint8(),e=no(t),r=e.body1Id>=0?o[e.body1Id]??null:null,a=e.body2Id>=0?o[e.body2Id]??null:null;switch(n){case Xt:{let i=t.readFloat64(),s=t.readFloat64(),l=t.readFloat64(),u=t.readFloat64(),m=new qa(r,a,I.weak(i,s),I.weak(l,u));return T(m,e),m}case qt:{let i=t.readFloat64(),s=t.readFloat64(),l=t.readFloat64(),u=t.readFloat64(),m=t.readFloat64(),D=t.readFloat64(),M=new na(r,a,I.weak(i,s),I.weak(l,u),m,D);return T(M,e),M}case zt:{let i=t.readFloat64(),s=t.readFloat64(),l=t.readFloat64(),u=new ma(r,a,i,s,l);return T(u,e),u}case Ht:{let i=t.readFloat64(),s=t.readFloat64(),l=new pa(r,a,i,s);return T(l,e),l}case $t:{let i=t.readFloat64(),s=t.readFloat64(),l=t.readFloat64(),u=t.readFloat64(),m=t.readFloat64(),D=t.readFloat64(),M=t.readFloat64(),S=t.readFloat64(),A=new oa(r,a,I.weak(i,s),I.weak(l,u),I.weak(m,D),M,S);return T(A,e),A}case Kt:{let i=t.readFloat64(),s=t.readFloat64(),l=t.readFloat64(),u=t.readFloat64(),m=t.readFloat64(),D=t.readFloat64(),M=t.readFloat64(),S=t.readFloat64(),A=t.readFloat64(),h=t.readFloat64(),c=t.readFloat64(),F=new ra(r,a,null,null,I.weak(i,s),I.weak(l,u),I.weak(m,D),I.weak(M,S),A,h,c);return T(F,e),F}case Qt:{let i=t.readFloat64(),s=t.readFloat64(),l=t.readFloat64(),u=t.readFloat64(),m=t.readFloat64(),D=new sa(r,a,I.weak(i,s),I.weak(l,u),m);return T(D,e),D}default:throw new Error(`nape-js binary: unknown constraint type tag ${n}`)}}function ro(t){let o=new it(t),n=o.readUint32();if(n!==dt)throw new Error(`nape-js binary: invalid magic bytes 0x${n.toString(16)} (expected 0x${dt.toString(16)})`);let e=o.readUint16();if(e!==_)throw new Error(`nape-js binary: unsupported version ${e} (expected ${_})`);let r=o.readUint32(),a=o.readUint32(),i=o.readUint32(),s=o.readFloat64(),l=o.readFloat64(),u=o.readFloat64(),m=o.readFloat64(),D=o.readBool(),M=o.readBool(),A=o.readUint8()===0?ka.SWEEP_AND_PRUNE:ka.DYNAMIC_AABB_TREE,h=new Y(I.weak(s,l),A);h.worldLinearDrag=u,h.worldAngularDrag=m,h.sortContacts=D,h.deterministic=M;let c=new Array(r);for(let p=0;p<r;p++)c[p]=eo(o);let F=new Array(a);for(let p=0;p<a;p++)F[p]=ao(o,c);let d=new Set,f=new Set;for(let p=0;p<i;p++){let g=new ba,I=o.readUint16();for(let w=0;w<I;w++){let J=o.readUint32();c[J].compound=g,d.add(J);}let C=o.readUint16();for(let w=0;w<C;w++){let J=o.readUint32();F[J].compound=g,f.add(J);}let x=o.readUint16();for(let w=0;w<x;w++)o.readUint32();g.space=h;}for(let p=0;p<r;p++)d.has(p)||(c[p].space=h);for(let p=0;p<a;p++)f.has(p)||(F[p].space=h);return h}export{_ as BINARY_SNAPSHOT_VERSION,at as SNAPSHOT_VERSION,ro as spaceFromBinary,Et as spaceFromJSON,Yt as spaceToBinary,wt as spaceToJSON};//# sourceMappingURL=index.js.map
1
+ export{a as BINARY_SNAPSHOT_VERSION,c as spaceFromBinary,b as spaceToBinary}from'../chunk-GPGT7DYQ.js';import {ka,Y,ba,I,aa,$,fa,ea,da,sa,ra,oa,pa,ma,na,qa,ga,ja,ia,ca,ha,U}from'../chunk-263GQ5UK.js';var J=1;function s(t){return {x:t.x,y:t.y}}function Q(t){if(t==null)return null;try{let a=JSON.stringify(t);return a==="{}"?null:JSON.parse(a)}catch{return null}}function it(t){return {elasticity:t.elasticity,dynamicFriction:t.dynamicFriction,staticFriction:t.staticFriction,density:t.density,rollingFriction:t.rollingFriction}}function st(t){return {collisionGroup:t.collisionGroup,collisionMask:t.collisionMask,sensorGroup:t.sensorGroup,sensorMask:t.sensorMask,fluidGroup:t.fluidGroup,fluidMask:t.fluidMask}}function lt(t){if(t==null)return null;let a=t.gravity;return {density:t.density,viscosity:t.viscosity,gravity:a!=null?s(a):null}}function ct(t){let a=it(t.material),n=st(t.filter),e=t.fluidEnabled,o=e?lt(t.fluidProperties):null,l=t.sensorEnabled;if(t.isCircle()){let c=t,r=t.localCOM;return {type:"circle",radius:c.radius,localCOM:s(r),material:a,filter:n,sensorEnabled:l,fluidEnabled:e,fluidProperties:o}}else if(t.isCapsule()){let c=t,r=t.localCOM;return {type:"capsule",width:c.width,height:c.height,localCOM:s(r),material:a,filter:n,sensorEnabled:l,fluidEnabled:e,fluidProperties:o}}else {let r=t.localVerts,m=[],u=r.length;for(let b=0;b<u;b++)m.push(s(r.at(b)));return {type:"polygon",localVerts:m,material:a,filter:n,sensorEnabled:l,fluidEnabled:e,fluidProperties:o}}}var ut={1:"STATIC",2:"DYNAMIC",3:"KINEMATIC"},pt={0:"DEFAULT",1:"FIXED",2:"FIXED_GROUP"},mt={0:"DEFAULT",1:"FIXED",2:"FIXED_GROUP"},Dt={0:"DEFAULT",1:"FIXED",2:"SCALED"};function yt(t,a){let n=t.zpp_inner,e=pt[n.massMode]??"DEFAULT",o=mt[n.inertiaMode]??"DEFAULT",l=Dt[n.gravMassMode]??"DEFAULT",c=[],r=t.shapes,m=r.length;for(let u=0;u<m;u++)c.push(ct(r.at(u)));return {id:a,type:ut[n.type]??"DYNAMIC",position:s(t.position),rotation:t.rotation,velocity:s(t.velocity),angularVel:t.angularVel,kinematicVel:s(t.kinematicVel),kinAngVel:t.kinAngVel,surfaceVel:s(t.surfaceVel),force:s(t.force),torque:n.type===2?t.torque:0,massMode:e,mass:e==="FIXED"?n.cmass:null,inertiaMode:o,inertia:o==="FIXED"?n.cinertia:null,gravMassMode:l,gravMassScale:n.gravMassScale,allowMovement:t.allowMovement,allowRotation:t.allowRotation,bullet:t.isBullet,shapes:c,userData:Q(n.userData)}}function h(t,a,n,e){let o=t.zpp_inner,l=n!=null?a.get(n.zpp_inner.id)??null:null,c=e!=null?a.get(e.zpp_inner.id)??null:null;return {body1Id:l,body2Id:c,active:o.active,ignore:o.ignore,stiff:o.stiff,frequency:o.frequency,damping:o.damping,maxForce:o.maxForce,maxError:o.maxError,breakUnderForce:o.breakUnderForce,breakUnderError:o.breakUnderError,removeOnBreak:o.removeOnBreak,userData:Q(o.userData)}}function ft(t,a){switch(t.constructor?.name??""){case "PivotJoint":{let e=t;return {...h(t,a,e.body1,e.body2),type:"PivotJoint",anchor1:s(e.anchor1),anchor2:s(e.anchor2)}}case "DistanceJoint":{let e=t;return {...h(t,a,e.body1,e.body2),type:"DistanceJoint",anchor1:s(e.anchor1),anchor2:s(e.anchor2),jointMin:e.jointMin,jointMax:e.jointMax}}case "AngleJoint":{let e=t;return {...h(t,a,e.body1,e.body2),type:"AngleJoint",jointMin:e.jointMin,jointMax:e.jointMax,ratio:e.ratio}}case "MotorJoint":{let e=t;return {...h(t,a,e.body1,e.body2),type:"MotorJoint",rate:e.rate,ratio:e.ratio}}case "LineJoint":{let e=t;return {...h(t,a,e.body1,e.body2),type:"LineJoint",anchor1:s(e.anchor1),anchor2:s(e.anchor2),direction:s(e.direction),jointMin:e.jointMin,jointMax:e.jointMax}}case "PulleyJoint":{let e=t;return {...h(t,a,e.body1,e.body2),type:"PulleyJoint",anchor1:s(e.anchor1),anchor2:s(e.anchor2),anchor3:s(e.anchor3),anchor4:s(e.anchor4),jointMin:e.jointMin,jointMax:e.jointMax,ratio:e.ratio}}case "WeldJoint":{let e=t;return {...h(t,a,e.body1,e.body2),type:"WeldJoint",anchor1:s(e.anchor1),anchor2:s(e.anchor2),phase:e.phase}}default:return null}}function dt(t){let a=[],n=new Map,e=new Map;function o(i){let y=i.zpp_inner.id;if(e.has(y))return;let D=a.length;n.set(i,D),e.set(y,D),a.push(yt(i,D));}let l=t.bodies,c=l.length;for(let i=0;i<c;i++)o(l.at(i));let r=t.compounds,m=r.length;for(let i=0;i<m;i++){let D=r.at(i).bodies,C=D.length;for(let f=0;f<C;f++)o(D.at(f));}let u=[],b=new Map;function I(i){if(b.has(i))return;let y=ft(i,e);y!=null&&(b.set(i,u.length),u.push(y));}let x=t.constraints,Z=x.length;for(let i=0;i<Z;i++)I(x.at(i));for(let i=0;i<m;i++){let D=r.at(i).constraints,C=D.length;for(let f=0;f<C;f++)I(D.at(f));}let V=[];for(let i=0;i<m;i++){let y=r.at(i),D=[],C=y.bodies,f=C.length;for(let M=0;M<f;M++){let F=C.at(M),S=n.get(F);S!=null&&D.push(S);}let B=[],w=y.constraints,nt=w.length;for(let M=0;M<nt;M++){let F=w.at(M),S=b.get(F);S!=null&&B.push(S);}V.push({bodyIds:D,constraintIndices:B,childIndices:[]});}let tt=t.zpp_inner.bphase.is_sweep?"SWEEP_AND_PRUNE":"DYNAMIC_AABB_TREE",et=t.gravity;return {version:1,gravity:s(et),worldLinearDrag:t.worldLinearDrag,worldAngularDrag:t.worldAngularDrag,sortContacts:t.sortContacts,deterministic:t.deterministic,broadphase:tt,bodies:a,constraints:u,compounds:V}}function d(t){return I.get(t.x,t.y)}function p(t){return I.weak(t.x,t.y)}function bt(t){return new ca(t.elasticity,t.dynamicFriction,t.staticFriction,t.density,t.rollingFriction)}function Mt(t){let a=new ha;return a.collisionGroup=t.collisionGroup,a.collisionMask=t.collisionMask,a.sensorGroup=t.sensorGroup,a.sensorMask=t.sensorMask,a.fluidGroup=t.fluidGroup,a.fluidMask=t.fluidMask,a}function ht(t){let a=new U(t.density,t.viscosity);return t.gravity!=null&&(a.gravity=d(t.gravity)),a}function gt(t){let a=bt(t.material),n=Mt(t.filter),e;if(t.type==="circle"){let o=d(t.localCOM);e=new ga(t.radius,o,a,n);}else if(t.type==="capsule"){let o=d(t.localCOM);e=new ja(t.width,t.height,o,a,n);}else {let o=t.localVerts.map(l=>d(l));e=new ia(o,a,n);}return e.sensorEnabled=t.sensorEnabled,e.fluidEnabled=t.fluidEnabled,t.fluidEnabled&&t.fluidProperties!=null&&(e.fluidProperties=ht(t.fluidProperties)),e}function Ct(t){let a=t.type==="STATIC"?aa.STATIC:t.type==="KINEMATIC"?aa.KINEMATIC:aa.DYNAMIC,n=new $(a,p(t.position));n.rotation=t.rotation,t.type!=="STATIC"&&(n.velocity=d(t.velocity),n.angularVel=t.angularVel),n.kinematicVel=d(t.kinematicVel),n.kinAngVel=t.kinAngVel,n.surfaceVel=d(t.surfaceVel),t.type==="DYNAMIC"&&(n.force=d(t.force),n.torque=t.torque),t.massMode==="FIXED"&&t.mass!=null?n.mass=t.mass:t.massMode==="DEFAULT"&&(n.massMode=fa.DEFAULT),t.inertiaMode==="FIXED"&&t.inertia!=null?n.inertia=t.inertia:t.inertiaMode==="DEFAULT"&&(n.inertiaMode=ea.DEFAULT),t.gravMassMode==="SCALED"?(n.gravMassMode=da.SCALED,n.gravMassScale=t.gravMassScale):t.gravMassMode==="FIXED"&&(n.gravMassMode=da.FIXED),n.allowMovement=t.allowMovement,n.allowRotation=t.allowRotation,n.isBullet=t.bullet;for(let e of t.shapes)gt(e).body=n;return t.userData!=null&&Object.assign(n.userData,t.userData),n}function g(t,a){t.active=a.active,t.ignore=a.ignore,t.stiff=a.stiff,t.frequency=a.frequency,t.damping=a.damping,t.maxForce=a.maxForce,t.maxError=a.maxError,t.breakUnderForce=a.breakUnderForce,t.breakUnderError=a.breakUnderError,t.removeOnBreak=a.removeOnBreak,a.userData!=null&&Object.assign(t.userData,a.userData);}function St(t,a){let n=t.body1Id!=null?a[t.body1Id]??null:null,e=t.body2Id!=null?a[t.body2Id]??null:null;switch(t.type){case "PivotJoint":{let o=new qa(n,e,p(t.anchor1),p(t.anchor2));return g(o,t),o}case "DistanceJoint":{let o=new na(n,e,p(t.anchor1),p(t.anchor2),t.jointMin,t.jointMax);return g(o,t),o}case "AngleJoint":{let o=new ma(n,e,t.jointMin,t.jointMax,t.ratio);return g(o,t),o}case "MotorJoint":{let o=new pa(n,e,t.rate,t.ratio);return g(o,t),o}case "LineJoint":{let o=new oa(n,e,p(t.anchor1),p(t.anchor2),p(t.direction),t.jointMin,t.jointMax);return g(o,t),o}case "PulleyJoint":{let o=new ra(n,e,null,null,p(t.anchor1),p(t.anchor2),p(t.anchor3),p(t.anchor4),t.jointMin,t.jointMax,t.ratio);return g(o,t),o}case "WeldJoint":{let o=new sa(n,e,p(t.anchor1),p(t.anchor2),t.phase);return g(o,t),o}}}function Et(t){if(t.version!==1)throw new Error(`nape-js serialization: unsupported snapshot version ${t.version} (expected ${1})`);let a=t.broadphase==="SWEEP_AND_PRUNE"?ka.SWEEP_AND_PRUNE:ka.DYNAMIC_AABB_TREE,n=new Y(p(t.gravity),a);n.worldLinearDrag=t.worldLinearDrag,n.worldAngularDrag=t.worldAngularDrag,n.sortContacts=t.sortContacts,n.deterministic=t.deterministic??false;let e=t.bodies.map(Ct),o=t.constraints.map(r=>St(r,e)),l=new Set,c=new Set;for(let r of t.compounds){let m=new ba;for(let u of r.bodyIds)e[u].compound=m,l.add(u);for(let u of r.constraintIndices)o[u].compound=m,c.add(u);m.space=n;}for(let r=0;r<e.length;r++)l.has(r)||(e[r].space=n);for(let r=0;r<o.length;r++)c.has(r)||(o[r].space=n);return n}export{J as SNAPSHOT_VERSION,Et as spaceFromJSON,dt as spaceToJSON};//# sourceMappingURL=index.js.map
2
2
  //# sourceMappingURL=index.js.map
package/llms-full.txt CHANGED
@@ -4,8 +4,8 @@
4
4
 
5
5
  - **npm**: `npm install @newkrok/nape-js`
6
6
  - **GitHub**: https://github.com/NewKrok/nape-js
7
- - **API docs**: https://newkrok.github.io/nape-js/api/index.html
8
- - **Demos**: https://newkrok.github.io/nape-js/examples.html
7
+ - **API docs**: https://napejs.org/api/index.html
8
+ - **Demos**: https://napejs.org/examples.html
9
9
  - **License**: MIT
10
10
  - **Version**: 3.30.0
11
11
  - **Cookbook**: https://github.com/NewKrok/nape-js/blob/master/docs/guides/cookbook.md
@@ -1424,8 +1424,9 @@ interactionType(shape1: Shape, shape2: Shape): InteractionType | null
1424
1424
 
1425
1425
  | Value | Description |
1426
1426
  |-------|-------------|
1427
+ | `Broadphase.DYNAMIC_AABB_TREE` | Default. Good for dynamic scenes with varied object sizes |
1427
1428
  | `Broadphase.SWEEP_AND_PRUNE` | Good for many objects with little movement |
1428
- | `Broadphase.DYNAMIC_AABB_TREE` | Good for dynamic scenes with varied object sizes |
1429
+ | `Broadphase.SPATIAL_HASH` | Best for dense, uniform-size scenes |
1429
1430
 
1430
1431
  ---
1431
1432
 
@@ -1653,6 +1654,97 @@ const grid2 = ldtkLayerToGrid(ldtkJson.levels[0].layerInstances[0]); // LDtk Int
1653
1654
  - `tiledLayerToGrid` / `ldtkLayerToGrid` only consume the data + dimension fields — they don't depend on the full Tiled/LDtk JSON shape, so you can pass a hand-shaped subset.
1654
1655
  - For destructible terrain, rebuild the body when the grid changes (`body.shapes.clear()` then call `buildTilemapBody(grid, { ..., body })`).
1655
1656
 
1657
+ ### ParticleEmitter
1658
+
1659
+ Physics-aware particle emitter — a pooled, lifecycle-managed swarm of dynamic bodies. Each particle is a real `Body` with a `Circle` or `Polygon` shape, so it collides with the world, reacts to forces / gravity / fluids, and triggers callbacks like any other body. Body pool is reused across spawns — zero allocation in the steady state.
1660
+
1661
+ ```typescript
1662
+ import { ParticleEmitter, ParticleEmitterGroup, Body, Vec2 } from "@newkrok/nape-js";
1663
+
1664
+ // Volcano: continuous lava cone.
1665
+ const volcano = new ParticleEmitter({
1666
+ space,
1667
+ origin: new Vec2(400, 100), // Vec2 OR Body (auto-tracking)
1668
+ spawn: { kind: "arc", radius: 6, angleStart: -Math.PI, angleEnd: 0 },
1669
+ velocity: {
1670
+ kind: "cone", // "fixed" | "cone" | "radial" | custom
1671
+ direction: -Math.PI / 2,
1672
+ spread: Math.PI / 5,
1673
+ speedMin: 320,
1674
+ speedMax: 520,
1675
+ },
1676
+ rate: 90, // particles/sec — fractional rates work
1677
+ maxParticles: 600,
1678
+ lifetimeMin: 4,
1679
+ lifetimeMax: 7,
1680
+ particleRadius: 2.5,
1681
+ selfCollision: false, // particles don't collide with each other
1682
+ });
1683
+
1684
+ // Each frame, BEFORE space.step():
1685
+ volcano.update(1 / 60);
1686
+ space.step(1 / 60);
1687
+
1688
+ // Manual burst:
1689
+ volcano.emit(40); // returns Body[]
1690
+
1691
+ // Periodic burst (5 particles every 0.5s):
1692
+ const fireworks = new ParticleEmitter({
1693
+ space, origin: pad,
1694
+ velocity: { kind: "radial", speedMin: 200, speedMax: 480 },
1695
+ burstCount: 30,
1696
+ burstInterval: 0.5,
1697
+ });
1698
+
1699
+ // Bullet emitter with collision callback (for shooter / damage logic):
1700
+ const bulletCb = new CbType();
1701
+ const bullets = new ParticleEmitter({
1702
+ space,
1703
+ origin: playerBody,
1704
+ velocity: { kind: "fixed", value: new Vec2(700, 0) }, // mutated per-shot
1705
+ particleRadius: 2,
1706
+ particleCbType: bulletCb,
1707
+ onCollide: (bullet, other) => {
1708
+ // Damage `other`, then defer the bullet's death (we're inside a
1709
+ // collision callback — never mutate the space synchronously).
1710
+ bullets.requestKill(bullet);
1711
+ },
1712
+ });
1713
+
1714
+ // Compose multiple emitters:
1715
+ const group = new ParticleEmitterGroup();
1716
+ group.add(volcano);
1717
+ group.add(fireworks);
1718
+ group.update(1 / 60); // runs every member
1719
+ ```
1720
+
1721
+ **Spawn patterns** (positions are sampled in emitter-local space, then translated by `origin`):
1722
+ - `{ kind: "point" }` — always at origin
1723
+ - `{ kind: "rect", width, height }` — uniform inside an axis-aligned box
1724
+ - `{ kind: "circle", radius, hollow? }` — uniform inside a disk; `hollow: true` samples the rim only
1725
+ - `{ kind: "arc", radius, angleStart, angleEnd }` — points on a circular arc
1726
+ - `{ kind: "custom", sample: (rng) => Vec2 }` — user-supplied sampler
1727
+
1728
+ **Velocity patterns:**
1729
+ - `{ kind: "fixed", value }` — every particle gets the same vector (mutate `value` for aimed shooting)
1730
+ - `{ kind: "cone", direction, spread, speedMin, speedMax }` — uniformly random direction inside a cone of half-width `spread` rad
1731
+ - `{ kind: "radial", speedMin, speedMax }` — outward from the spawn point relative to `origin`
1732
+ - `{ kind: "custom", sample: (rng, localPos) => Vec2 }` — user-supplied sampler
1733
+
1734
+ **Lifecycle hooks:**
1735
+ - `onSpawn(state, body)` — fired after the body is in the space
1736
+ - `onUpdate(body, age, dt)` — every `update()` for each live particle
1737
+ - `onDeath(body, reason)` — `reason` is `"lifetime" | "manual" | "bounds"`
1738
+ - `onCollide(body, other)` — requires `particleCbType`. Use `requestKill(body)` to defer death until the next `update()`
1739
+
1740
+ **Gotchas:**
1741
+ - Call `update(dt)` **before** `space.step(dt)`, with the same `dt`. The pattern matches `RadialGravityField.apply()`.
1742
+ - `space.gravity` and `body.force` still apply — particles are normal dynamic bodies. Clear `body.force` per-frame if you stack a custom field on top.
1743
+ - `overflowPolicy: "drop-oldest"` (default) kills the oldest live particle to make room for a new spawn — bullets always come out. Set to `"drop-new"` to protect already-visible particles instead.
1744
+ - `selfCollision: false` (default) generates a self-excluding `InteractionFilter` so particles don't waste cycles colliding with each other. Override by passing your own `particleFilter`.
1745
+ - `requestKill(body)` is the only safe way to kill a particle from inside a collision callback. Direct `body.space = null` mid-step is undefined behaviour.
1746
+ - `destroy()` removes every body (live + pooled) from the space and unregisters the collision listener; subsequent `update()` / `emit()` calls throw.
1747
+
1656
1748
  ### TriggerZone
1657
1749
 
1658
1750
  Sensor-based zone with `onEnter` / `onExit` callbacks — wraps the BEGIN/END `InteractionListener` plumbing so you don't have to wire it up by hand.
@@ -1981,3 +2073,133 @@ function render() {
1981
2073
  }
1982
2074
  render();
1983
2075
  ```
2076
+
2077
+ ---
2078
+
2079
+ ## Replay Sub-Package (`@newkrok/nape-js/replay`)
2080
+
2081
+ Record a deterministic simulation as `(initial snapshot, per-frame input log)`
2082
+ and replay it deterministically — same machine, another machine, days later.
2083
+ Built on `/serialization` plus `space.deterministic = true`. Tree-shakeable.
2084
+
2085
+ ```typescript
2086
+ import "@newkrok/nape-js"; // engine bootstrap
2087
+ import { Recorder, Player, encodeReplay, decodeReplay } from "@newkrok/nape-js/replay";
2088
+ ```
2089
+
2090
+ ### Recorder
2091
+
2092
+ ```typescript
2093
+ new Recorder<T>(space: Space, options?: { keyframeEvery?: number })
2094
+ ```
2095
+
2096
+ Captures the initial snapshot at construction and stores user-supplied input
2097
+ payloads via `recordFrame`. With `keyframeEvery > 0` (default `60`), also
2098
+ captures intermediate snapshots for fast scrub.
2099
+
2100
+ **Methods:**
2101
+ - `recordFrame(input?: T | null): void` — Log a payload at the current frame
2102
+ and advance. Pass `null` (or omit) for input-less frames; only non-null
2103
+ payloads are stored. Payloads are deep-cloned via JSON (primitives
2104
+ fast-pathed). Throws after `finish()`.
2105
+ - `finish(): Replay<T>` — Seal the recording and return an immutable result.
2106
+
2107
+ **Properties:**
2108
+ - `frame: number` — Frames recorded so far.
2109
+ - `finished: boolean` — True after `finish()`.
2110
+
2111
+ ### Player
2112
+
2113
+ ```typescript
2114
+ new Player<T>(replay: Replay<T>, applyInput?: ((input: T, space: Space, frame: number) => void) | null, options?: { dt?: number; velocityIterations?: number; positionIterations?: number })
2115
+ ```
2116
+
2117
+ Plays a recorded `Replay`. Owns its own `Space` deserialised from the initial
2118
+ snapshot.
2119
+
2120
+ **Lifecycle:**
2121
+ - `restore(): Space` — Restore initial snapshot. Idempotent (rewinds to frame 0).
2122
+ - `step(): void` — Apply the next recorded input via `applyInput`, then step
2123
+ physics by `dt`. Throws past end or before `restore()`.
2124
+ - `stepTo(frame: number): void` — Random-access seek. Forward jumps walk the
2125
+ log; backward jumps restore the latest keyframe ≤ target then step forward.
2126
+
2127
+ **Properties:**
2128
+ - `space: Space` — Active space (throws before `restore()`).
2129
+ - `frame: number` — Current frame index (0 = pre-step).
2130
+ - `frameCount: number` — Total frames in the replay.
2131
+ - `finished: boolean` — `frame >= frameCount`.
2132
+ - `applyInput` — Mutable; swap callback mid-playback.
2133
+
2134
+ ### encodeReplay / decodeReplay
2135
+
2136
+ ```typescript
2137
+ function encodeReplay<T>(replay: Replay<T>): Uint8Array
2138
+ function decodeReplay<T = unknown>(bytes: Uint8Array): Replay<T>
2139
+ ```
2140
+
2141
+ Compact binary format: magic `RPLY`, versioned, length-prefixed snapshots,
2142
+ UTF-8 JSON for input payloads. Round-trip preserves frame count, inputs, and
2143
+ keyframes byte-for-byte. Throws on bad magic or unsupported version.
2144
+
2145
+ ### validateDeterministicConfig
2146
+
2147
+ ```typescript
2148
+ function validateDeterministicConfig(space: Space): { ok: boolean; warnings: string[] }
2149
+ ```
2150
+
2151
+ Sanity-checks `space.deterministic = true` and friends. Pure inspection.
2152
+
2153
+ ### Replay type
2154
+
2155
+ ```typescript
2156
+ interface Replay<T> {
2157
+ readonly version: number;
2158
+ readonly initialSnapshot: Uint8Array;
2159
+ readonly inputs: ReadonlyArray<{ frame: number; payload: T }>;
2160
+ readonly keyframes: ReadonlyArray<{ frame: number; snapshot: Uint8Array }>;
2161
+ readonly frameCount: number;
2162
+ }
2163
+ ```
2164
+
2165
+ ### Determinism contract
2166
+
2167
+ Replay matches recording bit-close on the **same platform** when:
2168
+
2169
+ 1. `space.deterministic = true` is set on the recording space (and survives in
2170
+ the snapshot).
2171
+ 2. Both sides use a fixed `dt` and matching velocity/position iteration counts.
2172
+ 3. The user's `applyInput` is a pure function of `(input, space, frame)` — no
2173
+ `Math.random()`, no wall-clock reads, no closure mutations.
2174
+
2175
+ Cross-platform bit-exact replay is not currently supported (floating-point
2176
+ rounding differs across CPUs). `body.userData` is NOT preserved through binary
2177
+ snapshots — encode it into your input payload if needed.
2178
+
2179
+ ### Usage example
2180
+
2181
+ ```typescript
2182
+ import "@newkrok/nape-js";
2183
+ import { Recorder, Player, encodeReplay, decodeReplay } from "@newkrok/nape-js/replay";
2184
+
2185
+ type Input = { fire?: boolean };
2186
+
2187
+ // Record
2188
+ space.deterministic = true;
2189
+ const recorder = new Recorder<Input>(space, { keyframeEvery: 60 });
2190
+ for (let f = 0; f < 600; f++) {
2191
+ const input = readInput();
2192
+ recorder.recordFrame(input);
2193
+ if (input?.fire) ball.applyImpulse(new Vec2(0, -200));
2194
+ space.step(1 / 60);
2195
+ }
2196
+ const blob = encodeReplay(recorder.finish());
2197
+
2198
+ // Replay
2199
+ const replay = decodeReplay<Input>(blob);
2200
+ const player = new Player(replay, (input, sp) => {
2201
+ if (input.fire) sp.bodies.at(1).applyImpulse(new Vec2(0, -200));
2202
+ });
2203
+ player.restore();
2204
+ while (!player.finished) player.step();
2205
+ ```
package/llms.txt CHANGED
@@ -6,10 +6,11 @@ Install: `npm install @newkrok/nape-js`
6
6
 
7
7
  - [GitHub Repository](https://github.com/NewKrok/nape-js): Source code, issues, contributing guide
8
8
  - [npm Package](https://www.npmjs.com/package/@newkrok/nape-js): Published package with TypeScript declarations
9
- - [API Reference](https://newkrok.github.io/nape-js/api/index.html): Full TypeDoc-generated API docs
10
- - [Interactive Demos](https://newkrok.github.io/nape-js/examples.html): Live physics demos with source code
9
+ - [API Reference](https://napejs.org/api/index.html): Full TypeDoc-generated API docs
10
+ - [Interactive Demos](https://napejs.org/examples.html): Live physics demos with source code
11
11
  - [Full LLM Documentation](https://raw.githubusercontent.com/NewKrok/nape-js/master/packages/nape-js/llms-full.txt): Complete API reference in a single file
12
- - [Cookbook](https://github.com/NewKrok/nape-js/blob/master/docs/guides/cookbook.md): Task-oriented recipes (platformer, ragdoll, fluid, serialization, etc.)
12
+ - [Cookbook](https://github.com/NewKrok/nape-js/blob/master/docs/guides/cookbook.md): Task-oriented recipes (platformer, ragdoll, fluid, serialization, replay, etc.)
13
+ - [Replay Guide](https://github.com/NewKrok/nape-js/blob/master/docs/guides/replay-guide.md): Recorder + Player + binary encode/decode + determinism contract
13
14
  - [Troubleshooting](https://github.com/NewKrok/nape-js/blob/master/docs/guides/troubleshooting.md): Common problems and solutions
14
15
  - [Anti-Patterns](https://github.com/NewKrok/nape-js/blob/master/docs/guides/anti-patterns.md): Common mistakes to avoid
15
16
  - [PixiJS Integration](https://www.npmjs.com/package/@newkrok/nape-pixi) (`@newkrok/nape-pixi`): Sibling package — body/sprite sync, fixed-step render interpolation, debug draw, and off-thread physics bridge. PIXI v8.
@@ -36,91 +37,92 @@ function update() {
36
37
 
37
38
  ## Core Classes
38
39
 
39
- - [Space](https://newkrok.github.io/nape-js/api/classes/Space.html): Physics world — create with gravity, add bodies, call `step(dt)` to simulate. `space.deterministic = true` for same-platform reproducibility (multiplayer rollback/prediction)
40
- - [Body](https://newkrok.github.io/nape-js/api/classes/Body.html): Rigid body with position, velocity, rotation, mass, shapes list
41
- - [Vec2](https://newkrok.github.io/nape-js/api/classes/Vec2.html): 2D vector with pooling (`Vec2.get()`, `Vec2.weak()`) and arithmetic methods
42
- - [AABB](https://newkrok.github.io/nape-js/api/classes/AABB.html): Axis-aligned bounding box for spatial queries
40
+ - [Space](https://napejs.org/api/classes/Space.html): Physics world — create with gravity, add bodies, call `step(dt)` to simulate. `space.deterministic = true` for same-platform reproducibility (multiplayer rollback/prediction)
41
+ - [Body](https://napejs.org/api/classes/Body.html): Rigid body with position, velocity, rotation, mass, shapes list
42
+ - [Vec2](https://napejs.org/api/classes/Vec2.html): 2D vector with pooling (`Vec2.get()`, `Vec2.weak()`) and arithmetic methods
43
+ - [AABB](https://napejs.org/api/classes/AABB.html): Axis-aligned bounding box for spatial queries
43
44
 
44
45
  ## Shapes
45
46
 
46
- - [Circle](https://newkrok.github.io/nape-js/api/classes/Circle.html): Circular collision shape with radius
47
- - [Polygon](https://newkrok.github.io/nape-js/api/classes/Polygon.html): Convex polygon with factories: `Polygon.box()`, `Polygon.rect()`, `Polygon.regular()`
48
- - [Shape](https://newkrok.github.io/nape-js/api/classes/Shape.html): Base class — material, filter, sensor support, cbTypes
49
- - [Edge](https://newkrok.github.io/nape-js/api/classes/Edge.html): Read-only polygon edge with normal and projection data
47
+ - [Circle](https://napejs.org/api/classes/Circle.html): Circular collision shape with radius
48
+ - [Polygon](https://napejs.org/api/classes/Polygon.html): Convex polygon with factories: `Polygon.box()`, `Polygon.rect()`, `Polygon.regular()`
49
+ - [Shape](https://napejs.org/api/classes/Shape.html): Base class — material, filter, sensor support, cbTypes
50
+ - [Edge](https://napejs.org/api/classes/Edge.html): Read-only polygon edge with normal and projection data
50
51
 
51
52
  ## Physics Properties
52
53
 
53
- - [Material](https://newkrok.github.io/nape-js/api/classes/Material.html): Elasticity, friction, density — presets: `wood()`, `steel()`, `ice()`, `rubber()`, `glass()`, `sand()`
54
- - [FluidProperties](https://newkrok.github.io/nape-js/api/classes/FluidProperties.html): Density, viscosity for fluid interaction shapes
55
- - [InteractionFilter](https://newkrok.github.io/nape-js/api/classes/InteractionFilter.html): Bit-mask collision/sensor/fluid filtering
56
- - [InteractionGroup](https://newkrok.github.io/nape-js/api/classes/InteractionGroup.html): Group-based interaction management
54
+ - [Material](https://napejs.org/api/classes/Material.html): Elasticity, friction, density — presets: `wood()`, `steel()`, `ice()`, `rubber()`, `glass()`, `sand()`
55
+ - [FluidProperties](https://napejs.org/api/classes/FluidProperties.html): Density, viscosity for fluid interaction shapes
56
+ - [InteractionFilter](https://napejs.org/api/classes/InteractionFilter.html): Bit-mask collision/sensor/fluid filtering
57
+ - [InteractionGroup](https://napejs.org/api/classes/InteractionGroup.html): Group-based interaction management
57
58
 
58
59
  ## Constraints (Joints)
59
60
 
60
- - [PivotJoint](https://newkrok.github.io/nape-js/api/classes/PivotJoint.html): Pin two bodies at a shared anchor point
61
- - [DistanceJoint](https://newkrok.github.io/nape-js/api/classes/DistanceJoint.html): Constrain distance between two anchors to [min, max]
62
- - [WeldJoint](https://newkrok.github.io/nape-js/api/classes/WeldJoint.html): Fix relative position and rotation angle
63
- - [AngleJoint](https://newkrok.github.io/nape-js/api/classes/AngleJoint.html): Constrain relative angle to [min, max] with optional ratio
64
- - [MotorJoint](https://newkrok.github.io/nape-js/api/classes/MotorJoint.html): Drive relative angular velocity to a target rate
65
- - [LineJoint](https://newkrok.github.io/nape-js/api/classes/LineJoint.html): Constrain anchor to slide along a line direction
66
- - [PulleyJoint](https://newkrok.github.io/nape-js/api/classes/PulleyJoint.html): Constrain weighted sum of two distances (pulley/rope)
67
- - [Constraint](https://newkrok.github.io/nape-js/api/classes/Constraint.html): Base class — stiff/soft, frequency, damping, breaking, maxForce
61
+ - [PivotJoint](https://napejs.org/api/classes/PivotJoint.html): Pin two bodies at a shared anchor point
62
+ - [DistanceJoint](https://napejs.org/api/classes/DistanceJoint.html): Constrain distance between two anchors to [min, max]
63
+ - [WeldJoint](https://napejs.org/api/classes/WeldJoint.html): Fix relative position and rotation angle
64
+ - [AngleJoint](https://napejs.org/api/classes/AngleJoint.html): Constrain relative angle to [min, max] with optional ratio
65
+ - [MotorJoint](https://napejs.org/api/classes/MotorJoint.html): Drive relative angular velocity to a target rate
66
+ - [LineJoint](https://napejs.org/api/classes/LineJoint.html): Constrain anchor to slide along a line direction
67
+ - [PulleyJoint](https://napejs.org/api/classes/PulleyJoint.html): Constrain weighted sum of two distances (pulley/rope)
68
+ - [Constraint](https://napejs.org/api/classes/Constraint.html): Base class — stiff/soft, frequency, damping, breaking, maxForce
68
69
 
69
70
  ## Callbacks & Events
70
71
 
71
- - [InteractionListener](https://newkrok.github.io/nape-js/api/classes/InteractionListener.html): Listen for BEGIN/ONGOING/END collision, sensor, or fluid events
72
- - [BodyListener](https://newkrok.github.io/nape-js/api/classes/BodyListener.html): Listen for WAKE/SLEEP body state changes
73
- - [ConstraintListener](https://newkrok.github.io/nape-js/api/classes/ConstraintListener.html): Listen for BREAK constraint events
74
- - [PreListener](https://newkrok.github.io/nape-js/api/classes/PreListener.html): Pre-collision handler — filter or modify collisions before solving
75
- - [CbType](https://newkrok.github.io/nape-js/api/classes/CbType.html): Tag bodies/shapes/constraints for selective callback filtering
76
- - [CbEvent](https://newkrok.github.io/nape-js/api/classes/CbEvent.html): Event types — BEGIN, ONGOING, END, WAKE, SLEEP, BREAK, PRE
72
+ - [InteractionListener](https://napejs.org/api/classes/InteractionListener.html): Listen for BEGIN/ONGOING/END collision, sensor, or fluid events
73
+ - [BodyListener](https://napejs.org/api/classes/BodyListener.html): Listen for WAKE/SLEEP body state changes
74
+ - [ConstraintListener](https://napejs.org/api/classes/ConstraintListener.html): Listen for BREAK constraint events
75
+ - [PreListener](https://napejs.org/api/classes/PreListener.html): Pre-collision handler — filter or modify collisions before solving
76
+ - [CbType](https://napejs.org/api/classes/CbType.html): Tag bodies/shapes/constraints for selective callback filtering
77
+ - [CbEvent](https://napejs.org/api/classes/CbEvent.html): Event types — BEGIN, ONGOING, END, WAKE, SLEEP, BREAK, PRE
77
78
 
78
79
  ## Collision & Dynamics
79
80
 
80
- - [Arbiter](https://newkrok.github.io/nape-js/api/classes/Arbiter.html): Interaction data between two shapes (base class)
81
- - [CollisionArbiter](https://newkrok.github.io/nape-js/api/classes/CollisionArbiter.html): Collision contacts, normal, impulses, kinetic energy
82
- - [FluidArbiter](https://newkrok.github.io/nape-js/api/classes/FluidArbiter.html): Fluid overlap, buoyancy/drag impulses
83
- - [Contact](https://newkrok.github.io/nape-js/api/classes/Contact.html): Single contact point — position, penetration, friction
81
+ - [Arbiter](https://napejs.org/api/classes/Arbiter.html): Interaction data between two shapes (base class)
82
+ - [CollisionArbiter](https://napejs.org/api/classes/CollisionArbiter.html): Collision contacts, normal, impulses, kinetic energy
83
+ - [FluidArbiter](https://napejs.org/api/classes/FluidArbiter.html): Fluid overlap, buoyancy/drag impulses
84
+ - [Contact](https://napejs.org/api/classes/Contact.html): Single contact point — position, penetration, friction
84
85
 
85
86
  ## Geometry Utilities
86
87
 
87
- - [Ray](https://newkrok.github.io/nape-js/api/classes/Ray.html): Ray for raycasting queries with origin, direction, maxDistance
88
- - [Mat23](https://newkrok.github.io/nape-js/api/classes/Mat23.html): 2x3 affine transform matrix — rotation, translation, scale
89
- - [Geom](https://newkrok.github.io/nape-js/api/classes/Geom.html): Static utilities — distance queries, ray tests, polygon operations
90
- - [MarchingSquares](https://newkrok.github.io/nape-js/api/classes/MarchingSquares.html): Contour extraction from scalar fields
88
+ - [Ray](https://napejs.org/api/classes/Ray.html): Ray for raycasting queries with origin, direction, maxDistance
89
+ - [Mat23](https://napejs.org/api/classes/Mat23.html): 2x3 affine transform matrix — rotation, translation, scale
90
+ - [Geom](https://napejs.org/api/classes/Geom.html): Static utilities — distance queries, ray tests, polygon operations
91
+ - [MarchingSquares](https://napejs.org/api/classes/MarchingSquares.html): Contour extraction from scalar fields
91
92
 
92
93
  ## Destruction / Fracture
93
94
 
94
- - [fractureBody](https://newkrok.github.io/nape-js/api/functions/fractureBody.html): Shatter a polygon body into Voronoi fragments — `fractureBody(body, impactPoint, { fragmentCount?, explosionImpulse?, random? })`
95
- - [computeVoronoi](https://newkrok.github.io/nape-js/api/functions/computeVoronoi.html): Raw Voronoi diagram computation from point set
96
- - [generateFractureSites](https://newkrok.github.io/nape-js/api/functions/generateFractureSites.html): Generate random fracture site points within a polygon
95
+ - [fractureBody](https://napejs.org/api/functions/fractureBody.html): Shatter a polygon body into Voronoi fragments — `fractureBody(body, impactPoint, { fragmentCount?, explosionImpulse?, random? })`
96
+ - [computeVoronoi](https://napejs.org/api/functions/computeVoronoi.html): Raw Voronoi diagram computation from point set
97
+ - [generateFractureSites](https://napejs.org/api/functions/generateFractureSites.html): Generate random fracture site points within a polygon
97
98
 
98
99
  ## Helpers
99
100
 
100
101
  Higher-level building blocks layered on top of the engine — opt-in modules.
101
102
 
102
- - [CharacterController](https://newkrok.github.io/nape-js/api/classes/CharacterController.html): Velocity-based 2D platformer controller with ground/slope/wall raycasts, coyote-time, one-way platforms, moving-platform inheritance, and an overridable `down` direction (radial-gravity worlds)
103
- - [RadialGravityField](https://newkrok.github.io/nape-js/api/classes/RadialGravityField.html) / [RadialGravityFieldGroup](https://newkrok.github.io/nape-js/api/classes/RadialGravityFieldGroup.html): Point-source gravity field with `inverse-square` / `inverse` / `constant` / custom falloff, `maxRadius` / `softening` / `minRadius`, body filter, mass scaling — replaces hand-rolled `body.force = ...` loops for orbital, planet, and multi-body scenarios
103
+ - [CharacterController](https://napejs.org/api/classes/CharacterController.html): Velocity-based 2D platformer controller with ground/slope/wall raycasts, coyote-time, one-way platforms, moving-platform inheritance, and an overridable `down` direction (radial-gravity worlds)
104
+ - [RadialGravityField](https://napejs.org/api/classes/RadialGravityField.html) / [RadialGravityFieldGroup](https://napejs.org/api/classes/RadialGravityFieldGroup.html): Point-source gravity field with `inverse-square` / `inverse` / `constant` / custom falloff, `maxRadius` / `softening` / `minRadius`, body filter, mass scaling — replaces hand-rolled `body.force = ...` loops for orbital, planet, and multi-body scenarios
105
+ - [ParticleEmitter](https://napejs.org/api/classes/ParticleEmitter.html) / [ParticleEmitterGroup](https://napejs.org/api/classes/ParticleEmitterGroup.html): Physics-aware particle emitter — pooled `Body` swarm with continuous (`rate`) / periodic (`burstCount` + `burstInterval`) / manual (`emit(n)`) spawning, configurable spawn (`point` / `rect` / `circle` / `arc` / custom) and velocity (`fixed` / `cone` / `radial` / custom) patterns, deterministic RNG, world-space `bounds`, `selfCollision` toggle, lifecycle hooks (`onSpawn` / `onUpdate` / `onDeath` / `onCollide` with `requestKill`)
104
106
  - `buildTilemapBody` / `meshTilemap` / `tiledLayerToGrid` / `ldtkLayerToGrid`: Greedy-meshed collision body from a 2D tile grid (5–50× fewer shapes vs one-polygon-per-cell), with built-in parsers for Tiled JSON tile layers and LDtk IntGrid layers
105
- - [TriggerZone](https://newkrok.github.io/nape-js/api/classes/TriggerZone.html): Sensor zone with `onEnter` / `onExit` callbacks — wraps the BEGIN/END `InteractionListener` plumbing
106
- - [createConcaveBody](https://newkrok.github.io/nape-js/api/functions/createConcaveBody.html): Decomposes a concave outline into convex polygons and packs them into a single body
107
+ - [TriggerZone](https://napejs.org/api/classes/TriggerZone.html): Sensor zone with `onEnter` / `onExit` callbacks — wraps the BEGIN/END `InteractionListener` plumbing
108
+ - [createConcaveBody](https://napejs.org/api/functions/createConcaveBody.html): Decomposes a concave outline into convex polygons and packs them into a single body
107
109
 
108
110
  ## Enums
109
111
 
110
- - [BodyType](https://newkrok.github.io/nape-js/api/classes/BodyType.html): STATIC, DYNAMIC, KINEMATIC
111
- - [ShapeType](https://newkrok.github.io/nape-js/api/classes/ShapeType.html): CIRCLE, POLYGON
112
- - [Broadphase](https://newkrok.github.io/nape-js/api/classes/Broadphase.html): SWEEP_AND_PRUNE, DYNAMIC_AABB_TREE
113
- - [InteractionType](https://newkrok.github.io/nape-js/api/classes/InteractionType.html): COLLISION, SENSOR, FLUID, ANY
114
- - [ArbiterType](https://newkrok.github.io/nape-js/api/classes/ArbiterType.html): COLLISION, SENSOR, FLUID
112
+ - [BodyType](https://napejs.org/api/classes/BodyType.html): STATIC, DYNAMIC, KINEMATIC
113
+ - [ShapeType](https://napejs.org/api/classes/ShapeType.html): CIRCLE, POLYGON
114
+ - [Broadphase](https://napejs.org/api/classes/Broadphase.html): DYNAMIC_AABB_TREE (default), SWEEP_AND_PRUNE, SPATIAL_HASH
115
+ - [InteractionType](https://napejs.org/api/classes/InteractionType.html): COLLISION, SENSOR, FLUID, ANY
116
+ - [ArbiterType](https://napejs.org/api/classes/ArbiterType.html): COLLISION, SENSOR, FLUID
115
117
 
116
118
  ## Grouping & Hierarchy
117
119
 
118
- - [Compound](https://newkrok.github.io/nape-js/api/classes/Compound.html): Hierarchical grouping of bodies, constraints, and sub-compounds
119
- - [Interactor](https://newkrok.github.io/nape-js/api/classes/Interactor.html): Base class for Body, Shape, Compound — type casting, cbTypes
120
+ - [Compound](https://napejs.org/api/classes/Compound.html): Hierarchical grouping of bodies, constraints, and sub-compounds
121
+ - [Interactor](https://napejs.org/api/classes/Interactor.html): Base class for Body, Shape, Compound — type casting, cbTypes
120
122
 
121
123
  ## Collections
122
124
 
123
- - [NapeList](https://newkrok.github.io/nape-js/api/classes/NapeList.html): Generic iterable list with `for...of` support, add/remove/push/pop
125
+ - [NapeList](https://napejs.org/api/classes/NapeList.html): Generic iterable list with `for...of` support, add/remove/push/pop
124
126
 
125
127
  ## Web Worker (`@newkrok/nape-js/worker`)
126
128
 
@@ -128,3 +130,14 @@ Higher-level building blocks layered on top of the engine — opt-in modules.
128
130
  - `buildWorkerScript(napeUrl)`: Generate self-contained worker script for inline Blob or hosted file
129
131
  - `BodyTransform`: `{ x, y, rotation }` per-body transform read from shared buffer
130
132
  - `FLOATS_PER_BODY`, `HEADER_FLOATS`: Buffer layout constants
133
+
134
+ ## Replay (`@newkrok/nape-js/replay`)
135
+
136
+ Record a deterministic simulation and play it back. Built on `/serialization` + `space.deterministic = true`. Tree-shakeable; only loads when imported.
137
+
138
+ - [Recorder](https://napejs.org/api/classes/Recorder.html)`<T>`: Captures the initial snapshot at construction, then `recordFrame(input)` logs an input payload per frame. Optional `keyframeEvery` (default 60) records intermediate snapshots for fast scrub. `finish()` returns an immutable `Replay<T>`.
139
+ - [Player](https://napejs.org/api/classes/Player.html)`<T>`: Owns its own deserialised `Space`. `restore()` rewinds to frame 0; `step()` applies the next recorded input (via the user's `applyInput` callback) and steps physics; `stepTo(frame)` does random-access scrub (uses keyframes for backward jumps).
140
+ - [encodeReplay](https://napejs.org/api/functions/encodeReplay.html) / [decodeReplay](https://napejs.org/api/functions/decodeReplay.html): Compact binary format (magic `RPLY`, versioned, length-prefixed snapshots, UTF-8 JSON payloads). Round-trips byte-for-byte.
141
+ - [validateDeterministicConfig](https://napejs.org/api/functions/validateDeterministicConfig.html): Sanity-checks `space.deterministic = true` etc.; returns `{ ok, warnings }`.
142
+
143
+ **Determinism contract** — replay matches recording when (a) `space.deterministic = true`, (b) fixed `dt` and matching iteration counts on both sides, (c) the user's `applyInput` is a pure function of `(input, space, frame)`. Same-platform soft determinism only (floating-point rounding differs across CPUs).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newkrok/nape-js",
3
- "version": "3.31.0",
3
+ "version": "3.33.0",
4
4
  "description": "High-performance 2D physics engine for TypeScript & JavaScript — rigid bodies, constraints, fluid simulation, raycasting, and deterministic multiplayer. Tree-shakeable, zero dependencies.",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -49,6 +49,16 @@
49
49
  "default": "./dist/serialization/index.cjs"
50
50
  }
51
51
  },
52
+ "./replay": {
53
+ "import": {
54
+ "types": "./dist/replay/index.d.ts",
55
+ "default": "./dist/replay/index.js"
56
+ },
57
+ "require": {
58
+ "types": "./dist/replay/index.d.cts",
59
+ "default": "./dist/replay/index.cjs"
60
+ }
61
+ },
52
62
  "./worker": {
53
63
  "import": {
54
64
  "types": "./dist/worker/index.d.ts",
@@ -70,8 +80,8 @@
70
80
  }
71
81
  }
72
82
  },
73
- "llms": "https://newkrok.github.io/nape-js/llms.txt",
74
- "llmsFull": "https://newkrok.github.io/nape-js/llms-full.txt",
83
+ "llms": "https://napejs.org/llms.txt",
84
+ "llmsFull": "https://napejs.org/llms-full.txt",
75
85
  "files": [
76
86
  "dist",
77
87
  "!dist/**/*.map",
@@ -119,5 +129,5 @@
119
129
  "bugs": {
120
130
  "url": "https://github.com/NewKrok/nape-js/issues"
121
131
  },
122
- "homepage": "https://newkrok.github.io/nape-js/"
132
+ "homepage": "https://napejs.org/"
123
133
  }