SOURCE / PINNED RELEASE
Made of little things.
Drone Zone — deep field
- Release
- 578f277a8be1…
- Author-recorded commit
- a96742b428de…
- License
- LICENSE
- Author’s source reference
- nostr://npub1n8ga89w8h6tvwamxusfyzexw8gjy84yxu9rxgnmk955cxtml4ujswzxydd/wss%3A%2F%2Fgit.napplet.soy%2F/n-9753e6cf3ec
Archive hash verified: f7f3bf8ff3482551…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
// Integration fixture only: simulates the runtime contract; never shipped in the napplet.
// Run after building: node tests/browser-smoke.mjs
import assert from 'node:assert/strict';
import { readFile, mkdir, realpath } from 'node:fs/promises';
import { createRequire } from 'node:module';
const root = new URL('../', import.meta.url);
const require = createRequire(await realpath(new URL('node_modules/@napplet/conformance-cli/package.json', root)));
const { chromium } = require('playwright');
const artifact = await readFile(new URL('dist/index.html', root), 'utf8');
assert.doesNotMatch(artifact, /<script[^>]+src=/);
assert.doesNotMatch(artifact, /<link[^>]+href=/);
const output = '/private/tmp/drone-zone-qa-v5';
await mkdir(output, { recursive: true });
const browser = await chromium.launch({ headless: true });
const errors = [];
const page = await browser.newPage({viewport:{width:900,height:600},deviceScaleFactor:1});
page.on('pageerror', error => errors.push(error.message));
let mode='dark';
function fixture(mode, absent=false, deny=false, noGL=false, radio={}) {
return `<script>(${function(mode,absent,deny,noGL,radio) {
const log=[];window.__testLog=log;
window.__calloutSizes=new Set();
const originalDraw=CanvasRenderingContext2D.prototype.drawImage;
CanvasRenderingContext2D.prototype.drawImage=function(image,...args){
if(image instanceof HTMLImageElement)window.__calloutSizes.add(`${image.naturalWidth}x${image.naturalHeight}`);
return originalDraw.call(this,image,...args);
};
if(noGL){const original=HTMLCanvasElement.prototype.getContext;HTMLCanvasElement.prototype.getContext=function(type,...args){return type.startsWith('webgl')?null:original.call(this,type,...args);};}
if(absent){window.napplet={};return;}
const station={id:'a'.repeat(64),kind:31237,pubkey:'210f31b6019f5ae13c995c8d83faa41a129f1296842e4c3313ab8a4abb09d1a2',created_at:1774180769,tags:[['d','960eb2e9-0601-11e8-ae97-52543be04c81'],['name','Drone Zone']],content:JSON.stringify({streams:[{url:'https://example.org/radio',format:'audio/mpeg',quality:{bitrate:256000},primary:true}]}),sig:'0'.repeat(128)};
const colors=mode==='dark'?{background:'#05090d',text:'#d6e4e6',primary:'#94d8df'}:{background:'#f4f2e9',text:'#253331',primary:'#306c70'};
const sub=()=>({close(){log.push(['close']);}});
let state,capabilities,status='stopped',volume=.65,queries=0,destroyed=false;
const snapshot=()=>{if(!destroyed)state?.({status,volume});};
window.__allowAudio=()=>{status='playing';snapshot();};
window.napplet={
theme:{get:async()=>({colors}),onChanged:cb=>{window.__theme=cb;return sub();}},
config:{subscribe:cb=>{window.__config=cb;cb({motion:.6,stars:1,fog:true,distortion:true});return sub();},openSettings:()=>log.push(['settings'])},
outbox:{query:async(filters,options)=>{log.push(['query',filters,options]);return{events:radio.lookupFailsOnce&&queries++===0?[]:[{event:station}],incomplete:false};}},
link:{open:async url=>{log.push(['link',url]);return{status:'opened'};}},
media:{
createSession:async options=>{
log.push(['create',options]);
if(deny)return{error:'Playback denied by test host'};
if(radio.createDelay)await new Promise(resolve=>setTimeout(resolve,radio.createDelay));
setTimeout(()=>{
if(destroyed)return;
if(options.autoplay){log.push(['autoplay']);status=radio.autoplayBlocked?'paused':'playing';}
snapshot();
},30);
return{sessionId:'test-radio',owner:'shell'};
},
onState:(id,callback)=>{state=callback;return sub();},
onCapabilities:(id,callback)=>{capabilities=callback;setTimeout(()=>capabilities(['play','pause','volume']),10);return sub();},
sendCommand:(id,action,value)=>{log.push(['command',action,value]);setTimeout(()=>{if(action==='volume')volume=value;else status=action==='pause'?'paused':'playing';snapshot();},20);},
destroySession:id=>{destroyed=true;log.push(['destroy',id]);},
}
};
if(radio.mediaAbsent)delete window.napplet.media;
}.toString()})(${JSON.stringify(mode)},${absent},${deny},${noGL},${JSON.stringify(radio)});</script>`;
}
async function load({absent=false,deny=false,noGL=false,...radio}={}) {
await page.setContent('<style>html,body{margin:0;width:100%;height:100%;overflow:hidden}iframe{display:block;width:100%;height:100%;border:0}</style><iframe sandbox="allow-scripts"></iframe>');
await page.locator('iframe').evaluate((iframe,src)=>{iframe.srcdoc=src;}, artifact.replace('<head>',`<head>${fixture(mode,absent,deny,noGL,radio)}`));
const frame=await page.locator('iframe').elementHandle().then(e=>e.contentFrame());
await frame.locator('#universe').waitFor();await frame.waitForTimeout(450);return frame;
}
try {
let frame;
for(mode of ['dark','light']) {
frame=await load();
assert.equal(await frame.evaluate(()=>{const gl=document.querySelector('#galaxy').getContext('webgl');return !!gl?.getParameter(gl.CURRENT_PROGRAM);}),true,'galaxy shader compiled and linked');
for(const [width,height] of [[200,160],[320,560],[900,600],[2400,1200]]) {
await page.setViewportSize({width,height});await page.waitForTimeout(450);
const layout=await frame.evaluate(()=>{
const rect=document.querySelector('#play').getBoundingClientRect();
return{width:innerWidth,height:innerHeight,overflow:document.documentElement.scrollWidth>innerWidth,play:[rect.left,rect.top,rect.right,rect.bottom],backgrounds:['html','body','#app'].map(s=>getComputedStyle(document.querySelector(s)).backgroundColor)};
});
assert.equal(layout.overflow,false);assert.ok(layout.play[0]>=0&&layout.play[1]>=0&&layout.play[2]<=width&&layout.play[3]<=height);
assert.ok(layout.backgrounds.every(x=>x===layout.backgrounds[0]));
await page.screenshot({path:`${output}/${mode}-${width}x${height}.png`});
}
}
mode='dark';await page.setViewportSize({width:900,height:600});frame=await load();
await frame.waitForFunction(()=>document.querySelector('#radio-status').textContent.includes('Live transmission'));
const initialRadio=await frame.evaluate(()=>window.__testLog);
assert.equal(initialRadio.filter(x=>x[0]==='create').length,1,'station lookup creates one session without a click');
assert.equal(initialRadio.find(x=>x[0]==='create')[1].autoplay,true);
assert.equal(initialRadio.filter(x=>x[0]==='command'&&x[1]==='play').length,0,'autoplay must not send a duplicate play command');
assert.equal(await frame.locator('#play').getAttribute('aria-label'),'Pause Drone Zone');
assert.equal(await frame.locator('#tour').getAttribute('aria-pressed'),'true');
const tourStart=await frame.locator('#position').innerText();await page.waitForTimeout(1000);
assert.notEqual(await frame.locator('#position').innerText(),tourStart,'The opening tour must move the camera');
await page.mouse.move(440,280);await page.mouse.down();await page.mouse.up();
assert.equal(await frame.locator('#tour').getAttribute('aria-pressed'),'false','Pointer takeover must stop the tour');
await frame.locator('#tour').click();assert.equal(await frame.locator('#tour').getAttribute('aria-pressed'),'true');
await frame.locator('#zoom-in').click();assert.equal(await frame.locator('#tour').getAttribute('aria-pressed'),'false','Zooming must stop the tour');
// The final tape treatment includes the live instrument UI and shares the
// scene's lifecycle. Zero wear, frozen motion and reduced motion stay usable.
await frame.locator('#layers').click();
const wear=frame.locator('#tape-wear');
async function setWear(value) {await wear.evaluate((el,value)=>{el.value=String(value);el.dispatchEvent(new Event('input',{bubbles:true}));},value);}
await setWear(0);
assert.equal(await frame.locator('#app').evaluate(el=>getComputedStyle(el).filter),'none');
assert.equal(await frame.locator('#tape-overlay').isVisible(),false);
assert.equal(await frame.locator('#tape-pulse').isDisabled(),true);
await setWear(100);await frame.locator('#tape-pulse').click();
await frame.waitForFunction(()=>document.querySelector('#app').dataset.tapeGlitch==='true');
assert.match(await frame.locator('#app').evaluate(el=>getComputedStyle(el).filter),/url\(/);
assert.equal(await frame.locator('#play').evaluate(el=>el.closest('#app')?.dataset.tapeGlitch),'true','player shares the final surface filter');
await frame.waitForFunction(()=>document.querySelector('#app').dataset.tapePhase==='chroma');
assert.match(await frame.locator('#app').evaluate(el=>getComputedStyle(el).filter),/tape-chroma/,'tracking slip resolves into a color echo');
await page.screenshot({path:`${output}/tape-pulse.png`});
await frame.waitForFunction(()=>document.querySelector('#app').dataset.tapeGlitch==='false');
await frame.locator('#tape-pulse').click();await frame.locator('#motion').click();
await frame.waitForFunction(()=>document.querySelector('#app').dataset.tapeGlitch==='false');
assert.equal(await frame.locator('#tape-pulse').isDisabled(),true);
await frame.locator('#motion').click();
await frame.evaluate(()=>window.__config({motion:.6,stars:1,fog:true,distortion:true,vhs:.25}));
assert.equal(await wear.inputValue(),'25');
await setWear(0);await setWear(45);
await frame.waitForFunction(()=>document.querySelector('#app').dataset.tapeGlitch==='true',{},{timeout:10000});
await frame.waitForFunction(()=>document.querySelector('#app').dataset.tapeGlitch==='false');
await frame.locator('#layers').click();
await frame.locator('#play').click();await frame.waitForFunction(()=>document.querySelector('#radio-status').textContent.startsWith('Paused'));
await frame.locator('#retry').click();await frame.waitForFunction(()=>document.querySelector('#radio-status').textContent==='Paused');
assert.equal(await frame.locator('#app').getAttribute('data-playing'),'false','metadata refresh must not restart paused playback');
await frame.locator('#play').click();await frame.waitForFunction(()=>document.querySelector('#radio-status').textContent.includes('Live transmission'));
await frame.locator('#play').click();await frame.waitForFunction(()=>document.querySelector('#radio-status').textContent.startsWith('Paused'));
const log=await frame.evaluate(()=>window.__testLog);
assert.equal(log.filter(x=>x[0]==='create').length,1,'pause/resume reuses the autoplay session');
assert.equal(log.find(x=>x[0]==='create')[1].owner,'shell');assert.equal(log.find(x=>x[0]==='create')[1].source.url,'https://example.org/radio');
assert.ok(log.some(x=>x[0]==='query'&&x[1][0]['#d'][0]==='960eb2e9-0601-11e8-ae97-52543be04c81'));
await frame.locator('#inspect').click();assert.equal(await frame.locator('#observation-name').innerText(),'THE HYDROGEN LINE');
await frame.locator('#next').click();assert.equal(await frame.locator('#observation-name').innerText(),'A DISTANCE MADE OF TIME');assert.equal(await frame.locator('#discovery-count').innerText(),'02 / 20');await frame.locator('#close-observation').click();
await frame.locator('#home').click();await page.waitForTimeout(800);
const before=await frame.locator('#position').innerText();
await page.mouse.move(440,280);await page.mouse.down();await page.mouse.move(580,370,{steps:10});await page.mouse.up();await page.waitForTimeout(700);assert.notEqual(await frame.locator('#position').innerText(),before);
await frame.locator('#universe').focus();await page.keyboard.press('ArrowLeft');
for(let i=0;i<15;i++)await frame.locator('#zoom-in').click();await page.waitForTimeout(950);assert.equal(await frame.locator('#zoom').innerText(),'3.60×');
await page.screenshot({path:`${output}/close-up.png`});
for(let i=0;i<20;i++)await frame.locator('#zoom-out').click();await page.waitForTimeout(950);assert.equal(await frame.locator('#zoom').innerText(),'0.55×');
await frame.locator('#motion').click();assert.equal(await frame.locator('#motion').getAttribute('aria-pressed'),'true');
await frame.locator('#layers').click();await frame.locator('#routes').uncheck();await frame.locator('#fog').uncheck();await frame.locator('#layers').click();
await frame.evaluate(()=>window.__config({motion:0,stars:.2,fog:false,distortion:false}));assert.equal(await frame.locator('#fog').isChecked(),false);assert.equal(await frame.locator('#tour').getAttribute('aria-pressed'),'false');
assert.equal(await frame.locator('#tape-pulse').isDisabled(),true);
await frame.evaluate(()=>window.__theme({colors:{background:'#ffffff',text:'#111111',primary:'#335577'}}));assert.equal(await frame.locator('body').evaluate(e=>getComputedStyle(e).backgroundColor),'rgb(255, 255, 255)');
await frame.evaluate(()=>window.dispatchEvent(new Event('pagehide')));const after=await frame.evaluate(()=>window.__testLog);assert.ok(after.some(x=>x[0]==='destroy'));assert.ok(after.filter(x=>x[0]==='close').length>=4);
assert.equal(await frame.locator('#tape-overlay').isVisible(),false);
frame=await load({absent:true});assert.equal(await frame.locator('#play').isDisabled(),true);await frame.locator('#zoom-in').click();await page.waitForTimeout(500);assert.notEqual(await frame.locator('#zoom').innerText(),'1.00×');
frame=await load({deny:true});await frame.waitForFunction(()=>document.querySelector('#radio-status').textContent.includes('denied'));assert.equal(await frame.locator('#play').isDisabled(),false);
assert.equal(await frame.evaluate(()=>window.__testLog.filter(x=>x[0]==='create').length),1,'denied autoplay is attempted once');
await frame.locator('#play').click();assert.equal(await frame.evaluate(()=>window.__testLog.filter(x=>x[0]==='create').length),2,'denied playback remains manually retryable');
frame=await load({autoplayBlocked:true});assert.equal(await frame.locator('#radio-status').innerText(),'Paused · the field continues');
assert.equal(await frame.locator('#play').isDisabled(),false);assert.equal(await frame.locator('#app').getAttribute('data-playing'),'false');
assert.equal(await frame.evaluate(()=>window.__testLog.filter(x=>x[0]==='command'&&x[1]==='play').length),0,'the app must leave the host activation prompt intact');
await frame.evaluate(()=>window.__allowAudio());assert.match(await frame.locator('#radio-status').innerText(),/Live transmission/);
frame=await load({lookupFailsOnce:true});assert.equal(await frame.evaluate(()=>window.__testLog.filter(x=>x[0]==='create').length),0);
await frame.locator('#play').click();await frame.waitForFunction(()=>document.querySelector('#radio-status').textContent.includes('Live transmission'));
assert.equal(await frame.evaluate(()=>window.__testLog.filter(x=>x[0]==='create').length),1,'retrying station lookup starts once without toggling back to pause');
frame=await load({mediaAbsent:true});assert.equal(await frame.locator('#radio-status').innerText(),'Listen in Wavefunc ↗');
assert.equal(await frame.evaluate(()=>window.__testLog.filter(x=>['create','link'].includes(x[0])).length),0,'missing media must not automatically open an external page');
await frame.locator('#play').click();assert.equal(await frame.evaluate(()=>window.__testLog.filter(x=>x[0]==='link').length),1);
frame=await load({createDelay:1500});await frame.evaluate(()=>window.dispatchEvent(new Event('pagehide')));
await frame.waitForFunction(()=>window.__testLog.some(x=>x[0]==='destroy'));
assert.equal(await frame.evaluate(()=>window.__testLog.filter(x=>x[0]==='destroy').length),1,'late autoplay sessions are destroyed after teardown');
frame=await load({noGL:true});await page.screenshot({path:`${output}/canvas-fallback.png`});assert.ok(await frame.locator('#signals').evaluate(canvas=>canvas.getContext('2d').getImageData(450,300,1,1).data[3]>0));
// Observe a real rendered gate-entry flash in the built artwork. This Canvas
// spy lives only in the test iframe and does not expose a production debug API.
frame=await load();await frame.locator('#tour').click();
await frame.evaluate(()=>{
window.__entryFlashes=[];
const original=CanvasRenderingContext2D.prototype.createRadialGradient;
CanvasRenderingContext2D.prototype.createRadialGradient=function(...args){
if(args[5]>28)window.__entryFlashes.push({x:args[3],y:args[4]});
return original.apply(this,args);
};
window.__config({motion:1,stars:1,fog:true,distortion:true});
});
await frame.waitForFunction(()=>window.__entryFlashes.length>0,{},{timeout:25000});
await frame.evaluate(()=>window.__config({motion:0,stars:1,fog:true,distortion:true}));
const flash=await frame.evaluate(()=>window.__entryFlashes[0]);
assert.ok(flash.x>0&&flash.x<900&&flash.y>0&&flash.y<600,'first hypertravel gate is discoverable in the opening view');
await page.screenshot({path:`${output}/hypertravel.png`});
await page.setViewportSize({width:1200,height:750});frame=await load();
await frame.locator('#inspect').click();
for(let i=0;i<=18;i++) {
if(i>0)await frame.locator('#next').click();
if([0,1,5,8,10,16,18].includes(i))await page.waitForTimeout(650);
if(i===8) {
for(let zoom=0;zoom<6;zoom++)await frame.locator('#zoom-in').click();await page.waitForTimeout(800);
await page.screenshot({path:`${output}/orbital-transfer.png`});
}
if(i===10)await page.screenshot({path:`${output}/long-gap.png`});
if(i===18)await page.screenshot({path:`${output}/patience-transfer.png`});
}
const images=await frame.evaluate(()=>[...window.__calloutSizes]);
assert.deepEqual(images.sort(),['708x648','1040x302','368x248','642x554'].sort(),'all four new callout images render at their native aspect ratios');
await page.emulateMedia({reducedMotion:'reduce'});frame=await load();assert.equal(await frame.locator('#motion').getAttribute('aria-pressed'),'true');assert.equal(await frame.locator('#tour').getAttribute('aria-pressed'),'false');
assert.equal(await frame.locator('#tape-overlay').isVisible(),false);
assert.equal(await frame.locator('#app').evaluate(el=>getComputedStyle(el).filter),'none');
assert.equal(await frame.locator('#tape-pulse').isDisabled(),true);
assert.deepEqual(errors,[]);
console.log('PASS: eight frame/theme pairs, automatic tour, manual takeover/resume, pan, expanded bounded zoom, discovery counts, observations, live config/theme, shell-owned autoplay without a click, pause/refresh/resume, denied/blocked autoplay, lookup recovery, absent domains, late-session cleanup, WebGL fallback, all four callout assets, orbital transfers, rendered hypertravel, whole-surface tape wear, automatic/manual tracking slips, wear off, frozen/zero/reduced motion, and teardown.');
console.log(`Screenshots: ${output}`);
} finally {await browser.close();}