Back to GRAV / RIFT
SOURCE / PINNED RELEASE

Made of little things.

GRAV / RIFT

Release
288aa261ce07…
Author-recorded commit
c549b5a4e17e…
License
LICENSE
Author’s source reference
nostr://npub182jczunncwe0jn6frpqwq3e0qjws7yqqnc3auccqv9nte2dnd63scjm4rf/wss%3A%2F%2Fgit.napplet.soy%2F/n-8a7361981e8

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

tests/foundry.mjs
// Real user inputs in the soyLI sandbox; no game-state mutation.
import assert from 'node:assert/strict';
import {fireKeyboard,fireTouch} from './input.mjs';
import {bossPilot} from './boss-pilot.mjs';
import {createRequire} from 'node:module';
import {realpathSync,mkdirSync} from 'node:fs';
const require=createRequire(realpathSync(new URL('../node_modules/@napplet/conformance-cli/package.json',import.meta.url)));
const {chromium}=require('playwright');
const url=process.argv.slice(2).find(arg=>/^https?:\/\//.test(arg));if(!url)throw new Error('Pass the soyli dev URL.');
const browser=await chromium.launch();const errors=[];
mkdirSync('.napplet-space/promotion',{recursive:true});
let page;
try{
 page=await browser.newPage({viewport:{width:1200,height:750}});page.on('pageerror',e=>errors.push(e.message));
 await page.goto(url);await page.locator('iframe').waitFor();const frame=page.frames().find(f=>f!==page.mainFrame());await frame.locator('#deploy').waitFor();
 await page.addStyleTag({content:'#stage{position:fixed!important;inset:0!important;z-index:100!important}#stage iframe{width:100vw!important;height:100dvh!important;min-height:0!important}body{overflow:hidden!important}'});
 await page.clock.install({time:new Date('2026-09-27T12:00:00Z')});await page.clock.pauseAt(new Date('2026-09-27T12:01:00Z'));await page.clock.runFor(100);
 const state=()=>frame.locator('#game').evaluate(n=>({...n.dataset}));
 const aim=async(x,y)=>{const {d,r}=await frame.locator('#game').evaluate(n=>({d:{...n.dataset},r:n.getBoundingClientRect().toJSON()}));await page.mouse.move(r.left+(x-Number(d.camera))*Number(d.viewScale),r.top+(y-Number(d.cameraY))*Number(d.viewScale));};
 assert.equal(await frame.locator('#site').inputValue(),'foundry');
 await page.screenshot({path:'.napplet-space/promotion/foundry-menu.png'});
 await frame.locator('#practice').click();await frame.locator('#sound').click();await page.clock.runFor(1000);
 assert.equal((await state()).explosions,'0','floor cells stay stable until disturbed');
 await page.screenshot({path:'.napplet-space/promotion/foundry-floor.png'});
 await aim(640,822);await page.keyboard.down('Space');await page.clock.runFor(1400);
 let d=await state();console.log('COLLECTED',d);assert.ok(Number(d.payload)>5);assert.ok(Number(d.cellsHeld)>0,'floor cells can be collected');
 await page.screenshot({path:'.napplet-space/promotion/foundry-gather.png'});
 await aim(850,735);await page.clock.runFor(350);const before=Number((await state()).explosions);
 await fireKeyboard(page);await page.keyboard.up('Space');
 for(let i=0;i<60&&Number((await state()).explosions)===before;i++)await page.clock.runFor(25);
 assert.ok(Number((await state()).explosions)>before,'thrown cell detonates through actual collision');
 await page.screenshot({path:'.napplet-space/promotion/foundry-explosion.png'});
 await page.clock.runFor(650);d=await state();console.log('DEMOLITION',d);
 assert.ok(Number(d.demolished)>0,'explosion tears apart a real structure');
 await page.screenshot({path:'.napplet-space/promotion/foundry-demolition.png'});
 // Restart so an explosion chain cannot pre-demolish the tether test target.
 await frame.locator('#pause').click();await frame.locator('#retry').click();
 await page.keyboard.down('KeyD');
 for(let i=0;i<40&&Number((await state()).shipX)<550;i++)await page.clock.runFor(100);
 await page.keyboard.up('KeyD');await page.clock.runFor(300);
 await aim(850,740);await page.keyboard.press('Digit3');const ripped=Number((await state()).demolished);
 await page.keyboard.down('Space');await page.clock.runFor(2500);await fireKeyboard(page);await page.keyboard.up('Space');await page.clock.runFor(200);
 assert.ok(Number((await state()).demolished)>ripped,'rip tether removes support joints');
 await page.screenshot({path:'.napplet-space/promotion/foundry-tether.png'});
 await page.keyboard.down('KeyD');
 for(let i=0;i<80&&Number((await state()).shipX)<3800;i++)await page.clock.runFor(100);
 await page.keyboard.up('KeyD');await page.clock.runFor(500);await aim(4160,648);
 const trackBoss=bossPilot(page,frame);
 await page.keyboard.press('Digit3');await page.keyboard.down('Space');await trackBoss(8000);await fireKeyboard(page);await page.keyboard.up('Space');await trackBoss(1800);
 await page.keyboard.press('Digit2');
 for(let i=0;i<16&&await frame.locator('#app').getAttribute('data-phase')==='playing';i++){
  await page.keyboard.down('Space');await trackBoss(3000);await fireKeyboard(page);await page.keyboard.up('Space');await trackBoss(1800);
 }
 console.log('FOUNDRY BOSS',await state());assert.equal(await frame.locator('#app').getAttribute('data-phase'),'won');
 await page.screenshot({path:'.napplet-space/promotion/foundry-victory.png'});
 await frame.locator('#retry').click();
 assert.equal((await state()).site,'foundry');assert.equal((await state()).demolished,'0');
 await frame.locator('#pause').click();await frame.locator('#changeSite').click();
 await frame.locator('#site').selectOption('orbit');await frame.locator('#practice').click();
 assert.equal((await state()).site,'orbit');assert.equal((await state()).structureParts,'0');
 for(const [w,h] of [[390,844],[360,640],[844,390]]){
  const context=await browser.newContext({viewport:{width:w,height:h},isMobile:true,hasTouch:true});const phone=await context.newPage();phone.on('pageerror',e=>errors.push(e.message));
  await phone.goto(url);await phone.locator('iframe').waitFor();const f=phone.frames().find(f=>f!==phone.mainFrame());await f.locator('#deploy').waitFor();
  await phone.addStyleTag({content:'#stage{position:fixed!important;inset:0!important;z-index:100!important}#stage iframe{width:100vw!important;height:100dvh!important;min-height:0!important}body{overflow:hidden!important}'});
  await phone.clock.install({time:new Date('2026-09-27T12:00:00Z')});await phone.clock.pauseAt(new Date('2026-09-27T12:01:00Z'));await phone.clock.runFor(100);await f.locator('#practice').tap();
  const stick=await f.locator('#joystick').boundingBox(),field=await f.locator('#fieldButton').boundingBox(),cdp=await context.newCDPSession(phone);
  const touch=(id,x,y)=>({id,x,y,radiusX:4,radiusY:4,force:1});
  await cdp.send('Input.dispatchTouchEvent',{type:'touchStart',touchPoints:[touch(1,stick.x+stick.width/2+20,stick.y+stick.height/2),touch(2,field.x+field.width/2,field.y+field.height/2)]});
  await phone.clock.runFor(1800);assert.ok(Number(await f.locator('#game').getAttribute('data-payload'))>0,`${w}: collect floor salvage using touch`);
  await phone.screenshot({path:`.napplet-space/promotion/foundry-touch-${w}x${h}.png`});
  await fireTouch(phone,f,cdp,[touch(1,stick.x+stick.width/2+20,stick.y+stick.height/2),touch(2,field.x+field.width/2,field.y+field.height/2)]);await cdp.send('Input.dispatchTouchEvent',{type:'touchEnd',touchPoints:[]});await phone.clock.runFor(200);assert.equal(await f.locator('#game').getAttribute('data-shots'),'1');
  await f.locator('[data-mode="2"]').tap();assert.equal(await f.locator('[data-mode="2"]').getAttribute('aria-pressed'),'true');
  await context.close();
 }
 assert.deepEqual(errors,[]);console.log('FOUNDRY: stable floor salvage, cell collection, first-impact explosion, demolition, tether, boss victory, retry, site switching, and all three touch sizes passed.');
}catch(error){if(page)await page.screenshot({path:'.napplet-space/promotion/foundry-failure.png'});throw error;}
finally{await browser.close();}