Repository navigation
Expand file tree
/
Copy pathlifeform-families.js
More file actions
68 lines (68 loc) · 19.6 KB
/
Copy pathlifeform-families.js
File metadata and controls
68 lines (68 loc) · 19.6 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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
(function(root){
'use strict';
const TAU=2*Math.PI, PHI_INV=(Math.sqrt(5)-1)/2, tags=['logarithmic-mantle','toroidal-weave','phyllotaxis-fan'];
const LIMITS=Object.freeze({growth:[.12,.35],spread:[.5,1.2],curl:[.4,1.6],asymmetry:[0,.4],offset:[0,TAU],frequency:[2,8],lobes:[3,9],twist:[1,3],whorls:[2,4]});
const INT_KEYS=new Set(['frequency','lobes','whorls']);
function clampGeometry(g){const out={};for(const [k,[lo,hi]]of Object.entries(LIMITS)){const v=Number.isFinite(g?.[k])?Math.max(lo,Math.min(hi,g[k])):lo;out[k]=INT_KEYS.has(k)?Math.round(v):v;}if(g?.material&&g.material.model==='projected-compression')out.material=g.material;return out;}
function rng(seed){let s=seed>>>0;s=Math.imul(s^(s>>>16),0x7feb352d);s=Math.imul(s^(s>>>15),0x846ca68b);s=(s^(s>>>16))>>>0;return()=>((s=(Math.imul(s,1664525)+1013904223)>>>0)/4294967296);}
function authorParams(mechanism,t,seed){const r=rng((seed^(t?.hash||0))>>>0),depth=Number.isFinite(t?.depth)?t.depth:1,fanout=Number.isFinite(t?.fanout)?t.fanout:1,conv=Number.isFinite(t?.convergence)?t.convergence:0,eff=Number.isFinite(t?.effects)?t.effects:0,lit=Number.isFinite(t?.litNorm)?t.litNorm:r(),op=Number.isFinite(t?.opNorm)?t.opNorm:r();return clampGeometry({growth:.14+.16*(.55*r()+.45*lit),spread:.58+.52*(.5*r()+.3*op+.2*Math.min(1,fanout/4)),curl:.52+.98*(.5*r()+.3*lit+.2*Math.min(1,depth/6)),asymmetry:.06+.30*(.55*r()+.45*op),offset:TAU*((r()+.37*lit+.23*op)%1),frequency:2+Math.min(6,Math.round(depth*.7+lit*2.2)),lobes:3+Math.min(6,conv+eff+Math.max(0,fanout-1)+Math.round(op*1.5)),twist:1+Math.min(2,Math.max(0,depth)*.32+.65*lit+.35*r()),whorls:2+Math.min(2,Math.floor((depth+Math.max(0,fanout-1)+Math.round(op*2))/3)),...(mechanism==='toroidal-weave'?{}:{material:{model:'projected-compression',rows:48,columns:15,focus:Number((1.65+1.15*(.5*lit+.5*r())).toFixed(3)),quiet:Number((.07+.07*(.5*op+.5*r())).toFixed(3)),gain:Number((.52+.26*(.5*lit+.5*r())).toFixed(3))}})});}
function validate(record){const g=record.geometry;if(!tags.includes(record.mechanism)||!g||Object.keys(g).sort().join()!==['growth','spread','curl','asymmetry','offset','frequency','lobes','twist','whorls',...(Object.hasOwn(g,'material')?['material']:[])].sort().join())throw Error('LifeformFamilies: invalid geometry');if(g.material){const m=g.material;if(record.mechanism==='toroidal-weave'||Object.keys(m).sort().join()!==['model','rows','columns','focus','quiet','gain'].sort().join()||m.model!=='projected-compression'||m.rows!==48||m.columns!==15||!Number.isFinite(m.focus)||m.focus<1||m.focus>4||!Number.isFinite(m.quiet)||m.quiet<.02||m.quiet>.2||!Number.isFinite(m.gain)||m.gain<.3||m.gain>.9)throw Error('LifeformFamilies: invalid compression material');}for(const [k,v]of Object.entries(g).filter(([k])=>k!=='material'))if(!Number.isFinite(v)||v<LIMITS[k][0]||v>LIMITS[k][1])throw Error('LifeformFamilies: bounded parameters required');if(!Number.isInteger(g.frequency)||!Number.isInteger(g.lobes)||!Number.isInteger(g.whorls))throw Error('LifeformFamilies: integer structure required');return true;}
function compile(record,graph){validate(record);const r=JSON.parse(JSON.stringify(record)),nodes=graph.nodes,territories=r.territories.map(p=>({...p,owner:nodes.findIndex(n=>n.id===p.node)})),territoriesByComponent=Array.from({length:r.strands},()=>[]),territoryByOwner=new Array(nodes.length),territoryByNode=new Map();for(const p of territories){territoriesByComponent[p.component].push(p);if(territoryByOwner[p.owner]===undefined)territoryByOwner[p.owner]=p;if(!territoryByNode.has(p.node))territoryByNode.set(p.node,p);}return {record:r,nodes,territories,territoriesByComponent,territoryByOwner,territoryByNode,familyAdapter:true,chartCount:r.mechanism==='phyllotaxis-fan'?r.strands*r.geometry.whorls:r.strands,_basisCache:null};}
// Compiled-only caches preserve the authored record and exact arithmetic order.
function owner(body,k,u){const comp=body.territoriesByComponent[k%body.record.strands];if(comp.length===1)return comp[0].owner;for(let i=0;i<comp.length;i++){const p=comp[i];if(u>=p.u[0]&&(u<p.u[1]||u===1&&p.u[1]===1))return p.owner;}return comp[comp.length-1].owner;}
function computeBasis(body,k,u,phase){const r=body.record,g=r.geometry,p=phase%TAU,n=body.chartCount||r.record?.strands||r.strands,f=k/n,env=Math.sin(Math.PI*u),offset=g.offset+TAU*f,holonomy=Math.PI*u+TAU*PHI_INV*u*(1-u)*Math.sin(p+offset),sHol=Math.sin(holonomy),cHol=Math.cos(holonomy);
if(r.mechanism==='logarithmic-mantle'){
const theta=TAU*(.12+g.curl*u)+offset*.45+.18*Math.sin(p-5*u+offset),radius=.1*Math.exp(g.growth*theta)*(1+.12*Math.sin(offset+g.lobes*u)),collar=Math.exp(-(((u-.32-.1*Math.sin(offset))/.1)**2))+Math.exp(-(((u-.7-.06*Math.cos(offset))/.12)**2));
return {env,radius,collar,width:.19*env**.8*g.spread*(.55+1.3*collar),rollTail:.6*Math.sin(g.frequency*u-p+offset)+.22*sHol,cos:Math.cos(theta),sin:Math.sin(theta),dx:.1*Math.sin(2*u+p)*env,dy:.09*Math.cos(3*u-p+offset)*env,dz:.025*Math.sin(theta+p)+.018*env*cHol};
}else if(r.mechanism==='toroidal-weave'){
const theta=TAU*u,eta=.26+.20*(.5+.5*Math.sin(offset+.35*g.asymmetry*theta)),cEta=Math.cos(eta),sEta=Math.sin(eta),xi1=theta+.32*g.asymmetry*Math.sin(theta+p),xi2=offset+(g.twist+.5*g.curl)*theta+.35*Math.sin(p-2*theta)+.25*holonomy,x4=sEta*Math.sin(xi2),denom=1.35-.55*x4,hopfScale=(.3+.12*g.spread+.07*Math.sin(g.lobes*theta+offset+p))/denom;
return {env,major:hopfScale*cEta,minor:(.04+.05*g.curl+.025*Math.sin(g.frequency*theta-p+offset))*(.8+.3*Math.sin(offset)**2)*(1+.18*sEta/denom),angleHead:xi2,angleTail:.28*sHol,cos:Math.cos(xi1),sin:Math.sin(xi1),dx:.045*Math.sin(p+theta)+.035*hopfScale*sEta*Math.cos(xi2),yScale:.5+.4*g.curl+.12*Math.cos(offset),dy:.1*Math.sin(2*theta-p+offset)+.035*hopfScale*sEta*Math.sin(xi2),dz:.042*hopfScale*sEta*Math.cos(xi2-xi1)+.016*env*cHol};
}else{
const angle=k*Math.PI*(3-Math.sqrt(5))+g.offset+.19*Math.sin(p+offset-3*u)+(1-f)*1.5*u;
return {env,length:(.12+.58*((k+1)/n)**.8)*g.spread,width:(.025+.13*(1-f))*Math.sin(Math.PI*u)**.8,rollTail:.65*Math.sin(g.frequency*u-p+offset)+.24*sHol,bend:.13*Math.sin(Math.PI*u)*Math.sin(offset+p-2*u),cos:Math.cos(angle),sin:Math.sin(angle),dz:.045*env*Math.sin(offset-p)+.020*env*cHol};
}}
function basis(body,k,u,phase){let cache=body._basisCache;if(!cache){const n=body.chartCount||body.record.strands;cache=body._basisCache={phase,charts:Array.from({length:n},()=>new Map())};}else if(!Object.is(cache.phase,phase)){cache.phase=phase;for(let i=0;i<cache.charts.length;i++)cache.charts[i]?.clear();}let chart=cache.charts[k];if(!chart)cache.charts[k]=chart=new Map();const hit=chart.get(u);if(hit!==undefined)return hit;const b=computeBasis(body,k,u,phase);chart.set(u,b);return b;}
// Each component is a genuine two-parameter ribbon chart. Reuse its longitudinal
// basis across transverse samples without changing any point formula.
function point(body,k,u,v,phase,skipOwner=false){const r=body.record,g=r.geometry,b=basis(body,k,u,phase),env=b.env,edge=Math.max(0,Math.sin(Math.PI*(v+1)/2));let x,y,z;
if(r.mechanism==='logarithmic-mantle'){
const roll=Math.PI*(v+1)*(1+.7*b.collar)+b.rollTail,rr=b.radius+b.width*Math.cos(roll);
x=rr*b.cos+b.dx;y=rr*b.sin*1.15+b.dy;z=b.width*Math.sin(roll)+b.dz;
}else if(r.mechanism==='toroidal-weave'){
const angle=b.angleHead+.7*v+b.angleTail,rr=b.major+b.minor*Math.cos(angle);
x=rr*b.cos+b.dx;y=rr*b.sin*b.yScale+b.dy;z=b.minor*Math.sin(angle)+b.dz;
}else{
const roll=Math.PI*g.curl*v+b.rollTail,radius=.01+b.length*u+b.width*.5*Math.sin(roll),side=b.width*(.55*v+.6*Math.cos(roll));
x=radius*b.cos+(side+b.bend)*b.sin;y=radius*b.sin-(side+b.bend)*b.cos;z=b.width*Math.cos(roll)+b.dz;
}
const alpha=(.012+.075*env)*env**.5*(.3+.7*edge)*(1-.55*Math.abs(v));return{x,y,z,alpha,owner:skipOwner?-1:owner(body,k,u)};
}
// Precomputed transverse/longitudinal tables and O(N) Quickselect median for compressionGrid and frame.
const _v24=new Float64Array(24),_edgeFactor24=new Float64Array(24);
for(let c=0;c<24;c++){const v=2*(c+.5)/24-1,_e=Math.max(0,Math.sin(Math.PI*(v+1)/2));_v24[c]=v;_edgeFactor24[c]=(.3+.7*_e)*(1-.55*Math.abs(v));}
const _v15=new Float64Array(15),_edgeFactor15=new Float64Array(15);
for(let h=0;h<15;h++){const v=-1+2*h/14,_e=Math.max(0,Math.sin(Math.PI*(v+1)/2));_v15[h]=v;_edgeFactor15[h]=(.3+.7*_e)*(1-.55*Math.abs(v));}
const _u161=new Float64Array(161),_taper161=new Float64Array(161),_rowBases161=new Array(161),_rowOwners161=new Int32Array(161);
for(let j=0;j<=160;j++){const u=j/160;_u161[j]=u;_taper161[j]=Math.sin(Math.PI*u)**.7;}
function quickSelectMedian(arr,n){let lo=0,hi=n-1;const k=n>>1;while(lo<hi){const pivot=arr[(lo+hi)>>1];let i=lo,j=hi;while(i<=j){while(arr[i]<pivot)i++;while(arr[j]>pivot)j--;if(i<=j){const t=arr[i];arr[i]=arr[j];arr[j]=t;i++;j--;}}if(k<=j)hi=j;else if(k>=i)lo=i;else break;}return arr[k]||1;}
// A bounded numerical material grid samples the actual projected chart area.
// Median-relative concentration suppresses regular broad fields; bilinear
// interpolation lights the existing parameter curves without adding contours.
const _compX=new Float64Array(720),_compY=new Float64Array(720),_compPos=new Float64Array(720);
function compressionGrid(body,phase){const m=body.record.geometry.material;if(!m)return null;if(body.materialGrid?.phase===phase)return body.materialGrid;const n=body.chartCount||body.record.strands,rows=m.rows,cols=m.columns,len=rows*cols,mech=body.record.mechanism,curl=body.record.geometry.curl,grids=body.materialGrid?.grids?.length===n?body.materialGrid.grids:Array.from({length:n},()=>({values:new Float32Array(len),median:1}));
for(let k=0;k<n;k++){const gridEntry=grids[k],values=gridEntry.values;let posCount=0;
for(let i=0;i<rows;i++){const u=(i+.5)/rows,b=computeBasis(body,k,u,phase),rowOff=i*cols;for(let j=0;j<cols;j++){const v=_v15[j],index=rowOff+j;if(mech==='logarithmic-mantle'){const roll=Math.PI*(v+1)*(1+.7*b.collar)+b.rollTail,rr=b.radius+b.width*Math.cos(roll);_compX[index]=rr*b.cos+b.dx;_compY[index]=rr*b.sin*1.15+b.dy;}else if(mech==='toroidal-weave'){const angle=b.angleHead+.7*v+b.angleTail,rr=b.major+b.minor*Math.cos(angle);_compX[index]=rr*b.cos+b.dx;_compY[index]=rr*b.sin*b.yScale+b.dy;}else{const roll=Math.PI*curl*v+b.rollTail,radius=.01+b.length*u+b.width*.5*Math.sin(roll),side=b.width*(.55*v+.6*Math.cos(roll));_compX[index]=radius*b.cos+(side+b.bend)*b.sin;_compY[index]=radius*b.sin-(side+b.bend)*b.cos;}}}
for(let i=0;i<rows;i++)for(let j=0;j<cols;j++){const ia=Math.max(0,i-1),ib=Math.min(rows-1,i+1),ja=Math.max(0,j-1),jb=Math.min(cols-1,j+1),a=ia*cols+j,b=ib*cols+j,c=i*cols+ja,d=i*cols+jb,J=Math.abs(((_compX[b]-_compX[a])*(_compY[d]-_compY[c])-(_compY[b]-_compY[a])*(_compX[d]-_compX[c]))/(((ib-ia)/rows)*(2*(jb-ja)/(cols-1))));values[i*cols+j]=J;if(J>1e-12)_compPos[posCount++]=J;}
const median=posCount?quickSelectMedian(_compPos,posCount):1,invMed=1/median,focus=m.focus;for(let i=0;i<len;i++){const t=1-values[i]*invMed;values[i]=t>0?Math.pow(t,focus):0;}gridEntry.median=median;}
return body.materialGrid={phase,grids};}
function compressionAt(body,grid,k,u,v){if(!grid)return 0;const m=body.record.geometry.material,x=Math.max(0,Math.min(m.rows-1,u*m.rows-.5)),y=Math.max(0,Math.min(m.columns-1,(v+1)*(m.columns-1)/2)),i=Math.floor(x),j=Math.floor(y),a=x-i,b=y-j,g=grid.grids[k].values,cols=m.columns,i0=Math.min(m.rows-1,i)*cols,i1=Math.min(m.rows-1,i+1)*cols,j0=Math.min(cols-1,j),j1=Math.min(cols-1,j+1);return (1-a)*((1-b)*g[i0+j0]+b*g[i0+j1])+a*((1-b)*g[i1+j0]+b*g[i1+j1]);}
function normalizeExecution(body,exec){if(!exec||typeof exec!=='object')return null;const n=body.nodes.length,cl=(v,lo,hi)=>Number.isFinite(v)?Math.max(lo,Math.min(hi,v)):lo;let activeOwner=Number.isInteger(exec.activeOwner)&&exec.activeOwner>=0&&exec.activeOwner<n?exec.activeOwner:-1;if(activeOwner<0&&typeof exec.activeNodeId==='string')activeOwner=body.nodes.findIndex(nd=>nd.id===exec.activeNodeId);const weights=new Float32Array(n);if(Array.isArray(exec.nodeWeights)||ArrayBuffer.isView(exec.nodeWeights)){for(let i=0;i<n;i++)weights[i]=cl(exec.nodeWeights[i],0,1);}if(activeOwner>=0&&weights[activeOwner]<1)weights[activeOwner]=1;const stackRatio=cl(exec.stackRatio??(Number.isFinite(exec.recursionDepth)?exec.recursionDepth/8:0),0,1),fuelRatio=cl(exec.fuelRatio??1,0,1),progress=cl(exec.progress??0,0,1),stepFraction=cl(exec.stepFraction??0,0,1),outcome=exec.outcome==='Done'||exec.outcome==='Rejected'||exec.outcome==='Exhausted'?exec.outcome:'Running',fault=outcome==='Rejected'||outcome==='Exhausted'?1:0,done=outcome==='Done'?1:0;return {activeOwner,weights,stackRatio,fuelRatio,progress,stepFraction,outcome,fault,done};}
function frame(body,phase,{budget=6000,crests=true,reuse,execution}={}){if(!Number.isFinite(phase)||!Number.isFinite(budget)||budget<128||budget>100000)throw Error('LifeformFamilies: frame budget');budget=Math.floor(budget);const ex=normalizeExecution(body,execution),p=phase%TAU,effPhase=ex?phase+.25*ex.progress*TAU+.06*(1-ex.fuelRatio)*Math.sin(2*p)+.04*ex.done*Math.sin(p):phase,points=reuse?.points?.length===budget*4?reuse.points:new Float32Array(budget*4),owners=reuse?.owners?.length===budget?reuse.owners:new Uint16Array(budget),n=body.chartCount||body.record.strands,grid=compressionGrid(body,effPhase),rows=Math.ceil(budget/n/24),mech=body.record.mechanism,g=body.record.geometry,curl=g.curl,offset=g.offset;if(reuse){reuse.points=points;reuse.owners=owners;}
for(let k=0;k<n;k++){let lastU=-1,b=null,own=0,envBase=0;for(let local=0;;local++){const j=local*n+k;if(j>=budget)break;const u=Math.min(.999,(Math.floor(local/24)+.5)/rows);if(u!==lastU){lastU=u;b=computeBasis(body,k,u,effPhase);own=owner(body,k,u);envBase=(.012+.075*b.env)*Math.sqrt(Math.max(0,b.env));}const c=local%24,v=_v24[c],p4=j*4;let x,y,z;if(mech==='logarithmic-mantle'){const roll=Math.PI*(v+1)*(1+.7*b.collar)+b.rollTail,rr=b.radius+b.width*Math.cos(roll);x=rr*b.cos+b.dx;y=rr*b.sin*1.15+b.dy;z=b.width*Math.sin(roll)+b.dz;}else if(mech==='toroidal-weave'){const angle=b.angleHead+.7*v+b.angleTail,rr=b.major+b.minor*Math.cos(angle);x=rr*b.cos+b.dx;y=rr*b.sin*b.yScale+b.dy;z=b.minor*Math.sin(angle)+b.dz;}else{const roll=Math.PI*curl*v+b.rollTail,radius=.01+b.length*u+b.width*.5*Math.sin(roll),side=b.width*(.55*v+.6*Math.cos(roll));x=radius*b.cos+(side+b.bend)*b.sin;y=radius*b.sin-(side+b.bend)*b.cos;z=b.width*Math.cos(roll)+b.dz;}let alpha=envBase*_edgeFactor24[c];if(grid)alpha*=.3+2.4*compressionAt(body,grid,k,u,v);if(ex){const w=ex.weights[own]||0,sScale=1+.06*ex.stackRatio*(.5+.5*Math.sin(TAU*u+effPhase)),tremor=ex.outcome==='Rejected'?Math.sin(2*effPhase+own*PHI_INV*TAU-TAU*u)*.024:ex.outcome==='Exhausted'?Math.cos(effPhase+own*PHI_INV*TAU-TAU*u)*.016:0;x*=sScale+tremor*.35*b.env;y*=sScale-tremor*.25*b.env;z+=(.028*w+tremor)*b.env;alpha=Math.min(1,alpha*(1+.65*w+.18*ex.done));}points[p4]=x;points[p4+1]=y;points[p4+2]=z;points[p4+3]=alpha;owners[j]=own;}}
// Ensure even the smallest source-declared operation patch has real tissue.
for(let j=0;j<body.nodes.length;j++){const patch=body.territoryByOwner[j]||body.territories[j%body.territories.length],uMid=(patch.u[0]+patch.u[1])/2,q=point(body,patch.component,uMid,0,effPhase),p4=j*4;let x=q.x,y=q.y,z=q.z,alpha=q.alpha;if(ex){const w=ex.weights[j]||0,envM=Math.sin(Math.PI*uMid),tremor=ex.outcome==='Rejected'?Math.sin(2*effPhase+j*PHI_INV*TAU-TAU*uMid)*.024:ex.outcome==='Exhausted'?Math.cos(effPhase+j*PHI_INV*TAU-TAU*uMid)*.016:0;z+=(.028*w+tremor)*envM;alpha=Math.min(1,alpha*(1+.65*w+.18*ex.done));}points[p4]=x;points[p4+1]=y;points[p4+2]=z;points[p4+3]=alpha;owners[j]=j;}
const ridges=[];if(crests){const material=g.material,quiet=material?.quiet||0,gain=material?.gain||0,usePool=Boolean(reuse),pool=usePool?(body._ridgePool||(body._ridgePool=[])):null;let rIdx=0;for(let k=0;k<n;k++){const fanScale=mech==='phyllotaxis-fan'?1.7-1.1*k/n:1;for(let j=0;j<=160;j++){const u=_u161[j];_rowBases161[j]=computeBasis(body,k,u,effPhase);_rowOwners161[j]=owner(body,k,u);}for(let h=0;h<15;h++){const v=_v15[h],hWeight=h%7===0?1:.65;let ridgeObj=pool?pool[rIdx]:null;if(!ridgeObj){ridgeObj={line:new Array(161),material:'parameter-filament'};if(pool)pool[rIdx]=ridgeObj;}const line=ridgeObj.line;for(let j=0;j<=160;j++){const u=_u161[j],b=_rowBases161[j],own=_rowOwners161[j],taper=_taper161[j],along=mech==='phyllotaxis-fan'?fanScale:mech==='logarithmic-mantle'?1.4-.5*u:1;let x,y,z;if(mech==='logarithmic-mantle'){const roll=Math.PI*(v+1)*(1+.7*b.collar)+b.rollTail,rr=b.radius+b.width*Math.cos(roll);x=rr*b.cos+b.dx;y=rr*b.sin*1.15+b.dy;z=b.width*Math.sin(roll)+b.dz;}else if(mech==='toroidal-weave'){const angle=b.angleHead+.7*v+b.angleTail,rr=b.major+b.minor*Math.cos(angle);x=rr*b.cos+b.dx;y=rr*b.sin*b.yScale+b.dy;z=b.minor*Math.sin(angle)+b.dz;}else{const roll=Math.PI*curl*v+b.rollTail,radius=.01+b.length*u+b.width*.5*Math.sin(roll),side=b.width*(.55*v+.6*Math.cos(roll));x=radius*b.cos+(side+b.bend)*b.sin;y=radius*b.sin-(side+b.bend)*b.cos;z=b.width*Math.cos(roll)+b.dz;}const exBoost=ex?1+.55*(ex.weights[own]||0)+.15*ex.done:1;if(ex){const tremor=ex.outcome==='Rejected'?Math.sin(2*effPhase+own*PHI_INV*TAU-TAU*u)*.024:ex.outcome==='Exhausted'?Math.cos(effPhase+own*PHI_INV*TAU-TAU*u)*.016:0;z+=(.022*(ex.weights[own]||0)+tremor*.8)*taper;}let pt=usePool?line[j]:null;if(grid){const focus=compressionAt(body,grid,k,u,v),alpha=Math.min(1,(quiet+gain*focus)*taper*along*exBoost);if(pt){pt.x=x;pt.y=y;pt.z=z;pt.alpha=alpha;pt.owner=own;pt.compression=focus;}else line[j]={x,y,z,alpha,owner:own,compression:focus};}else{const alpha=Math.min(1,hWeight*taper*(.5+.5*Math.sin(3*u+offset+k)**2)*along*exBoost);if(pt){pt.x=x;pt.y=y;pt.z=z;pt.alpha=alpha;pt.owner=own;}else line[j]={x,y,z,alpha,owner:own};}}ridges.push(ridgeObj);rIdx++;}}}return{points,owners,ridges};}
function anchor(body,nodeId,phase,execution){const p=body.territoryByNode?body.territoryByNode.get(nodeId):body.territories.find(p=>p.node===nodeId);if(!p)throw Error('LifeformFamilies: unknown operation');const ex=normalizeExecution(body,execution),ph=phase%TAU,effPhase=ex?phase+.25*ex.progress*TAU+.06*(1-ex.fuelRatio)*Math.sin(2*ph)+.04*ex.done*Math.sin(ph):phase,uMid=(p.u[0]+p.u[1])/2,q=point(body,p.component,uMid,0,effPhase);if(ex){const w=ex.weights[p.owner]||0,envM=Math.sin(Math.PI*uMid),tremor=ex.outcome==='Rejected'?Math.sin(2*effPhase+p.owner*PHI_INV*TAU-TAU*uMid)*.024:ex.outcome==='Exhausted'?Math.cos(effPhase+p.owner*PHI_INV*TAU-TAU*uMid)*.016:0;q.z+=(.028*w+tremor)*envM;q.alpha=Math.min(1,q.alpha*(1+.65*w+.18*ex.done));}return q;}
function portraitFrame(body){if(body.bounds)return body.bounds;const lo=[Infinity,Infinity,Infinity],hi=[-Infinity,-Infinity,-Infinity],mech=body.record.mechanism,curl=body.record.geometry.curl,n=body.chartCount||body.record.strands,vs=[-1,-.5,0,.5,1];for(let h=0;h<32;h++){const phase=TAU*h/32;for(let k=0;k<n;k++)for(let j=0;j<=80;j++){const u=j/80,b=computeBasis(body,k,u,phase);for(let vi=0;vi<5;vi++){const v=vs[vi];let x,y,z;if(mech==='logarithmic-mantle'){const roll=Math.PI*(v+1)*(1+.7*b.collar)+b.rollTail,rr=b.radius+b.width*Math.cos(roll);x=rr*b.cos+b.dx;y=rr*b.sin*1.15+b.dy;z=b.width*Math.sin(roll)+b.dz;}else if(mech==='toroidal-weave'){const angle=b.angleHead+.7*v+b.angleTail,rr=b.major+b.minor*Math.cos(angle);x=rr*b.cos+b.dx;y=rr*b.sin*b.yScale+b.dy;z=b.minor*Math.sin(angle)+b.dz;}else{const roll=Math.PI*curl*v+b.rollTail,radius=.01+b.length*u+b.width*.5*Math.sin(roll),side=b.width*(.55*v+.6*Math.cos(roll));x=radius*b.cos+(side+b.bend)*b.sin;y=radius*b.sin-(side+b.bend)*b.cos;z=b.width*Math.cos(roll)+b.dz;}if(x<lo[0])lo[0]=x;if(x>hi[0])hi[0]=x;if(y<lo[1])lo[1]=y;if(y>hi[1])hi[1]=y;if(z<lo[2])lo[2]=z;if(z>hi[2])hi[2]=z;}}}const pad=.15;return body.bounds={cx:(lo[0]+hi[0])/2,cy:(lo[1]+hi[1])/2,cz:(lo[2]+hi[2])/2,width:hi[0]-lo[0]+2*pad,height:hi[1]-lo[1]+2*pad,depth:hi[2]-lo[2]+2*pad};}
const api={LIMITS,clampGeometry,authorParams,validate,compile,frame,anchor,portraitFrame,normalizeExecution};if(typeof module!=='undefined')module.exports=api;root.LifeformFamilies=api;
})(typeof globalThis!=='undefined'?globalThis:this);