Repository navigation
Expand file tree
/
Copy pathranch-world.js
More file actions
52 lines (52 loc) · 21.3 KB
/
Copy pathranch-world.js
File metadata and controls
52 lines (52 loc) · 21.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
(function(root){
'use strict';
const Q=typeof module!=='undefined'?require('./core.js'):root.Quinelings;
const C=typeof module!=='undefined'?require('./ranch-crypto.js'):root.RanchCrypto;
const LIMITS=Object.freeze({residents:32,nursery:8,pairs:16,proposals:16,events:256,receipts:256,ceiling:1000000,bytes:1048576,width:512,height:320,guard:24,invitation:120,approach:160,attempt:240,court:80,proposal:600,cooldown:200});
const FOUNDERS=[{x:120,y:24},{x:168,y:24},{x:344,y:24},{x:392,y:24},{x:24,y:144},{x:72,y:144},{x:440,y:144},{x:488,y:144},{x:168,y:296},{x:344,y:296}];
const SPAWNS=Object.freeze([...FOUNDERS,...Array.from({length:60},(_,i)=>({x:24+(i%10)*48,y:24+Math.floor(i/10)*48})).filter(p=>!FOUNDERS.some(s=>s.x===p.x&&s.y===p.y))].map(Object.freeze));
const CLEARINGS=Object.freeze([[{x:120,y:96},{x:168,y:96}],[{x:344,y:96},{x:392,y:96}],[{x:120,y:224},{x:168,y:224}],[{x:344,y:224},{x:392,y:224}]].map(a=>Object.freeze(a.map(Object.freeze))));
function fail(code,message,path=''){const e=new Error(message);e.code=code;e.path=path;throw e;}
function plain(v,seen=new Set(),path='',depth=0){if(depth>32)fail('invalid-input','Data too deep',path);if(v===null||typeof v==='string'||typeof v==='boolean'||typeof v==='number'&&Number.isFinite(v))return;if(typeof v!=='object'||seen.has(v))fail('invalid-input','Only detached acyclic data accepted',path);const p=Object.getPrototypeOf(v);if(Array.isArray(v)?p!==Array.prototype:p!==Object.prototype&&p!==null)fail('invalid-input','Plain records required',path);if(Array.isArray(v)){const ks=Reflect.ownKeys(v).filter(k=>k!=='length');if(ks.length!==v.length||ks.some((k,i)=>k!==String(i)))fail('invalid-input','Dense closed arrays required',path);}seen.add(v);for(const k of Reflect.ownKeys(v)){if(typeof k!=='string')fail('invalid-input','Symbol field',path);if(Array.isArray(v)&&k==='length')continue;const d=Object.getOwnPropertyDescriptor(v,k);if(!d||!('value'in d)||!d.enumerable)fail('invalid-input','Accessor or hidden field',path+'.'+k);plain(d.value,seen,path+'.'+k,depth+1);}seen.delete(v);}
function closed(v,keys){if(!v||Array.isArray(v)||typeof v!=='object'||Object.keys(v).length!==keys.length||keys.some(k=>!Object.hasOwn(v,k)))fail('invalid-input','Closed fields required: '+keys.join(','));}
function str(v,max=128){if(typeof v!=='string'||v.length<1||v.length>max)fail('invalid-input','Invalid string');}
function num(v,min=0,max=LIMITS.ceiling){if(!Number.isInteger(v)||v<min||v>max)fail('invalid-input','Integer outside bounds');}
function hash(v){if(typeof v!=='string'||! /^[a-f0-9]{64}$/.test(v))fail('invalid-input','Invalid source/intent hash');}
const clone=v=>JSON.parse(JSON.stringify(v));
function bytes(v){const s=JSON.stringify(v);return typeof TextEncoder!=='undefined'?new TextEncoder().encode(s).length:Buffer.byteLength(s);}
function inc(n){if(n>=LIMITS.ceiling)fail('capacity','Counter ceiling');return n+1;}
function deadline(w,n){if(w.tick+n>LIMITS.ceiling)fail('capacity','Deadline ceiling');return w.tick+n;}
function overlap(a,b){return Math.abs(a.x-b.x)<48&&Math.abs(a.y-b.y)<48;}
function inside(p){return Number.isInteger(p.x)&&Number.isInteger(p.y)&&p.x>=24&&p.x<=488&&p.y>=24&&p.y<=296;}
function liveSlots(w){return w.pairs.flatMap(p=>p.slots.map((s,i)=>({...s,owner:p.parentResidents[i]})));}
function free(w,p,owners=[]){return inside(p)&&w.residents.every(r=>owners.includes(r.id)||!overlap(p,r))&&liveSlots(w).every(s=>owners.includes(s.owner)||!overlap(p,s));}
function spawn(w){const p=SPAWNS.find(s=>free(w,s));if(!p)fail('capacity','No guard-safe spawn');return p;}
function event(w,kind,ids=[]){w.events.push({tick:w.tick,kind,residentIds:ids.slice()});if(w.events.length>256){w.events.shift();w.droppedEvents=inc(w.droppedEvents);}}
function resident(w,id){str(id);const r=w.residents.find(r=>r.id===id);if(!r)fail('not-found','Resident missing');return r;}
function cooldown(w,r,n){r.readyAfterTick=Math.max(r.readyAfterTick,Math.min(LIMITS.ceiling,w.tick+n));}
function removeProposal(w,p,kind){w.proposals=w.proposals.filter(x=>x.id!==p.id);for(const id of p.parentResidents){const r=w.residents.find(r=>r.id===id);if(r&&r.pendingProposal===p.id)r.pendingProposal=null;}event(w,kind,p.parentResidents);}
function removePair(w,p,kind){w.pairs=w.pairs.filter(x=>x.id!==p.id);for(const id of p.parentResidents){const r=resident(w,id);r.partner=null;r.invitation=null;cooldown(w,r,40);}event(w,kind,p.parentResidents);}
function cleanup(w,r){for(const p of w.pairs.slice())if(p.parentResidents.includes(r.id))removePair(w,p,'pair-aborted');for(const p of w.proposals.slice())if(p.parentResidents.includes(r.id))removeProposal(w,p,'proposal-cancelled');r.invitation=null;for(const a of w.residents)if(a.invitation&&a.invitation.partnerId===r.id)a.invitation=null;}
function artifact(c){closed(c,['artifact']);const a=c.artifact;closed(a,['id','sourceHash','intentHash','roles','gestureKind']);str(a.id);hash(a.sourceHash);if(a.intentHash!==null)hash(a.intentHash);if(!Array.isArray(a.roles)||a.roles.length>64)fail('invalid-input','Bounded roles required');a.roles.forEach(x=>str(x,64));str(a.gestureKind,64);return a;}
function insert(w,a,child){if(w.residents.length>=32||child&&w.residents.filter(r=>r.nurseryUntil>0).length>=8)fail('capacity','Resident/nursery cap');const p=spawn(w),n=child?deadline(w,200):0;const id='wr_'+w.nextResident;w.nextResident=inc(w.nextResident);w.residents.push({id,artifactId:a.id,sourceHash:a.sourceHash,intentHash:a.intentHash,epoch:0,enabled:false,energy:child?40:60,rest:false,nurseryUntil:n,readyAfterTick:0,x:p.x,y:p.y,heading:0,invitation:null,partner:null,pendingProposal:null,roles:a.roles.slice(),gestureKind:a.gestureKind});event(w,child?'birth':'import',[id]);return id;}
function eligible(w,r){return r.enabled&&!r.nurseryUntil&&!r.rest&&r.energy>=60&&w.tick>=r.readyAfterTick&&!r.partner&&!r.pendingProposal;}
function affinity(a,b,neutral=false){if(neutral)return -Math.max(Math.abs(a.x-b.x),Math.abs(a.y-b.y));const ar=new Set(a.roles),br=new Set(b.roles);let common=0;for(const r of ar)if(br.has(r))common++;return 8*common+4*(a.gestureKind===b.gestureKind)+2*(a.sourceHash!==b.sourceHash)-Math.max(Math.abs(a.x-b.x),Math.abs(a.y-b.y));}
function pairings(w){const adults=w.residents.filter(r=>eligible(w,r));for(const r of adults)if(!r.invitation){const b=adults.filter(a=>a.id!==r.id).sort((a,b)=>affinity(r,b,w.affinity==='neutral')-affinity(r,a,w.affinity==='neutral')||Number(a.id.slice(3))-Number(b.id.slice(3)))[0];if(b)r.invitation={partnerId:b.id,expiry:deadline(w,120)};}
const mutual=adults.filter(a=>a.invitation).map(a=>[a,adults.find(b=>b.id===a.invitation.partnerId)]).filter(([a,b])=>b&&b.invitation&&b.invitation.partnerId===a.id&&Number(a.id.slice(3))<Number(b.id.slice(3)));
for(const [a,b]of mutual){if(a.partner||b.partner||w.pairs.length>=16)continue;const slots=CLEARINGS.find(s=>s.every((p,i)=>free(w,p,[a.id,b.id])&&Math.max(Math.abs(p.x-[a,b][i].x),Math.abs(p.y-[a,b][i].y))<=160));if(!slots)continue;const start=w.tick,p={id:'pair_'+a.id+'_'+b.id+'_'+start,parentResidents:[a.id,b.id],slots:clone(slots),startTick:start,approachDeadline:deadline(w,160),attemptDeadline:deadline(w,240),dwell:0,arrived:false};w.pairs.push(p);a.partner=b.id;b.partner=a.id;a.invitation=b.invitation=null;event(w,'pair-created',p.parentResidents);}}
const MOVES=[[0,0],[1,0],[1,1],[0,1],[-1,1],[-1,0],[-1,-1],[0,-1],[1,-1]];
function box(a,b){return {l:Math.min(a.x,b.x)-24,r:Math.max(a.x,b.x)+24,t:Math.min(a.y,b.y)-24,b:Math.max(a.y,b.y)+24};}
function boxes(a,b){return a.l<b.r&&a.r>b.l&&a.t<b.b&&a.b>b.t;}
function move(w){const old=w.residents.map(r=>({id:r.id,x:r.x,y:r.y})),accepted=[];const order=w.residents.slice().sort((a,b)=>Number(a.id.slice(3))-Number(b.id.slice(3)));if(order.length){const rotation=w.tick%order.length;order.push(...order.splice(0,rotation));}for(const r of order){const p=w.pairs.find(p=>p.parentResidents.includes(r.id));const goal=p?p.slots[p.parentResidents.indexOf(r.id)]:r.enabled&&!r.rest&&!r.nurseryUntil?SPAWNS[(w.seed%SPAWNS.length+Number(r.id.slice(3))*7+Math.floor(w.tick/80)*11)%SPAWNS.length]:r;const moves=MOVES.map(([dx,dy],i)=>{const q={x:r.x+dx,y:r.y+dy};const crowd=old.filter(o=>o.id!==r.id&&Math.max(Math.abs(o.x-q.x),Math.abs(o.y-q.y))<96).length;return {q,i,rank:[Math.max(Math.abs(q.x-goal.x),Math.abs(q.y-goal.y)),Math.abs(q.x-goal.x)+Math.abs(q.y-goal.y),crowd,i===0?0:Math.min(Math.abs(i-r.heading),8-Math.abs(i-r.heading)),i]};}).sort((a,b)=>{for(let k=0;k<5;k++)if(a.rank[k]!==b.rank[k])return a.rank[k]-b.rank[k];return 0;});for(const m of moves){const sweep=box(r,m.q);if(!inside(m.q)||old.some(o=>o.id!==r.id&&boxes(sweep,box(o,o)))||accepted.some(s=>boxes(sweep,s))||liveSlots(w).some(s=>!(p?p.parentResidents.includes(s.owner):s.owner===r.id)&&overlap(m.q,s)))continue;r.x=m.q.x;r.y=m.q.y;if(m.i)r.heading=m.i;accepted.push(sweep);break;}}}
function step(w){w.tick=inc(w.tick);for(const r of w.residents)if(r.nurseryUntil&&w.tick>=r.nurseryUntil){r.nurseryUntil=0;event(w,'mature',[r.id]);}if(w.tick%20===0){for(const r of w.residents){const delta=r.nurseryUntil||r.rest?4:r.partner?-2:w.tick<r.readyAfterTick?2:-1;r.energy=Math.max(0,Math.min(100,r.energy+delta));if(r.energy<20)r.rest=true;else if(r.energy>=60)r.rest=false;}for(const r of w.residents)if(r.energy<20)cleanup(w,r);}
for(const p of w.pairs.slice())if(w.tick>=p.attemptDeadline||!p.arrived&&w.tick>=p.approachDeadline)removePair(w,p,'pair-expired');for(const p of w.proposals.slice())if(w.tick>=p.expiry)removeProposal(w,p,'proposal-expired');for(const r of w.residents)if(r.invitation&&w.tick>=r.invitation.expiry){r.invitation=null;cooldown(w,r,20);}pairings(w);move(w);
for(const p of w.pairs.slice()){const rs=p.parentResidents.map(id=>resident(w,id));const at=rs.every((r,i)=>r.x===p.slots[i].x&&r.y===p.slots[i].y);if(at)p.arrived=true;p.dwell=at?p.dwell+1:0;if(p.dwell===80){if(w.proposals.length>=16)fail('capacity','Proposal cap');const expiry=deadline(w,600),proposal={id:'wp_'+p.id,parentResidents:p.parentResidents.slice(),artifactIds:rs.map(r=>r.artifactId),sourceHashes:rs.map(r=>r.sourceHash),intentHashes:rs.map(r=>r.intentHash),epochs:rs.map(r=>r.epoch),createdTick:w.tick,expiry};w.pairs=w.pairs.filter(x=>x.id!==p.id);w.proposals.push(proposal);for(const r of rs){r.partner=null;r.invitation=null;r.pendingProposal=proposal.id;cooldown(w,r,200);}event(w,'proposal-created',proposal.parentResidents);}}}
function create(config){plain(config);closed(config,Object.hasOwn(config,'affinity')?['worldKey','seed','affinity']:['worldKey','seed']);str(config.worldKey,64);num(config.seed,0,4294967295);const affinity=config.affinity===undefined?'structural':config.affinity;if(!['structural','neutral'].includes(affinity))fail('invalid-input','Unknown affinity mode');const normalized={worldKey:config.worldKey,seed:config.seed,affinity};return {id:'qw_'+C.sha256(Q.canon(normalized)),worldKey:config.worldKey,seed:config.seed,affinity,tick:0,revision:0,nextResident:1,nextSequence:1,residents:[],pairs:[],proposals:[],events:[],droppedEvents:0,receipts:[]};}
function validate(w){plain(w);closed(w,['id','worldKey','seed','affinity','tick','revision','nextResident','nextSequence','residents','pairs','proposals','events','droppedEvents','receipts']);str(w.id);str(w.worldKey,64);if(!['structural','neutral'].includes(w.affinity))fail('invalid-world','Affinity mode');num(w.seed,0,4294967295);for(const k of ['tick','revision','nextResident','nextSequence','droppedEvents'])num(w[k]);for(const [k,cap]of [['residents',32],['pairs',16],['proposals',16],['events',256],['receipts',256]])if(!Array.isArray(w[k])||w[k].length>cap)fail('invalid-world','Collection bound');if(w.id!=='qw_'+C.sha256(Q.canon({worldKey:w.worldKey,seed:w.seed,affinity:w.affinity})))fail('invalid-world','World config identity');if(w.residents.filter(r=>r.nurseryUntil>0).length>8)fail('invalid-world','Nursery cap');const ids=new Set();for(const r of w.residents){closed(r,['id','artifactId','sourceHash','intentHash','epoch','enabled','energy','rest','nurseryUntil','readyAfterTick','x','y','heading','invitation','partner','pendingProposal','roles','gestureKind']);str(r.id);if(!/^wr_[1-9][0-9]*$/.test(r.id)||Number(r.id.slice(3))>=w.nextResident)fail('invalid-world','Resident counter identity');if(ids.has(r.id)||!inside(r))fail('invalid-world','Resident identity/geometry');ids.add(r.id);artifact({artifact:{id:r.artifactId,sourceHash:r.sourceHash,intentHash:r.intentHash,roles:r.roles,gestureKind:r.gestureKind}});for(const k of ['epoch','nurseryUntil','readyAfterTick'])num(r[k]);num(r.energy,0,100);num(r.heading,0,8);if(typeof r.enabled!=='boolean'||typeof r.rest!=='boolean')fail('invalid-world','Boolean resident state');if(r.invitation){closed(r.invitation,['partnerId','expiry']);if(!w.residents.some(a=>a.id===r.invitation.partnerId)||r.invitation.partnerId===r.id)fail('invalid-world','Invitation owner');num(r.invitation.expiry);}if(r.partner!==null&&!w.pairs.some(p=>p.parentResidents.includes(r.id)&&p.parentResidents.includes(r.partner)))fail('invalid-world','Pair backlink');if(r.pendingProposal!==null&&!w.proposals.some(p=>p.id===r.pendingProposal&&p.parentResidents.includes(r.id)))fail('invalid-world','Proposal backlink');}for(let i=0;i<w.residents.length;i++)for(let j=0;j<i;j++)if(overlap(w.residents[i],w.residents[j]))fail('invalid-world','Overlapping guards');const paired=new Set(),pending=new Set();for(const p of w.pairs){closed(p,['id','parentResidents','slots','startTick','approachDeadline','attemptDeadline','dwell','arrived']);str(p.id);if(!Array.isArray(p.parentResidents)||!Array.isArray(p.slots)||p.parentResidents.length!==2||p.slots.length!==2||p.parentResidents[0]===p.parentResidents[1]||overlap(...p.slots))fail('invalid-world','Pair shape');for(const slot of p.slots)closed(slot,['x','y']);if(!CLEARINGS.some(c=>Q.canon(c)===Q.canon(p.slots)))fail('invalid-world','Unknown clearing');num(p.startTick);num(p.approachDeadline);num(p.attemptDeadline);num(p.dwell,0,79);if(p.approachDeadline!==p.startTick+160||p.attemptDeadline!==p.startTick+240||typeof p.arrived!=='boolean')fail('invalid-world','Immutable deadline');for(let i=0;i<2;i++){const r=resident(w,p.parentResidents[i]);if(paired.has(r.id)||r.partner!==p.parentResidents[1-i]||r.pendingProposal||r.invitation||!inside(p.slots[i]))fail('invalid-world','Pair ownership');paired.add(r.id);if(!free(w,p.slots[i],p.parentResidents))fail('invalid-world','Invalid reservation');}}for(const p of w.proposals){closed(p,['id','parentResidents','artifactIds','sourceHashes','intentHashes','epochs','createdTick','expiry']);str(p.id);if(['parentResidents','artifactIds','sourceHashes','intentHashes','epochs'].some(k=>!Array.isArray(p[k])||p[k].length!==2)||p.parentResidents[0]===p.parentResidents[1]||p.expiry!==p.createdTick+600)fail('invalid-world','Proposal shape');num(p.createdTick);num(p.expiry);if(p.expiry<=w.tick)fail('invalid-world','Expired retained proposal');for(let i=0;i<2;i++){const r=resident(w,p.parentResidents[i]);if(pending.has(r.id)||r.pendingProposal!==p.id||r.partner||r.invitation||r.artifactId!==p.artifactIds[i]||r.sourceHash!==p.sourceHashes[i]||r.intentHash!==p.intentHashes[i]||r.epoch!==p.epochs[i])fail('invalid-world','Stale proposal');pending.add(r.id);}}for(const k of ['pairs','proposals'])if(new Set(w[k].map(p=>p.id)).size!==w[k].length)fail('invalid-world','Duplicate lifecycle id');for(const e of w.events){closed(e,['tick','kind','residentIds']);num(e.tick,0,w.tick);str(e.kind,64);if(!Array.isArray(e.residentIds)||e.residentIds.length>32)fail('invalid-world','Event residents');e.residentIds.forEach(id=>str(id));}let seq=0;for(const receipt of w.receipts){closed(receipt,['sequence','payloadHash','result']);num(receipt.sequence,1,w.nextSequence-1);hash(receipt.payloadHash);if(receipt.sequence<=seq)fail('invalid-world','Receipt ordering');seq=receipt.sequence;const r=receipt.result,allowed=['worldId','revision','sequence','tick','residentId','proposalId','appliedTicks'];if(!r||typeof r!=='object'||Array.isArray(r)||Object.keys(r).some(k=>!allowed.includes(k))||['worldId','revision','sequence','tick'].some(k=>!Object.hasOwn(r,k)))fail('invalid-world','Receipt acknowledgement');if(r.worldId!==w.id||r.sequence!==receipt.sequence)fail('invalid-world','Receipt identity');num(r.revision,1,w.revision);num(r.tick,0,w.tick);if(Object.hasOwn(r,'residentId'))str(r.residentId);if(Object.hasOwn(r,'proposalId'))str(r.proposalId);if(Object.hasOwn(r,'appliedTicks'))num(r.appliedTicks,1,4);}if(bytes(w)>LIMITS.bytes)fail('capacity','World byte cap');return true;}
function finish(w,result){validate(w);if(bytes(result)>4096)fail('capacity','Acknowledgement cap');return {world:w,result};}
function command(world,input,context={}){plain(input);plain(context);closed(input,['worldId','expectedRevision','sequence','command']);str(input.worldId);num(input.expectedRevision);num(input.sequence,1);const c=input.command;if(!c||typeof c.kind!=='string')fail('invalid-input','Command required');const schemas={import:['kind','artifactId'],retire:['kind','residentId'],participate:['kind','residentId','enabled'],invite:['kind','residentId','partnerId'],cancelProposal:['kind','proposalId'],advance:['kind','ticks']};if(!Object.hasOwn(schemas,c.kind))fail('invalid-input','Unknown command');closed(c,schemas[c.kind]);const payloadHash=C.sha256(Q.canon(input));validate(world);if(input.worldId!==world.id)fail('not-found','Wrong world');const saved=world.receipts.find(r=>r.sequence===input.sequence);if(saved){if(saved.payloadHash!==payloadHash)fail('conflict','Sequence payload conflict');return {world:clone(world),result:clone(saved.result)};}if(input.sequence!==world.nextSequence)fail('sequence',input.sequence<world.nextSequence?'Stale sequence':'Sequence gap');if(input.expectedRevision!==world.revision)fail('stale','Revision mismatch');const w=clone(world),result={worldId:w.id,revision:inc(w.revision),sequence:input.sequence,tick:w.tick};w.nextSequence=inc(w.nextSequence);w.revision=result.revision;
if(c.kind==='import'){str(c.artifactId);const a=artifact(context);if(a.id!==c.artifactId)fail('invalid-input','Artifact identity mismatch');result.residentId=insert(w,a,false);}else {closed(context,[]);if(c.kind==='advance'){num(c.ticks,1,4);for(let i=0;i<c.ticks;i++)step(w);result.appliedTicks=c.ticks;}else if(c.kind==='cancelProposal'){str(c.proposalId);const p=w.proposals.find(p=>p.id===c.proposalId);if(!p)fail('not-found','Proposal missing');removeProposal(w,p,'proposal-cancelled');result.proposalId=p.id;}else {const r=resident(w,c.residentId);result.residentId=r.id;if(c.kind==='retire'){r.epoch=inc(r.epoch);cleanup(w,r);w.residents=w.residents.filter(a=>a.id!==r.id);event(w,'retired',[r.id]);}else if(c.kind==='participate'){if(typeof c.enabled!=='boolean')fail('invalid-input','enabled must be Boolean');if(r.enabled!==c.enabled){r.epoch=inc(r.epoch);if(!c.enabled)cleanup(w,r);r.enabled=c.enabled;}}else {const b=resident(w,c.partnerId);if(r.id===b.id||!eligible(w,r)||!eligible(w,b))fail('ineligible','Invitation requires eligible distinct adults');r.invitation={partnerId:b.id,expiry:deadline(w,120)};}}}result.tick=w.tick;w.receipts.push({sequence:input.sequence,payloadHash,result:clone(result)});if(w.receipts.length>256)w.receipts.shift();return finish(w,result);}
function admit(world,input,context){plain(input);plain(context);closed(input,['origin','target','childArtifactId']);closed(input.target,['kind','worldId','expectedRevision']);closed(input.origin,['kind','worldId','proposalId','parentResidents','epochs']);validate(world);const o=input.origin,t=input.target;str(o.worldId);str(o.proposalId);str(t.worldId);if(!Array.isArray(o.parentResidents)||o.parentResidents.length!==2||!Array.isArray(o.epochs)||o.epochs.length!==2)fail('invalid-input','Two ordered origin parents required');o.parentResidents.forEach(id=>str(id));o.epochs.forEach(n=>num(n));if(o.kind!=='pairing'||t.kind!=='world'||o.worldId!==world.id||t.worldId!==world.id)fail('invalid-input','World pairing origin required');num(t.expectedRevision);if(t.expectedRevision!==world.revision)fail('stale','Revision mismatch');const a=artifact(context);str(input.childArtifactId);if(a.id!==input.childArtifactId)fail('invalid-input','Child identity mismatch');const w=clone(world),p=w.proposals.find(p=>p.id===o.proposalId);if(!p||w.tick>=p.expiry)fail('stale','Proposal missing/expired');if(Q.canon(o.parentResidents)!==Q.canon(p.parentResidents)||Q.canon(o.epochs)!==Q.canon(p.epochs))fail('stale','Ordered origin pins mismatch');const rs=p.parentResidents.map(id=>resident(w,id));if(rs.some((r,i)=>!r.enabled||r.nurseryUntil||r.rest||r.energy<50||r.pendingProposal!==p.id||r.epoch!==p.epochs[i]||r.artifactId!==p.artifactIds[i]||r.sourceHash!==p.sourceHashes[i]||r.intentHash!==p.intentHashes[i]))fail('ineligible','Birth parent pins/energy/participation');deadline(w,200);w.revision=inc(w.revision);const id=insert(w,a,true);removeProposal(w,p,'proposal-consumed');for(const r of rs){r.energy-=30;cooldown(w,r,200);}return finish(w,{worldId:w.id,revision:w.revision,tick:w.tick,residentId:id,parentResidents:p.parentResidents.slice()});}
function annotate(world,artifactId,newIntentHash){plain(world);str(artifactId);if(newIntentHash!==null)hash(newIntentHash);validate(world);const w=clone(world),rs=w.residents.filter(r=>r.artifactId===artifactId&&r.intentHash!==newIntentHash);if(rs.length){w.revision=inc(w.revision);for(const r of rs){r.epoch=inc(r.epoch);cleanup(w,r);r.intentHash=newIntentHash;}event(w,'annotated',rs.map(r=>r.id));}return finish(w,{worldId:w.id,revision:w.revision,tick:w.tick});}
const api={LIMITS,SPAWNS,CLEARINGS,create,command,admit,annotate,validate};if(typeof module!=='undefined')module.exports=api;root.QuinelingWorld=api;
})(typeof globalThis!=='undefined'?globalThis:this);