Back to Crossfire
SOURCE / PINNED RELEASE

Made of little things.

Crossfire

Release
9614abe48623
Author-recorded commit
6f6c8299e2fb…
License
LICENSE
Author’s source reference
nostr://npub1n8ga89w8h6tvwamxusfyzexw8gjy84yxu9rxgnmk955cxtml4ujswzxydd/wss%3A%2F%2Fgit.napplet.soy%2F/n-347ab685aef

Archive hash verified: 34d5295141b419db. The source-to-build association is the author’s claim; it has not been independently rebuilt.

tests/browser/latency.mjs
const {chromium}=await import(process.env.PLAYWRIGHT_MODULE||'playwright-core');
import fs from 'node:fs';
const url=process.env.GAME_URL||'https://napplet.soy/r/e3712801078e012fabd55f5fdb9007c6194b093f3f5e4002ba69d31e67b6edfc';
fs.mkdirSync('.napplet-space/verification/performance',{recursive:true});
const label=process.env.BENCH_LABEL||'before',delay=Number(process.env.DELAY_MS||100),count=Number(process.env.PLAYERS||2);
const browser=await chromium.launch({headless:true,args:['--disable-background-timer-throttling','--disable-renderer-backgrounding','--disable-backgrounding-occluded-windows']});const pages=[],frames=[];let room='';
try{
for(let i=0;i<count;i++){
 const c=await browser.newContext({viewport:{width:1100,height:820}});
 await c.addInitScript((force)=>{const Base=window.RTCPeerConnection;window.__pcs=[];window.RTCPeerConnection=class extends Base{constructor(config){super(force?{...config,iceTransportPolicy:'relay'}:config);window.__pcs.push(this);}};},process.env.FORCE_TURN!=='0');
 const p=await c.newPage();pages.push(p);p.on('pageerror',e=>console.log('ERROR',i,e.message));await p.goto(url);
 if(url.startsWith('https:'))await p.getByRole('button',{name:'Start Crossfire',exact:true}).click();
 await p.locator('iframe').waitFor();await p.waitForTimeout(600);await p.locator('iframe').evaluate(el=>{Object.assign(el.style,{width:'960px',height:'680px',minWidth:'0',minHeight:'0',flex:'none'});});
 const f=p.frames().find(f=>f.url()==='about:srcdoc');frames.push(f);
 await f.evaluate(({i,delay,room})=>{
  window.probe={roster:[],rows:[]};window.metrics={poses:[],shots:[],sends:[],received:[],marks:{},opens:[]};
  const ctx=CanvasRenderingContext2D.prototype,fill=ctx.fillText,rect=ctx.fillRect,stroke=ctx.stroke;
  let angle=0;
  ctx.fillRect=function(x,y,w,h){if(w===24&&h===10){const m=this.getTransform();angle=Math.atan2(m.b,m.a);}return rect.call(this,x,y,w,h);};
  ctx.fillText=function(text,x,y,...args){if(text==='Guest (You)'||text==='YOU'||/Guest|P\d/.test(text)){const m=this.getTransform();window.metrics.poses.push({t:performance.now(),name:text,x:m.e,y:m.f,a:angle});}return fill.call(this,text,x,y,...args);};
  ctx.stroke=function(...args){if(this.lineWidth===4)window.metrics.shots.push(performance.now());return stroke.apply(this,args);};
  const rtc=window.napplet.webrtc,send=rtc.send.bind(rtc),open=rtc.open.bind(rtc);let due=0,serial=0;
  rtc.open=async request=>{window.metrics.opens.push(request);return open(request);};
  rtc.send=(id,payload)=>{
   const copy=JSON.parse(JSON.stringify(payload));window.metrics.sends.push({t:performance.now(),bytes:new TextEncoder().encode(JSON.stringify(copy)).length,type:copy.type,world:!!copy.world});
   if(copy.world)window.latestWorld=copy.world;
   if(copy.type==='state'){if(copy.roster)window.probe.roster=copy.roster;for(const row of copy.p)window.probe.rows[row[0]]=row;}
   // Ordered application-message delay, before the real NAP/WebRTC send. Same seeded jitter each run.
   const jitter=[-20,0,20,10,-10][serial++%5];due=Math.max(due,performance.now()+Math.max(0,delay+jitter));
   setTimeout(()=>send(id,copy).catch(()=>{}),Math.max(0,due-performance.now()));return Promise.resolve();
  };
  const on=rtc.onEvent.bind(rtc);rtc.onEvent=cb=>on(e=>{if(e.type==='message'){window.metrics.received.push({t:performance.now(),bytes:JSON.stringify(e.payload).length,type:e.payload?.type});if(e.payload?.world)window.latestWorld=e.payload.world;}cb(e);});
  const api=window.napplet.cvm,call=api.callTool.bind(api);api.callTool=async(...args)=>{const r=await call(...args);if(args[1]==='soy_room_list'&&room)r.structuredContent.rooms=r.structuredContent.rooms.filter(x=>x.room===room);if(args[1]==='soy_room_create')window.testRoom=r.structuredContent.room;if(args[1]==='soy_session')window.actor=r.structuredContent.actor;return r;};
 },{i,delay,room});
 if(i===0){await f.locator('#game-name').fill('Latency '+Date.now());await f.locator('#find').click();}
 else{await f.locator('#refresh').click();await f.locator('.room-row button').first().click({timeout:20000});}
 for(let n=0;n<40;n++){await p.waitForTimeout(250);const allow=p.getByRole('button',{name:'Allow',exact:true});if(await allow.isVisible()){await allow.click();break;}const old=p.getByText('Connect to other players? Peers may learn your network address.',{exact:false});if(await old.isVisible()){await p.getByRole('button',{name:'Connect',exact:true}).last().click();break;}}

 if(i===0)room=await f.evaluate(()=>window.testRoom);console.log('join',i,room,await f.locator('#status').innerText());
}
await Promise.all(frames.map(f=>f.waitForFunction(()=>document.querySelector('#status').textContent.startsWith('Live'),{},{timeout:65000})));
await Promise.all(frames.map(f=>f.waitForFunction(count=>document.querySelector('#score').textContent.startsWith(count+'/8'),count,{timeout:30000})));await pages[0].waitForTimeout(1500);
const guest=frames.at(-1),host=frames[0];await host.evaluate(()=>{window.metrics.sends=[];window.metrics.start=performance.now();});
await guest.evaluate(()=>{window.metrics.poses=[];window.metrics.shots=[];window.metrics.sends=[];window.metrics.received=[];const c=document.querySelector('canvas');c.focus();window.metrics.marks.move=performance.now();c.dispatchEvent(new KeyboardEvent('keydown',{code:'KeyA',bubbles:true}));});
await pages[0].waitForTimeout(1000);await guest.evaluate(()=>document.querySelector('canvas').dispatchEvent(new KeyboardEvent('keyup',{code:'KeyA',bubbles:true})));await pages[0].waitForTimeout(700);
await guest.evaluate(()=>{const c=document.querySelector('canvas'),r=c.getBoundingClientRect();window.metrics.marks.aim=performance.now();c.dispatchEvent(new PointerEvent('pointermove',{pointerType:'mouse',clientX:r.x+100,clientY:r.y+600,bubbles:true}));});await pages[0].waitForTimeout(500);
await guest.evaluate(()=>{window.metrics.marks.fire=performance.now();document.querySelector('canvas').dispatchEvent(new KeyboardEvent('keydown',{code:'Space',bubbles:true}));});await pages[0].waitForTimeout(350);await guest.evaluate(()=>document.querySelector('canvas').dispatchEvent(new KeyboardEvent('keyup',{code:'Space',bubbles:true})));await pages[0].waitForTimeout(1200);
await guest.evaluate(()=>{window.metrics.marks.remoteStart=performance.now();});await host.evaluate(()=>document.querySelector('canvas').dispatchEvent(new KeyboardEvent('keydown',{code:'KeyD',bubbles:true})));await pages[0].waitForTimeout(1000);await host.evaluate(()=>document.querySelector('canvas').dispatchEvent(new KeyboardEvent('keyup',{code:'KeyD',bubbles:true})));await pages[0].waitForTimeout(700);
const hostTraffic=await host.evaluate(()=>({bytes:window.metrics.sends.reduce((s,m)=>s+m.bytes,0),duration:(performance.now()-window.metrics.start)/1000}));
const data=await guest.evaluate(()=>window.metrics),own=data.poses.filter(p=>p.name.includes('(You)')||p.name==='YOU');
const latency=(mark,key)=>{const t=data.marks[mark],before=own.filter(p=>p.t<t+1).at(-1)||own[0],after=own.find(p=>p.t>=t&&Math.abs(p[key]-before[key])>.5);return after?Math.round(after.t-t):null;};
const remoteAll=data.poses.filter(p=>!p.name.includes('(You)')&&p.name!=='YOU'&&p.y<120&&p.x<400&&p.t>data.marks.remoteStart);const firstRemoteMove=remoteAll.find(p=>Math.abs(p.x-(remoteAll[0]?.x??0))>1)?.t??data.marks.remoteStart;const remote=remoteAll.filter(p=>p.t>=firstRemoteMove&&p.t<firstRemoteMove+600);let frozen=0,maxStep=0;
for(let i=1;i<remote.length;i++){const d=Math.abs(remote[i].x-remote[i-1].x);if(d<.05)frozen++;maxStep=Math.max(maxStep,d);}
const guestActor=await guest.evaluate(()=>window.actor),hostX=await host.evaluate(id=>window.probe.rows[window.probe.roster.findIndex(r=>r[0]===id)]?.[1]??window.latestWorld?.p.find(p=>p.id===id)?.x,guestActor);
const result={label,forcedTurn:process.env.FORCE_TURN!=='0',delayEachDirectionMs:delay,jitterMs:20,players:count,movementLatencyMs:latency('move','x'),aimLatencyMs:latency('aim','a'),shotLatencyMs:Math.round((data.shots.find(t=>t>=data.marks.fire)??NaN)-data.marks.fire),remoteFrozenFramePercent:Math.round(100*frozen/Math.max(1,remote.length-1)),remoteMaxFrameStepPx:+maxStep.toFixed(2),clientBytesPerSecond:Math.round(data.sends.reduce((s,m)=>s+m.bytes,0)/((data.poses.at(-1).t-data.marks.move)/1000)),clientWorldMessages:data.sends.filter(m=>m.world).length,positionErrorAfterReleasePx:hostX===undefined?null:+Math.abs(own.at(-1).x-hostX).toFixed(2),sessionOpens:await Promise.all(frames.map(f=>f.evaluate(()=>window.metrics.opens.length))),hostBytesPerSecond:Math.round(hostTraffic.bytes/hostTraffic.duration),transport:await Promise.all(pages.map(p=>p.evaluate(()=>window.__pcs.filter(p=>p.connectionState!=='closed').map(p=>p.connectionState))))};
console.log(JSON.stringify(result,null,2));fs.writeFileSync('.napplet-space/verification/performance/'+label+'.json',JSON.stringify({result,data},null,2));
if(result.movementLatencyMs>80||result.remoteFrozenFramePercent>20)console.log('RESPONSIVENESS REGRESSION: movement >80ms or frozen frames >20%');
if(process.env.LIFECYCLE==='1'&&count===2){
 const target=await host.evaluate(()=>window.probe.rows[0][1]);
 await guest.evaluate(()=>document.querySelector('canvas').dispatchEvent(new KeyboardEvent('keydown',{code:'KeyA',bubbles:true})));
 await guest.waitForFunction(target=>window.metrics.poses.filter(p=>p.name.includes('(You)')).at(-1)?.x<=target+8,target,{timeout:5000,polling:16});
 await guest.evaluate(()=>document.querySelector('canvas').dispatchEvent(new KeyboardEvent('keyup',{code:'KeyA',bubbles:true})));await pages[0].waitForTimeout(1500);
 await guest.evaluate(target=>{const c=document.querySelector('canvas'),r=c.getBoundingClientRect();c.dispatchEvent(new PointerEvent('pointermove',{pointerType:'mouse',clientX:r.x+target,clientY:r.y+100,bubbles:true}));c.dispatchEvent(new KeyboardEvent('keydown',{code:'Space',bubbles:true}));},target);
 await host.waitForFunction(()=>window.probe.rows[1]?.[5]>=1,{},{timeout:12000});await guest.evaluate(()=>document.querySelector('canvas').dispatchEvent(new KeyboardEvent('keyup',{code:'Space',bubbles:true})));
 await host.waitForFunction(()=>window.probe.rows[0]?.[10]>=1&&window.probe.rows[0]?.[4]===100,{},{timeout:6000});
 await guest.waitForFunction(()=>document.querySelector('#score').textContent.includes('1/7'),{},{timeout:6000});console.log('PASS remote shooting, authoritative kill/score, respawn');
 await guest.locator('#leave').click();await pages[0].waitForTimeout(2500);await guest.locator('#refresh').click();await guest.locator('.room-row button').first().click();
 await guest.waitForFunction(()=>document.querySelector('#status').textContent.startsWith('Live'),{},{timeout:45000});await pages[0].waitForTimeout(1500);
 const prior=await host.evaluate(()=>window.probe.rows[1][1]);await guest.evaluate(()=>document.querySelector('canvas').dispatchEvent(new KeyboardEvent('keydown',{code:'KeyA',bubbles:true})));await pages[0].waitForTimeout(600);await guest.evaluate(()=>document.querySelector('canvas').dispatchEvent(new KeyboardEvent('keyup',{code:'KeyA',bubbles:true})));await pages[0].waitForTimeout(1500);
 const moved=await host.evaluate(()=>window.probe.rows[1][1]);if(Math.abs(moved-prior)<30)throw Error('Rejoined client movement did not reach host');console.log('PASS same-session leave/rejoin movement',prior,moved);
}
for(const f of frames)if(await f.locator('#leave').isVisible())await f.locator('#leave').click();
if(process.env.ASSERT_RESPONSIVE==='1'&&(!Number.isFinite(result.movementLatencyMs)||result.movementLatencyMs>80||!Number.isFinite(result.shotLatencyMs)||result.shotLatencyMs>80||result.remoteFrozenFramePercent>20||result.clientWorldMessages!==0))throw Error('Responsiveness regression');
}catch(error){for(let i=0;i<pages.length;i++){console.log('FAILURE DIAGNOSTICS',i,await frames[i]?.locator('#status').innerText().catch(()=>''),await pages[i].evaluate(()=>window.__pcs.map(p=>({connection:p.connectionState,ice:p.iceConnectionState,local:p.localDescription?.sdp?.split('\\r\\n').filter(l=>l.startsWith('a=candidate:'))}))).catch(()=>[]));}throw error;}finally{await browser.close();}