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.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { testModules } from '../scripts/test-modules.mjs';
const {createStage,parseStage,signature,Game,levelSnapshot,validateLevelRecord,sha256,verifiedBlob,LevelFiles,appDataCollection,decodeDataRecord}=await testModules();
const A='a'.repeat(64),B='b'.repeat(64),scope='c'.repeat(64);
const listing={package:{url:'https://assets.example/level.json',sha256:'d'.repeat(64),size:100,mime:'application/json'},description:'Fixture map',rooms:6,zombies:8};
function fixture(){
let viewer=A,sequence=0,fail=false;const events=[];
const host={shell:{onReady(cb){cb({capabilities:{appData:{profile:'soy.app-data/1',scope,defaultRelays:['wss://relay.example'],maxContentBytes:16384}}});return {close(){}};}},identity:{async getPublicKey(){return viewer;}},outbox:{
async query(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};},
async publish(template){if(fail){fail=false;return {ok:false,error:'fixture denied'};}const {id,body}=decodeDataRecord(template);const base=events.filter(e=>e.pubkey===viewer&&decodeDataRecord(e).id===id).sort((a,b)=>b.created_at-a.created_at)[0];
if((base?.id??null)!==body.previous)return {ok:false,error:'app-data-stale'};
const event={...structuredClone(template),pubkey:viewer,id:(++sequence).toString(16).padStart(64,'0'),sig:'0'.repeat(128)};events.push(event);return {ok:true,event};}
}};
return {host,events,viewer(v){viewer=v;},deny(){fail=true;}};
}
test('two viewer identities discover, reopen/update, remix and remove; failed/stale writes preserve retry',async()=>{
const f=fixture(),collection=await appDataCollection({host:f.host,collection:'levels',schema:'soyindent.level',version:1,validate:validateLevelRecord});
const prepared=await collection.prepare({id:'test-level',title:'Original',data:listing,base:null});f.deny();await assert.rejects(prepared.publish(),/denied/);const original=await prepared.publish();
f.viewer('');assert.equal((await collection.list()).records.length,1);await assert.rejects(collection.prepare({id:'guest',title:'Guest',data:listing,base:null}),/not-signed-in/);
f.viewer(B);const found=(await collection.list()).records[0];assert.equal(found.author,A);assert.equal(found.revision,original.revision);
await assert.rejects(collection.prepare({id:original.id,title:'Stolen',data:listing,base:original}),/owner-mismatch/);
const remix=await(await collection.prepare({id:'remix-level',title:'Remix',data:{...listing,remix:{author:A,id:found.id,scope,revision:found.revision}},base:null})).publish();assert.equal(remix.author,B);
f.viewer(A);const reopened=(await collection.get(A,original.id)).record;
const stale=await collection.prepare({id:original.id,title:'Stale edit',data:listing,base:reopened});
const update=await(await collection.prepare({id:original.id,title:'Updated',data:listing,base:reopened})).publish();assert.equal((await collection.get(A,original.id)).record.title,'Updated');await assert.rejects(stale.publish(),/stale/);
const tombstone=await collection.prepare({id:update.id,title:update.title,base:update,deleted:true});f.deny();await assert.rejects(tombstone.publish(),/denied/);assert((await tombstone.publish()).deleted);assert.equal((await collection.list()).records.length,1);
const identityChange=await collection.prepare({id:'another',title:'Another',data:listing,base:null});f.viewer(B);await assert.rejects(identityChange.publish(),/identity-changed/);
});
test('level packs roundtrip rules and geometry; altered bytes/MIME/counts are rejected',async()=>{
const stage=createStage();stage.assets=[];for(const r of stage.rooms)for(const c of r.cameras)delete c.background;
stage.rules={playerHealth:75,reserve:12,zombieHealth:3,zombieSpeed:.7,biteDamage:20};
// Imported spawn metadata must not override the explicit level rules/player state.
stage.rooms[0].spawn.hp=0;stage.rooms[0].spawn.yaw='invalid';
let fail=true,uploads=0;const store=new Map(),io={async read(ref){return store.get(ref.url);},async upload(blob){uploads++;if(fail){fail=false;throw new Error('fixture upload denied');}const hash=await sha256(blob),url=`https://assets.example/${hash}`;store.set(url,blob);return {url,sha256:hash,size:blob.size,mime:blob.type};}};
const files=new LevelFiles([],io),before=JSON.stringify(stage);await assert.rejects(files.prepare(stage),/denied/);assert.equal(JSON.stringify(stage),before);
const ref=await files.prepare(stage),loaded=await files.load(ref);assert.deepEqual(loaded.rooms,stage.rooms);assert.deepEqual(loaded.rules,stage.rules);assert.deepEqual(loaded.rooms[0].herbs,stage.rooms[0].herbs);assert.deepEqual(loaded.rooms[0].cameras.find(c=>c.door).zone,stage.rooms[0].cameras.find(c=>c.door).zone);assert.equal(uploads,2);await files.prepare(stage);assert.equal(uploads,2);
const game=new Game(loaded);assert.equal(game.player.hp,75);assert.equal(game.reserve,12);assert(game.zombies.every(z=>z.hp===3));
await assert.rejects(verifiedBlob(new Blob(['changed'],{type:'application/json'}),ref),/check/);
const wrong=new Blob([await store.get(ref.url).text()],{type:'text/plain'});await assert.rejects(verifiedBlob(wrong,ref),/MIME/);
assert.throws(()=>validateLevelRecord({...listing,rooms:17}));assert.throws(()=>validateLevelRecord({...listing,package:{...listing.package,url:'javascript:alert(1)'}}));
const invalid=structuredClone(stage);invalid.rules.zombieHealth=0;assert.throws(()=>parseStage(JSON.stringify(invalid)));files.dispose();
});
test('geometry edits prune stale art without mutating the live editor and malformed latest records hide old values',async()=>{
const stage=createStage(),original=JSON.stringify(stage),staleAsset=stage.rooms[0].cameras[0].background.asset;stage.rooms[0].cameras[0].position[1]+=.5;const edited=JSON.stringify(stage),snapshot=levelSnapshot(stage);assert.equal(JSON.stringify(stage),edited);assert(!snapshot.rooms[0].cameras[0].background);assert.equal(snapshot.assets.length,stage.assets.length-1);assert(!snapshot.assets.some(a=>a.id===staleAsset));assert.notEqual(edited,original);
assert.equal(snapshot.rooms[0].cameras[1].background.signature,signature(snapshot.rooms[0],snapshot.rooms[0].cameras[1]));
const f=fixture(),c=await appDataCollection({host:f.host,collection:'levels',schema:'soyindent.level',version:1,validate:validateLevelRecord});const record=await(await c.prepare({id:'bad-level',title:'Good',data:listing,base:null})).publish();
f.events.push({...record.event,id:'e'.repeat(64),created_at:record.createdAt+1,content:JSON.stringify({schema:'soyindent.level',version:1,title:'Broken',previous:record.revision,deleted:false,data:{rooms:1000}})});
const result=await c.list();assert.equal(result.records.length,0);assert.equal(result.invalid,1);
});
