Back to Outbreak: Soycoon City
SOURCE / PINNED RELEASE

Made of little things.

Outbreak: Soycoon City

Release
db7750b1d009…
Author-recorded commit
3ee3f5fd27a6…
License
LICENSE
Author’s source reference
nostr://npub182jczunncwe0jn6frpqwq3e0qjws7yqqnc3auccqv9nte2dnd63scjm4rf/wss%3A%2F%2Fgit.napplet.soy%2F/n-b87c04571a9

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

tests/features-hosted.mjs
import { createRequire } from 'node:module';
import { realpath, mkdir } from 'node:fs/promises';
import { execFileSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import assert from 'node:assert/strict';
const url=process.env.SOYINDENT_PREVIEW_URL;
if(!url)throw new Error('Start soyli dev and set SOYINDENT_PREVIEW_URL to its printed URL.');
const require=createRequire(await realpath(new URL('../node_modules/@napplet/conformance-cli/package.json',import.meta.url))),{chromium}=require('playwright');
const profile=JSON.parse(execFileSync('soyli',['browser','path','--json'],{encoding:'utf8'}));
const browser=await chromium.launch({executablePath:profile.executables.find(e=>e.name==='chromium-headless-shell'&&e.installed).path,headless:true,args:['--enable-webgl','--use-gl=angle','--use-angle=swiftshader']});
const A='a'.repeat(64),B='b'.repeat(64),scope='c'.repeat(64),events=[],files=new Map(),errors=[],players=[];
let sequence=0,denyPublish=false,uploadCount=0,resourceReads=0;
// Isolated synthetic host services. Nothing signs or sends records to a real relay.
async function player(author){
  const context=await browser.newContext({viewport:{width:1400,height:1000}}),page=await context.newPage();page.on('pageerror',e=>errors.push(e.message));
  await page.exposeFunction('fixtureQuery',filters=>{const f=filters[0];return {events:events.filter(e=>(!f.authors||f.authors.includes(e.pubkey))&&(!f['#d']||e.tags.some(t=>t[0]==='d'&&f['#d'].includes(t[1])))).map(event=>({event})),incomplete:false};});
  await page.exposeFunction('fixturePublish',template=>{
    if(denyPublish){denyPublish=false;return {ok:false,error:'fixture consent denied'};}
    const d=template.tags.find(t=>t[0]==='d')[1],base=events.filter(e=>e.pubkey===author&&e.tags.some(t=>t[0]==='d'&&t[1]===d)).sort((a,b)=>b.created_at-a.created_at)[0];
    if((base?.id??null)!==JSON.parse(template.content).previous)return {ok:false,error:'app-data-stale'};
    const event={...template,pubkey:author,id:(++sequence).toString(16).padStart(64,'0'),sig:'0'.repeat(128)};events.push(event);return {ok:true,event};
  });
  await page.exposeFunction('fixtureUpload',({base64,mime})=>{uploadCount++;const bytes=Buffer.from(base64,'base64'),sha256=createHash('sha256').update(bytes).digest('hex'),url=`https://fixture.invalid/${sha256}`;files.set(url,{base64,mime});return {ok:true,status:'complete',uploadId:sha256,url,sha256,size:bytes.length,mimeType:mime};});
  await page.exposeFunction('fixtureResource',url=>{resourceReads++;if(!files.has(url))throw new Error('fixture missing file');return files.get(url);});
  const install=async()=>{
    await page.goto(url);const f=page.frameLocator('iframe');await f.locator('#scene canvas').waitFor();
    await f.locator('body').evaluate((_,{author,scope})=>{
      const fixtureRuntime=window.napplet;
      fixtureRuntime.shell={onReady(cb){cb({capabilities:{appData:{profile:'soy.app-data/1',scope,maxContentBytes:16384,defaultRelays:['wss://fixture.invalid']}}});return {close(){}};}};
      fixtureRuntime.identity={async getPublicKey(){return author;},onChanged(){return {close(){}};}};
      fixtureRuntime.outbox={query:filters=>window.fixtureQuery(filters),publish:template=>window.fixturePublish(template)};
      fixtureRuntime.upload={async upload({data,mimeType}){const bytes=new Uint8Array(await data.arrayBuffer());let binary='';for(let i=0;i<bytes.length;i+=16384)binary+=String.fromCharCode(...bytes.subarray(i,i+16384));return window.fixtureUpload({base64:btoa(binary),mime:mimeType});}};
      fixtureRuntime.resource={async bytes(url){const r=await window.fixtureResource(url);return new Blob([Uint8Array.from(atob(r.base64),c=>c.charCodeAt(0))],{type:r.mime});}};
    },{author,scope});return f;
  };
  const player={context,page,f:await install(),install};players.push(player);return player;
}
await mkdir('.napplet-space/testing',{recursive:true});
try {
  const a=await player(A),b=await player(B);
  await a.f.locator('#editorTab').click();await a.f.locator('summary').filter({hasText:'Map JSON'}).click();await a.f.locator('#showJson').click();
  const stage=JSON.parse(await a.f.locator('#mapJson').inputValue());
  stage.rooms[0].cameras[0].backdrop=await a.f.locator('body').evaluate(()=>{const c=document.createElement('canvas');c.width=640;c.height=480;const x=c.getContext('2d');x.fillStyle='#283d32';x.fillRect(0,0,640,480);return c.toDataURL('image/png');});
  stage.rooms[0].cameras[0].artSignature=stage.rooms[0].cameras[0].background.signature;
  await a.f.locator('#mapJson').fill(JSON.stringify(stage));await a.f.locator('#applyJson').click();
  await a.f.locator('#levelsTab').click();await a.f.locator('#levelTitle').fill('Player A precinct');denyPublish=true;
  const expectedUploads=stage.assets.length+1;
  await a.f.locator('#publishLevel').click();await a.f.locator('#levelStatus').filter({hasText:'fixture consent denied'}).waitFor({timeout:20000});assert.equal(uploadCount,expectedUploads);
  await a.f.locator('#retryLevel').click();await a.f.locator('#levelStatus').filter({hasText:'Level published'}).waitFor();assert.equal(uploadCount,expectedUploads);assert.equal(events.length,1);
  const pack=JSON.parse(Buffer.from(files.get(JSON.parse(events[0].content).data.package.url).base64,'base64').toString());assert.equal(pack.rooms.length,6);assert.equal(pack.assets.length,pack.rooms.reduce((count,room)=>count+room.cameras.length,0));assert(pack.assets.every(asset=>asset.url&&!asset.path));
  await b.f.locator('#levelsTab').click();await b.f.locator('#refreshLevels').click();await b.f.locator('.level-card').waitFor();assert.equal(await b.f.locator('.level-card h3').innerText(),'Player A precinct');
  await b.f.locator('.level-card').getByRole('button',{name:'Play',exact:true}).click();await b.f.locator('#roomTitle').filter({hasText:'Evidence Room'}).waitFor();assert(await b.f.locator('#levelsView').isHidden());assert(resourceReads>=2);
  // Reopen under the original viewer, edit actual rule fields, then update.
  a.f=await a.install();await a.f.locator('#levelsTab').click();await a.f.locator('#refreshLevels').click();await a.f.locator('.level-card').getByRole('button',{name:'Remix / edit'}).click();await a.f.locator('#editorView').waitFor({state:'visible'});
  await a.f.locator('summary').filter({hasText:'Level settings'}).click();await a.f.locator('#ruleReserve').fill('36');await a.f.locator('#ruleReserve').dispatchEvent('change');await a.f.locator('#levelsTab').click();
  await a.f.locator('#levelTitle').fill('Updated A precinct');await a.f.locator('#updateLevel').click();await a.f.locator('#levelStatus').filter({hasText:'Level published'}).waitFor();assert.equal(events.length,2);assert.equal(JSON.parse(events[1].content).previous,events[0].id);
  // Player B makes a distinct attributed record with independent uploaded art reads.
  await b.f.locator('#levelsTab').click();await b.f.locator('#refreshLevels').click();await b.f.locator('.level-card').getByRole('button',{name:'Remix / edit'}).click();await b.f.locator('#editorView').waitFor({state:'visible'});await b.f.locator('#levelsTab').click();await b.f.locator('#levelTitle').fill('Player B remix');await b.f.locator('#publishLevel').click();await b.f.locator('#levelStatus').filter({hasText:'Level published'}).waitFor();assert.equal(events.length,3);assert.equal(events[2].pubkey,B);assert.equal(JSON.parse(events[2].content).data.remix.author,A);
  assert.equal(await b.f.locator('#unpublishLevel').isEnabled(),true);
  await b.f.locator('#editorTab').click();await b.f.locator('summary').filter({hasText:'Level settings'}).click();await b.f.locator('#stageName').fill('Unsaved local name');await b.f.locator('#stageName').dispatchEvent('change');await b.f.locator('#levelsTab').click();
  await b.f.locator('.level-card').filter({hasText:'Player B remix'}).getByRole('button',{name:'Remix / edit'}).click();await b.f.locator('#levelStatus').filter({hasText:'Save or export'}).waitFor();
  await b.f.locator('#editorTab').click();await b.f.locator('#saveMap').click();await b.f.locator('#status').filter({hasText:'Draft saved'}).waitFor();await b.f.locator('#levelsTab').click();
  // A pending removal uses the same Retry path and never renders its tombstone as a level.
  denyPublish=true;await a.f.locator('#unpublishLevel').click();await a.f.locator('#levelStatus').filter({hasText:'fixture consent denied'}).waitFor();await a.f.locator('#retryLevel').click();await a.f.locator('#levelStatus').filter({hasText:'Unpublished'}).waitFor();assert.equal(await a.f.locator('.level-card').count(),0);
  await b.f.locator('#refreshLevels').click();await b.f.locator('#levelStatus').filter({hasText:'1 recent levels'}).waitFor();assert.equal(await b.f.locator('.level-card h3').innerText(),'Player B remix');
  for(const [w,h] of [[320,560],[900,600],[1600,900]]){await b.page.locator('iframe').evaluate((e,[w,h])=>Object.assign(e.style,{width:`${w}px`,height:`${h}px`,minHeight:'0',flex:'none'}),[w,h]);assert((await b.f.locator('body').evaluate(()=>document.body.scrollWidth))<=w);await b.page.locator('iframe').screenshot({path:`.napplet-space/testing/levels-${w}x${h}.png`});}
  console.log('PASS independent synthetic browser identities: publish/deny/retry, verified remote art/package play, reopen/update, attributed remix, tombstone/retry and library layouts');
  // Native Gamepad API samples inside the actual hosted gameplay iframe.
  await b.page.locator('iframe').evaluate(e=>Object.assign(e.style,{width:'900px',height:'600px'}));await b.f.locator('#playTab').click();await b.f.locator('#startButton').click();
  await b.f.locator('body').evaluate(()=>{window.fixturePads=[{index:0,id:'fixture-pad',mapping:'standard',connected:true,axes:[0,0,0,0],buttons:Array.from({length:17},()=>({value:0,pressed:false,touched:false}))}];Object.defineProperty(navigator,'getGamepads',{configurable:true,value:()=>window.fixturePads});});
  await b.page.waitForTimeout(250);
  const press=async button=>{await b.f.locator('body').evaluate((_,i)=>window.fixturePads[0].buttons[i].value=1,button);await b.page.waitForTimeout(100);await b.f.locator('body').evaluate((_,i)=>window.fixturePads[0].buttons[i].value=0,button);await b.page.waitForTimeout(100);};
  await press(7);assert.equal(await b.f.locator('#ammoCount').innerText(),'05');await press(2);await b.f.locator('#ammoCount').filter({hasText:'06'}).waitFor();assert.equal(await b.f.locator('#ammoCount').innerText(),'06');assert.equal(await b.f.locator('#reserveCount').innerText(),'35');
  await b.f.locator('body').evaluate(()=>window.fixturePads[0].axes[1]=-1);await b.f.locator('#cameraLabel').filter({hasText:'02'}).waitFor();await b.f.locator('body').evaluate(()=>window.fixturePads[0].axes[1]=0);assert.match(await b.f.locator('#cameraLabel').innerText(),/02/);
  await press(9);assert(await b.f.locator('#sceneState').isVisible());await press(9);assert(await b.f.locator('#sceneState').isHidden());
  await b.f.locator('body').evaluate(()=>window.fixturePads=[]);await b.page.waitForTimeout(120);assert(await b.f.locator('#sceneState').isVisible());
  console.log('PASS hosted native controller samples: RT shot, X reload, analog movement/camera cut, Start pause/resume, disconnect pause');
  const readsBefore=resourceReads;b.f=await b.install();await b.f.locator('#editorTab').click();await b.f.locator('#loadMap').click();await b.f.locator('#status').filter({hasText:'Draft loaded'}).waitFor();
  for(let i=0;i<30&&resourceReads===readsBefore;i++)await b.page.waitForTimeout(100);
  assert(resourceReads>readsBefore);await b.f.locator('#levelsTab').click();
  console.log('PASS unsaved edits protected; a reopened SDK draft requests its custom linked image');
  await b.f.locator('body').evaluate(()=>{delete window.napplet.identity;delete window.napplet.outbox;delete window.napplet.upload;delete window.napplet.resource;});
  // A fresh unsupported host has no cached collection.
  const guestContext=await browser.newContext(),guest=await guestContext.newPage();guest.on('pageerror',e=>errors.push(e.message));await guest.goto(url);const gf=guest.frameLocator('iframe');await gf.locator('#scene canvas').waitFor();await gf.locator('body').evaluate(()=>{delete window.napplet.identity;delete window.napplet.outbox;});await gf.locator('#levelsTab').click();await gf.locator('#refreshLevels').click();await gf.locator('#levelStatus').filter({hasText:'sharing is unavailable'}).waitFor();await gf.locator('#openRepositoryLevel').click();await gf.locator('#playView').waitFor({state:'visible'});assert(await gf.locator('#sceneState').isHidden());
  console.log('PASS missing sharing domains leave checked-in level play available');await guestContext.close();
  const touchContext=await browser.newContext({viewport:{width:390,height:844},hasTouch:true,isMobile:true,deviceScaleFactor:1});
  try{const page=await touchContext.newPage();page.on('pageerror',e=>errors.push(e.message));await page.goto(url);const f=page.frameLocator('iframe');await f.locator('#scene canvas').waitFor();await f.locator('body').evaluate(()=>{delete window.napplet.identity;delete window.napplet.outbox;});await f.locator('#levelsTab').tap();await f.locator('#refreshLevels').tap();await f.locator('#levelStatus').filter({hasText:'sharing is unavailable'}).waitFor();await f.locator('#openRepositoryLevel').tap();await f.locator('#playView').waitFor({state:'visible'});await f.locator('#editorTab').tap();await f.locator('summary').filter({hasText:'Level settings'}).tap();await f.locator('#ruleReserve').fill('30');await f.locator('#ruleReserve').dispatchEvent('change');assert.equal(await f.locator('#ruleReserve').inputValue(),'30');console.log('PASS touch library scrolling, repository level selection and level-rule authoring');}finally{await touchContext.close();}
  assert.deepEqual(errors,[]);
}catch(error){for(const [i,p] of players.entries()){console.log('PLAYER',i,await p.f.locator('#levelStatus').innerText(),await p.f.locator('#status').textContent());await p.page.screenshot({path:`.napplet-space/testing/feature-failure-${i}.png`});}console.log({uploadCount,resourceReads,events:events.length,errors});throw error;}finally{await browser.close();}