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/polish-hosted.mjs
import {createRequire} from 'node:module';import {readFile,realpath,mkdir} from 'node:fs/promises';import {execFileSync} from 'node:child_process';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.');
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 executablePath=profile.executables.find(e=>e.name==='chromium-headless-shell'&&e.installed)?.path;
const browser=await chromium.launch({executablePath,headless:true,args:['--enable-webgl','--use-gl=angle','--use-angle=swiftshader']}),page=await browser.newPage({viewport:{width:1450,height:1100}}),errors=[];page.on('pageerror',e=>errors.push(e.message));await mkdir('.napplet-space/testing',{recursive:true});
try{
 await page.goto(url);const f=page.frameLocator('iframe');await f.locator('#scene canvas').waitFor();await f.locator('#editorTab').click();await f.locator('summary').filter({hasText:'Map JSON'}).click();
 const read=async()=>{await f.locator('#showJson').click();return JSON.parse(await f.locator('#mapJson').inputValue());};const apply=async s=>{await f.locator('#mapJson').fill(JSON.stringify(s));await f.locator('#applyJson').click();await f.locator('#status').filter({hasText:'Map JSON applied'}).waitFor();};const initial=await read();
 assert.equal(initial.rooms.reduce((n,r)=>n+r.cameras.length,0),32);assert.match(await f.locator('#cameraCoverage').innerText(),/framed/);
 const plan=f.locator('#plan');const xy=async p=>{await plan.scrollIntoViewIfNeeded();const b=await plan.boundingBox(),d=await plan.evaluate(c=>({...c.dataset}));return{x:b.x+Number(d.offsetX)+p.x*Number(d.scale),y:b.y+Number(d.offsetZ)+p.z*Number(d.scale)};};
 const tap=async p=>{const q=await xy(p);await page.mouse.click(q.x,q.y);};const drag=async(a,b)=>{const p=await xy(a),q=await xy(b);await page.mouse.move(p.x,p.y);await page.mouse.down();await page.mouse.move(q.x,q.y,{steps:8});await page.mouse.up();};
 await f.locator('summary').filter({hasText:'Trigger area'}).click();await f.locator('#drawPolygon').click();
 const points=[{x:-2,z:2},{x:2,z:2},{x:2,z:3.5},{x:0,z:3.5},{x:0,z:4},{x:-2,z:4}];for(const p of points)await tap(p);await f.locator('#finishPolygon').click();let s=await read();assert.equal(s.rooms[0].cameras[0].zone.points.length,6);
 await f.locator('#cameraName').fill('Concave patrol');await f.locator('#cameraName').dispatchEvent('change');assert.deepEqual((await read()).rooms[0].cameras[0].zone,s.rooms[0].cameras[0].zone);
 await drag(points[0],{x:-2.25,z:1.75});s=await read();assert.deepEqual(s.rooms[0].cameras[0].zone.points[0],{x:-2.25,z:1.75});
 const saved=s.rooms[0].cameras[0].zone;await f.locator('#drawPolygon').click();await tap({x:0,z:2});await tap({x:1,z:3});await f.locator('#cancelPolygon').click();assert.deepEqual((await read()).rooms[0].cameras[0].zone,saved);
 await f.locator('[data-tool=herb]').click();await tap({x:-1.5,z:3});s=await read();assert.equal(s.rooms[0].herbs.length,2);await f.locator('#deleteSelection').click();assert.equal((await read()).rooms[0].herbs.length,1);
 await f.locator('#saveMap').click();await f.locator('#status').filter({hasText:'Draft saved'}).waitFor();await f.locator('#loadMap').click();await f.locator('#status').filter({hasText:'Draft loaded'}).waitFor();assert.deepEqual((await read()).rooms[0].cameras[0].zone,saved);
 await page.locator('iframe').screenshot({path:'.napplet-space/testing/polygon-editor.png'});console.log('PASS hosted polygon drawing, vertex dragging, cancellation, field preservation, herb editing and SDK storage roundtrip');
 await apply(initial);s=structuredClone(initial);const r=s.rooms[0];r.props=[];r.dressing=[];r.spawn={x:0,z:2};r.zombies=[{x:0,z:.3},{x:3,z:-3}];r.herbs=[{id:'heal',kind:'green',x:0,z:2.45}];r.cameras=[{id:'wide',name:'Combat test',position:[-4,3.5,5],target:[0,.8,.8],fov:60,zone:{x:0,z:0,w:10,d:9}}];
 await apply(s);await f.locator('#testMap').click();await page.keyboard.down('KeyI');await page.keyboard.press('Space');await page.keyboard.up('KeyI');await f.locator('#gameMessage').filter({hasText:'Headshot'}).waitFor();assert.equal(await f.locator('#ammoCount').innerText(),'05');
 await page.locator('iframe').screenshot({path:'.napplet-space/testing/upward-headshot.png'});await page.keyboard.press('Escape');const frozen=await f.locator('#scene canvas').evaluate(c=>c.toDataURL());await page.waitForTimeout(180);assert.equal(await f.locator('#scene canvas').evaluate(c=>c.toDataURL()),frozen);console.log('PASS upward keyboard headshot, accepted shot ammo and paused animation frame');
 await f.locator('#editorTab').click();s.rooms[0].zombies[0]={x:0,z:1.5};await apply(s);await f.locator('#testMap').click();await f.locator('#gameMessage').filter({hasText:'Bitten'}).waitFor();await page.keyboard.press('KeyE');await f.locator('#gameMessage').filter({hasText:'Green herb'}).waitFor();assert.equal(await f.locator('#healthLabel').innerText(),'Fine');await page.keyboard.press('Escape');console.log('PASS real bite and nearby herb healing');
 await f.locator('#editorTab').click();await apply(initial);await f.locator('#testMap').click();await page.keyboard.down('KeyW');await page.waitForTimeout(350);await page.keyboard.up('KeyW');await page.locator('iframe').screenshot({path:'.napplet-space/testing/animated-patrol.png'});await page.keyboard.press('Escape');
 const touch=await browser.newContext({viewport:{width:390,height:844},hasTouch:true,isMobile:true,deviceScaleFactor:1});try{const tp=await touch.newPage();tp.on('pageerror',e=>errors.push(e.message));await tp.goto(url);const tf=tp.frameLocator('iframe');await tf.locator('#scene canvas').waitFor();await tf.locator('#editorTab').tap();await tf.locator('summary').filter({hasText:'Map JSON'}).tap();const safe=structuredClone(initial);safe.rooms[0].zombies=[];await tf.locator('#mapJson').fill(JSON.stringify(safe));await tf.locator('#applyJson').tap();await tf.locator('#testMap').tap();
 for(const [width,height] of [[320,560],[844,390]]){await tp.setViewportSize({width,height});await tp.locator('iframe').evaluate((e,[w,h])=>Object.assign(e.style,{width:`${w}px`,height:`${h}px`,minHeight:'0',flex:'none'}),[width,height]);for(const control of ['[data-hold=aimUp]','[data-action=shoot]','[data-action=interact]']){const b=await tf.locator(control).boundingBox(),frame=await tp.locator('iframe').boundingBox();assert(b.width>=44&&b.height>=44);assert(b.x>=frame.x&&b.x+b.width<=frame.x+width+1);assert(b.y+b.height<=frame.y+height+1);}await tp.locator('iframe').screenshot({path:`.napplet-space/testing/polish-touch-${width}x${height}.png`});}
 await tf.locator('[data-hold=aimUp]').scrollIntoViewIfNeeded();await tf.locator('[data-action=shoot]').scrollIntoViewIfNeeded();const cdp=await touch.newCDPSession(tp),aim=await tf.locator('[data-hold=aimUp]').boundingBox(),fire=await tf.locator('[data-action=shoot]').boundingBox();await cdp.send('Input.dispatchTouchEvent',{type:'touchStart',touchPoints:[{x:aim.x+aim.width/2,y:aim.y+aim.height/2,id:1}]});await cdp.send('Input.dispatchTouchEvent',{type:'touchStart',touchPoints:[{x:aim.x+aim.width/2,y:aim.y+aim.height/2,id:1},{x:fire.x+fire.width/2,y:fire.y+fire.height/2,id:2}]});await cdp.send('Input.dispatchTouchEvent',{type:'touchEnd',touchPoints:[{x:aim.x+aim.width/2,y:aim.y+aim.height/2,id:1}]});await cdp.send('Input.dispatchTouchEvent',{type:'touchEnd',touchPoints:[]});await tf.locator('#ammoCount').filter({hasText:'05'}).waitFor();assert.equal(await tf.locator('.held').count(),0);console.log('PASS touch portrait/landscape Aim-up, fire, interact reachability and two-pointer firing');
 }finally{await touch.close();}assert.deepEqual(errors,[]);
}catch(e){await page.screenshot({path:'.napplet-space/testing/polish-failure.png'});throw e;}finally{await browser.close();}