<!doctype html>
<html lang="ar" dir="rtl"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="robots" content="noindex,nofollow">
<meta name="referrer" content="no-referrer">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; connect-src wss://wingstorm-challenge-stage5-test-2.onrender.com; base-uri 'none'; form-action 'none'">
<title>WING STORM — Stage 7 Live Check</title>
<style>body{margin:0;padding:16px;background:#0b1a33;color:#eaf2ff;font:15px/1.6 -apple-system,system-ui,sans-serif;-webkit-text-size-adjust:100%}main{max-width:820px;margin:auto}
.card{background:#13264a;border:1px solid #24406f;border-radius:14px;padding:14px;margin:12px 0}
button{-webkit-appearance:none;appearance:none;display:inline-block;padding:14px 20px;border:0;border-radius:12px;font:800 16px -apple-system,system-ui,sans-serif;background:#2f6fd8;color:#fff;margin:8px 0 0 6px;cursor:pointer;touch-action:manipulation;-webkit-tap-highlight-color:rgba(255,255,255,.25)}
button:disabled{background:#3b4f78;color:#c8d4ea}button.alt{background:#24406f}
pre{background:#07112a;border:1px solid #24406f;border-radius:10px;padding:12px;white-space:pre-wrap;word-break:break-word;direction:ltr;text-align:left;font:12.5px/1.5 ui-monospace,Menlo,monospace;-webkit-user-select:text;user-select:text}
p{color:#9fb4d8}code{direction:ltr;unicode-bidi:embed}
#nojs{background:#5a1f2a;border:1px solid #ff7a8a;border-radius:12px;padding:12px;margin:12px 0;font-weight:700}
#ready{display:none;color:#8ff0b0;font-weight:700}</style></head>
<body><main><h1 style="font-size:20px">فحص خادم المرحلة ٧ على الإنترنت</h1>
<div id="nojs">⚠️ هذه الصفحة لا تعمل هنا: هذا العارض لا يشغّل JavaScript (مثل معاينة «الملفات» أو معاينة داخل تطبيق). افتحها في Safari من رابط موقع يبدأ بـ <code>https://</code>.</div>
<p id="ready">✓ الصفحة جاهزة في هذا المتصفح.</p>
<p>الخادم الوحيد الذي تتصل به هذه الصفحة: <code>wss://wingstorm-challenge-stage5-test-2.onrender.com/ws</code>. تتحقق الصفحة من الإصدار، ثم تشغّل غرف تحدي أصدقاء حقيقية عبر الإنترنت بـ٢ ثم ٣ ثم ٤ ثم ٥ ثم ٦ ثم ١٢ طائرًا آليًّا من هذا الجهاز، ويُرفض لاعب ثالث عشر. تُغلق كل غرفة بعد الانتهاء. لا تحتاج أي مفتاح ولا تغيّر أي إعداد.</p>
<p>أبقِ الشاشة مفتوحة وابقَ في هذه الصفحة حتى ينتهي الفحص (نحو دقيقتين). يستهلك نحو ١٠–٢٠ MB من البيانات.</p>
<div class="card"><button id="run" type="button">▶ ابدأ الفحص</button><button id="copy" class="alt" type="button">📋 نسخ النتيجة</button></div>
<div class="card"><pre id="log">—</pre></div>
<script>
"use strict";
var LOGL=[];function $(id){return document.getElementById(id);}
function log(s){LOGL.push(String(s));var el=$("log");if(el)el.textContent=LOGL.join("\n");}
/* any script error is shown on the page instead of failing silently */
window.addEventListener("error",function(e){log("SCRIPT ERROR  "+(e&&e.message)+" @"+(e&&e.lineno));});
window.addEventListener("unhandledrejection",function(e){log("SCRIPT ERROR  "+(e&&e.reason&&(e.reason.message||e.reason)));});
(function(){var n=$("nojs");if(n)n.style.display="none";var r=$("ready");if(r)r.style.display="block";})();   /* proves the script runs in this viewer */

var SERVER="wss://wingstorm-challenge-stage5-test-2.onrender.com/ws";   /* fixed target */
var TEST_HOST="wingstorm-challenge-stage5-test-2.onrender.com",EXPECTED="stage7-friend12-2",SIZES=[2,3,4,5,6,12],WINDOW_S=15;
var CSPV=[];document.addEventListener("securitypolicyviolation",function(e){CSPV.push(e.violatedDirective+" blocked "+e.blockedURI);});
function sleep(ms){return new Promise(function(r){setTimeout(r,ms);});}
function sock(url){return new Promise(function(res,rej){var w;try{w=new WebSocket(url);}catch(e){rej(new Error(e.name+": "+e.message));return;}
  var q=[],waits=[],opened=false;
  var S={w:w,bytes:0,snaps:0,rtt:[],closed:false,last:null,ended:null,
    send:function(o){if(w.readyState===1)w.send(JSON.stringify(o));},
    next:function(p,ms){ms=ms||15000;for(var i=0;i<q.length;i++)if(p(q[i]))return Promise.resolve(q.splice(i,1)[0]);
      return new Promise(function(r,j){var it={p:p,r:r};waits.push(it);setTimeout(function(){var k=waits.indexOf(it);if(k>=0){waits.splice(k,1);j(new Error("no reply within "+Math.round(ms/1000)+" s"));}},ms);});}};
  w.onopen=function(){opened=true;res(S);};
  w.onerror=function(){if(!opened)rej(new Error(CSPV.length?"blocked by the page policy: "+CSPV[CSPV.length-1]:"cannot connect (server asleep, wrong address, or no network)"));};
  w.onclose=function(){S.closed=true;};
  w.onmessage=function(e){if(typeof e.data==="string")S.bytes+=e.data.length;var m;try{m=JSON.parse(e.data);}catch(_){return;}
    if(m.t==="coop_s"){S.snaps++;S.last=m;return;}if(m.t==="pong"){if(m.c!=null)S.rtt.push(performance.now()-m.c);return;}if(m.t==="coop_end"){S.ended=m;}
    for(var i=0;i<waits.length;i++)if(waits[i].p(m)){var it=waits.splice(i,1)[0];it.r(m);return;}if(q.length<300)q.push(m);};});}
var R=[];function ok(n,c,d){R.push(!!c);log((c?"PASS  ":"FAIL  ")+n+(d?"\n      "+d:""));}
function pct(a,p){if(!a.length)return"-";var s=a.slice().sort(function(x,y){return x-y;});return s[Math.min(s.length-1,Math.floor(s.length*p))].toFixed(0);}
function fin(live){log("\n"+R.filter(Boolean).length+"/"+R.length+" passed"+(R.length&&R.every(Boolean)?(live?" · STAGE 7 LIVE VERIFIED":" · local server only — NOT the live test service"):""));}
var running=false;
async function runCheck(){
  if(running)return;running=true;var btn=$("run");btn.disabled=true;btn.textContent="⏳ جارٍ الفحص…";
  LOGL=[];R=[];CSPV=[];var url=SERVER,live=new URL(url).hostname===TEST_HOST,admin=null;
  log("Stage 7 live check · "+new Date().toISOString()+"\nserver: "+url+"\npage:   "+location.protocol+"//"+location.host+" · "+(navigator.userAgent.split(") ")[0].split("(")[1]||"")+"\nconnecting… (a sleeping Render service can take up to ~60 s to wake)");
  try{
    var t0=performance.now();admin=await sock(url);await admin.next(function(m){return m.t==="hello";},60000);ok("WebSocket opens and the server greets",true,Math.round(performance.now()-t0)+" ms");
    admin.send({t:"coop_version"});var v=await admin.next(function(m){return m.t==="coop_version"||m.t==="coop_error";}).catch(function(){return{t:"none"};});
    ok("Server version is exactly "+EXPECTED,v.version===EXPECTED,"reply: "+JSON.stringify(v));
    if(v.version!==EXPECTED){log("\nSTOP: the test service is not running Stage 7B ("+EXPECTED+"). No room tests were run.");return;}
    if(v.friendPlayersCap!=null)log("      server limits: "+v.friendMax+" per room · "+v.friendRoomsCap+" Friend rooms · "+v.friendPlayersCap+" Friend players in total");
    var totalBytes=0;
    for(var si=0;si<SIZES.length;si++){var n=SIZES[si],S=[],stopped=false,tm=null,pt=null;
      log("\n… Friend room with "+n+" players: connecting");
      try{
        var H=await sock(url);await H.next(function(m){return m.t==="hello";});H.send({t:"coop_create",name:"Check-1",kind:"friend",skin:"sun"});
        var yh=await H.next(function(m){return m.t==="coop_you"||m.t==="coop_error";});if(yh.t!=="coop_you")throw new Error("room not created: "+yh.error+(yh.reason?" ("+yh.reason+")":""));S.push(H);
        for(var i=1;i<n;i++){var J=await sock(url);await J.next(function(m){return m.t==="hello";});J.send({t:"coop_join",code:yh.code,name:"Check-"+(i+1),skin:"sun"});
          var y=await J.next(function(m){return m.t==="coop_you"||m.t==="coop_error";});if(y.t!=="coop_you")throw new Error("player "+(i+1)+" could not join: "+y.error+(y.reason?" ("+y.reason+")":""));S.push(J);}
        var room=await H.next(function(m){return m.t==="coop_room"&&m.players.length===n;});
        var extra="";if(n===12){var X=await sock(url);await X.next(function(m){return m.t==="hello";});X.send({t:"coop_join",code:yh.code,name:"Check-13"});
          var yx=await X.next(function(m){return m.t==="coop_you"||m.t==="coop_error";});extra=yx.t==="coop_error"?yx.error:"ACCEPTED";try{X.w.close();}catch(e){}}
        H.send({t:"coop_start"});var go=await H.next(function(m){return m.t==="coop_go"||m.t==="coop_error";});if(go.t!=="coop_go")throw new Error("start refused: "+go.error);
        S.forEach(function(s){s.send({t:"coop_test",op:"shield"});});   /* ignored unless the service has COOP_TEST_HOOKS=1 */
        S.forEach(function(s){s.bytes=0;s.snaps=0;s.rtt=[];});
        var seq=S.map(function(){return 0;}),k=Math.random()*6;
        tm=setInterval(function(){var t=performance.now()/1000+k;S.forEach(function(s,i){s.send({t:"coop_in",seq:++seq[i],tx:195+120*Math.cos(t*1.1+i),ty:420+150*Math.sin(t+i*.7)});});},33);
        pt=setInterval(function(){S.forEach(function(s){s.send({t:"ping",c:performance.now()});});},1000);
        /* measure while the round is running: until the window ends or the round ends (all bots fell) */
        var T0=performance.now();while(performance.now()-T0<WINDOW_S*1000&&!S[0].ended)await sleep(200);
        var dt=Math.max(.5,(performance.now()-T0)/1000);clearInterval(tm);clearInterval(pt);tm=pt=null;
        var per=S.map(function(s){return s.snaps/dt;}),rt=[].concat.apply([],S.map(function(s){return s.rtt;})),bytes=S.reduce(function(a,s){return a+s.bytes;},0);
        var dropped=S.filter(function(s){return s.closed;}).length,last=S[0].last,alive=last?last.players.filter(function(p){return p.alive;}).length:0;
        totalBytes+=bytes;var minSnap=Math.min.apply(null,per),med=per.slice().sort(function(a,b){return a-b;})[per.length>>1];
        var good=dropped===0&&minSnap>=16&&room.max===12&&go.players.length===n&&(n!==12||extra==="full");
        ok("Friend "+(n<10?" ":"")+n+" players online",good,
          "room max "+room.max+" · started with "+go.players.length+" · measured "+dt.toFixed(1)+" s"+(S[0].ended?" (round ended: all bots fell)":"")+
          " · updates per player min "+minSnap.toFixed(1)+" / median "+med.toFixed(1)+" per s (target 20) · RTT median "+pct(rt,.5)+" ms, p95 "+pct(rt,.95)+" ms · dropped connections "+dropped+" · bots alive at end "+alive+"/"+n+
          "\n      data received: "+(bytes/1024/dt).toFixed(0)+" KB/s for the room = "+(bytes/1024/dt/n).toFixed(1)+" KB/s per player (≈ "+(bytes/1024/dt/n*180/1024).toFixed(1)+" MB per player per 3-min round)"+
          (n===12?"\n      13th player: "+extra:""));
      }catch(e){stopped=true;ok("Friend "+n+" players online",false,String(e&&e.message||e));}
      finally{if(tm)clearInterval(tm);if(pt)clearInterval(pt);S.forEach(function(s){try{s.send({t:"coop_leave"});s.w.close();}catch(e){}});}
      if(stopped){log("\nSTOP: this size failed — later sizes were not run.");break;}await sleep(2000);}
    log("\ntotal data received by this device: "+(totalBytes/1048576).toFixed(1)+" MB");
  }catch(e){ok("run",false,String(e&&e.message||e));}
  finally{try{admin&&admin.w.close();}catch(e){}fin(live);running=false;btn.disabled=false;btn.textContent="▶ ابدأ الفحص";}}
$("run").addEventListener("click",function(){runCheck().catch(function(e){log("SCRIPT ERROR  "+(e&&e.message||e));});});
$("copy").addEventListener("click",function(){var txt=LOGL.join("\n"),b=$("copy");
  function done(){b.textContent="✓ نُسخ";}
  if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(txt).then(done,fallback);}else fallback();
  function fallback(){var sel=window.getSelection(),rg=document.createRange();rg.selectNodeContents($("log"));sel.removeAllRanges();sel.addRange(rg);try{if(document.execCommand("copy"))done();else b.textContent="حدِّد النص وانسخه";}catch(e){b.textContent="حدِّد النص وانسخه";}}});
</script></main></body></html>