memoscan
← all memos

Memo 0x48f7d435…198122 on Ethereum

an(). -- const dna = SPEC.armPlan(st.seed, pseed); const lean = dna.lean, crook = dna.crook||0; // HEIGHT is driven by maturity only (monotonic — never shrinks); the accordion (thirst) contracts // only the GIRTH/ribs laterally, the real saguaro pleating. Mixing accordion into H made the whole // column pulse taller/shorter with vitality → "growth flicker". const H=(70+560*m)*S*dna.tall, colW=(14+21*m)*accordion*S*dna.stout; const steps=Math.max(2,Math.ceil(H/GRID)); let pos=[cx,by], dir=UP; const nRibT=clamp(Math.round(colW/2.5),7,17); let apex=[cx,by-H]; for(let s=1;s<=steps;s++){ const u=s/steps; dir += angTo(dir, UP + lean + crook*smooth(0.25,1,u))*0.05; // upper column curves → varied, non-ramrod silhouettes pos=[pos[0]+Math.cos(dir)*GRID, pos[1]+Math.sin(dir)*GRID]; const dome = u<0.86?1 : Math.sqrt(Math.max(0,1-Math.pow((u-0.86)/0.14,2))); const flare = u<0.06? 1+(0.06-u)*3 : 1; ribRow(pos[0],pos[1],Math.cos(dir),Math.sin(dir),colW*(1-0.10*u)*dome*flare,nRibT,50,rng('col',st.seed,pseed,s), s%4===0); if(u>0.9) apex=[pos[0],pos[1]]; } // -- ARMS: emerge progressively (each has its own emergeAt) from the seed's arm plan; out then UP at // the elbow. Asymmetric heights/sides/lengths + rare secondary sub-arms → varied candelabra. -- const armTips=[]; const nRibFor=aw=>clamp(Math.round(aw/2.0),6,12); function growArm(p0, d0, A, grow, rsKey, depth){ const armLen=(135+205*m)*S*A.lenF*clamp(grow,0.22,1)*(depth?0.6:1); const aw=colW*(0.50+0.14*A.curve)*(depth?0.7:1); let p=p0.slice(), d=d0, sub=null; const asteps=Math.max(3,Math.ceil(armLen/GRID)), nRibA=nRibFor(aw); for(let k=1;k<=asteps;k++){ const u=k/asteps; d += angTo(d, UP + sen*A.side*0.45)*(u<A.elbow?0.05:0.18*A.curve); p=[p[0]+Math.cos(d)*GRID, p[1]+Math.sin(d)*GRID]; const tipR=u>0.84?Math.sqrt(Math.max(0,1-Math.pow((u-0.84)/0.16,2))):1; ribRow(p[0],p[1], 0,1, aw*(1-0.10*u)*tipR, nRibA, 49, rng(rsKey,st.seed,pseed,depth,k), k%4===0); if(depth===0 && A.sub && !sub && u>=A.subU) sub=[p.slice(), d]; // mark a secondary-branch point } armTips.push(p); if(sub && grow>0.72){ const subA={side:A.side, lenF:A.lenF, elbow:0.32, curve:A.curve, spread:A.spread, sub:false, subU:1}; growArm(sub[0], sub[1]+A.side*0.85, subA, 0.85, rsKey+'s', 1); } } for(const A of dna.arms){ if(m < A.emergeAt) continue; const grow=clamp((m-A.emergeAt)/0.18, 0.22, 1); // arms keep extending after emerging const start=[cx + A.side*colW*0.55, by - H*A.atU], d0=UP + A.side*A.spread; growArm(start, d0, A, grow, 'ar'+(A.side>0?'R':'L'), 0); } // -- apex CROWN: white funnel flowers + green club buds → ruby fruit, at the trunk top & arm tips -- // a green, club-shaped, scaly bud (the pre-bloom stage in the reference) function drawBud(fx,fy,R,rs){ for(let gy=-R*1.45;gy<=R*0.45;gy+=GRID){ const u=(gy/(R*1.45)+1)/1.45; const w=R*0.58*Math.sin(Math.PI*clamp(u*0.72+0.16,0,1)); for(let gx=-w;gx<=w;gx+=GRID){ const edge=Math.abs(gx)>w-GRID; const scale=edge && (Math.round(gy/(GRID*2))%2===0); // suggest overlapping scales C.push(fx+gx,fy+gy,86,'.',shade(FC(scale||gy<-R*1.0?P.budTip:P.bud),rs),'flowers'); } } } // a white funnel flower, read cleanly at plant scale ("fried-egg"): a darker shadow halo rims the // bloom so the white separates from pale backdrops → white petal disc (thin sage rim) → gold centre. function drawFlower(fx,fy,R,openF,rs){ const RR=R*(0.98+0.18*openF), halo=RR*1.16; for(let gx=-halo;gx<=halo;gx+=GRID)for(let gy=-halo;gy<=halo*1.04;gy+=GRID){ if(gx*gx+gy*gy>halo*halo)continue; C.push(fx+gx,fy+gy,83,'.',shade(FC(P.petalShade),rs),'flowers'); } // soft drop-shadow ring const pr=RR; // white petal disc — white dominates for(let gx=-pr;gx<=pr;gx+=GRID)for(let gy=-pr;gy<=pr;gy+=GRID){ const d=Math.hypot(gx,gy); if(d>pr)continue; C.push(fx+gx,fy+gy,86,'.',shade(FC(d>pr-GRID*0.7?P.petalEdge:P.petal),rs),'flowers'); } // very thin rim const cr=RR*0.34; // smaller golden stamen centre for(let gx=-cr;gx<=cr;gx+=GRID)for(let gy=-cr;gy<=cr;gy+=GRID){ const rr=Math.hypot(gx,gy)/cr; if(rr>1)continue; C.push(fx+gx,fy+gy,90,'.',shade(FC(rr<0.34?P.stamenDk:rr>0.78?P.stamenDk:P.stamen),rs),'flowers'); } } // a ruby fruit: a clean oval (green→red→ruby) with a shadow halo, areole dots + brown floral // remnant; if open, a central red-flesh cup packed with black seeds (the iconic split saguaro fig). function drawFruit(fx,fy,R,ripe,open,rs,fid){ const base = ripe<0.5 ? mix(P.fruitGreen,P.fruit, ripe*2) : mix(P.fruit,P.fruitDk,(ripe-0.5)*2); const halo=R*1.06; // soft separation from neighbours for(let gx=-halo;gx<=halo;gx+=GRID)for(let gy=-halo*1.35;gy<=halo;gy+=GRID){ if((gx*gx)/(halo*halo)+(gy*gy)/(halo*halo*1.6)>1)continue; C.push(fx+gx,fy+gy,85,'.',shade(FC(P.petalShade),rs),'flowers'); } for(let gx=-R;gx<=R;gx+=GRID)for(let gy=-R*1.35;gy<=R*1.35;gy+=GRID){ const ny=gy/(R*1.35), rr=(gx*gx)/(R*R)+ny*ny; if(rr>1)continue; const edge=rr>0.76; C.push(fx+gx,fy+gy,86,'.',shade(FC(edge?mix(base,'#000',0.16):base),rs),'flowers'); } for(let i=0;i<8;i++){ const aa=rng('arl',st.seed,pseed,fid,i); // areole dots on skin (STABLE seed) const ax=(aa()*2-1)*R*0.7, ay=(aa()*2-1)*R*1.05; if((ax*ax)/(R*R)+(ay*ay)/(R*R*1.7)>0.7)continue; C.push(fx+ax,fy+ay,87,'.',shade(FC(P.spine),rs),'flowers'); } for(let gx=-R*0.3;gx<=R*0.3;gx+=GRID) // brown dried-flower remnant tuft C.push(fx+gx,fy-R*1.24,88,'.',shade(FC(P.skelDk),rs),'flowers'); if(open){ const ow=R*0.56, oh=R*0.92; // split: central red-flesh cup for(let gx=-ow;gx<=ow;gx+=GRID)for(let gy=-oh;gy<=oh*0.9;gy+=GRID){ if((gx*gx)/(ow*ow)+(gy*gy)/(oh*oh)>1)continue; C.push(fx+gx,fy+gy,89,'.',shade(FC(P.pulp),rs),'flowers'); } for(let i=0;i<16;i++){ const sr=rng('sd',st.seed,pseed,fid,i); // dense black seeds (STABLE seed) const sx=(sr()*2-1)*ow*0.78, sy=(sr()*2-1)*oh*0.82; if((sx*sx)/(ow*ow)+(sy*sy)/(oh*oh)>0.82)continue; C.push(fx+sx,fy+sy,90,'.',shade(FC(P.seed),sr),'flowers'); } } } // a coherent crown: one mode (buds OR flowers OR fruit, by season) so it doesn't muddle, spaced // in a small fan so the blooms read individually. // cid = a STABLE crown id (e.g. 'apex' / 'arm2'); all per-item randomness is seeded on it + // st.seed, NOT on the drifting pixel position, so buds/flowers/fruit don't flicker as the plant grows. function crown(tx,ty,scale,rs,cid){ if(drySkel>0.5 || !crownOn) return; const Rf=(15+7*scale)*S; if(crownMode==='fruit'){ // a row of distinct ruby figs, one or two split open const nf=Math.max(2,Math.round(1.4+1.4*scale)); for(let k=0;k<nf;k++){ const jr=rng('cj',st.seed,pseed,cid,k), off=k-(nf-1)/2; const fx=tx+off*Rf*1.34, fy=ty - Rf*0.35 - Math.abs(off)*Rf*0.30 + (jr()-0.5)*Rf*0.2; const ripe=clamp((isAutumn?0.92:0.66)+(jr()-0.5)*0.28,0,1), open=jr()<0.4; // stable per-fruit (no vitality) drawFruit(fx,fy,Rf*0.56, ripe, open, rs, cid+'_'+k); } return; } const n=Math.max(2,Math.round(1.6+1.8*scale)); // spring blooms in a small fan for(let k=0;k<n;k++){ const a=UP+(k-(n-1)/2)*0.82, rr=Rf*1.42; const fx=tx+Math.cos(a)*rr, fy=ty+Math.sin(a)*rr*0.5 - Rf*0.5; const jr=rng('cj',st.seed,pseed,cid,k); if(jr()<0.18) drawBud(fx,fy,Rf*0.6,rs); else drawFlower(fx,fy,Rf, 0.85, rs); // fixed openness → no size shimmer } } if(crownOn){ crown(apex[0],apex[1],1, rng('cr',st.seed,pseed), 'apex'); armTips.forEach((tp,i)=>{ if(rng('at',st.seed,pseed,i)()<0.7) crown(tp[0],tp[1],0.78,rng('cr',st.seed,pseed,i+1), 'arm'+i); }); } } // ===================== STAND: 1 → several saguaros from the seed ===================== const cr=rng('colony',st.seed), roll=cr(); const nC = roll<0.50?0 : roll<0.80?1 : roll<0.93?2 : 3+Math.floor(cr()*3); // companions: 0..5 const comps=[]; for(let k=0;k<nC;k++){ const r=rng('comp',st.seed,k); const side=k%2?1:-1, rank=Math.floor(k/2)+1, depth=clamp(0.20+0.70*r(),0,1); const S=lerp(0.62,0.30,depth), fade=lerp(0.24,0.62,depth); const cxP=clamp(baseX+side*(150+(rank-1)*135)+(r()*2-1)*36, FRAME+70, W-FRAME-70); const byP=baseY - depth*42; // far ones raised toward the horizon const mm=clamp(m + (r()*2-1)*0.30, 0.16, 1), vv=clamp(v*(0.6+0.5*r()), 0.2, 1), ssn=clamp(sen + (r()*2-1)*0.12, 0, 1); comps.push({cx:cxP, by:byP, S, fade, m:mm, v:vv, sen:ssn, ageF:env.ageF, rewards:clamp(rewards*(0.5+0.7*r()), 0, 1), reproOK:mm>=0.75, browny:clamp(Math.max(1-vv,ssn),0,1), dead:false, pseed:'c'+k, depth}); } comps.sort((a,b)=>b.depth-a.depth).forEach(paintSaguaro); // far (faint) → near paintSaguaro({cx:baseX, by:baseY, S:nC>0?0.92:1.0, fade:0, m, v, sen, ageF:env.ageF, rewards, reproOK, browny, dead, pseed:'hero'}); }, // per-seed silhouette plan — shared by draw() and traits() so the arm count stays in sync. // Returns {stout, tall, lean, arms:[{side, atU, emergeAt, lenF, elbow, spread, curve, sub, subU}]}. armPlan(seed, pseed){ const r=rng('dna',seed,pseed); // wider girth/height/lean ranges + a gentle per-seed column CROOK so even armless columns differ // (most slight, some clearly curved/leaning — the pow(r,2) skews bends LOW). const stout=0.76+0.62*r(), tall=0.74+0.52*r(), lean=(r()*2-1)*0.13; const crook=Math.pow(r(),2)*0.34*(r()<0.5?-1:1); const t=r(), maxA = t<0.08?0 : t<0.22?1 : t<0.46?2 : t<0.70?3 : t<0.88?4 : 5; // ~8% lone columns, rest 1–5 arms const arms=[]; let last=r()<0.5?-1:1; for(let i=0;i<maxA;i++){ const rr=rng('armp',seed,pseed,i); const side=rr()<0.66?-last:last; last=side; // emerge at REACHABLE maturity, STAGGERED by arm index → a progressive candelabra instead of a // pole that only sprouts arms at near-max m (the "always a single straight cactus" cause). arms.push({ side, atU:0.30+0.46*rr(), emergeAt:0.54+i*0.05+0.15*rr(), lenF:0.60+0.85*rr(), elbow:0.16+0.20*rr(), spread:0.85+0.70*rr(), curve:0.65+0.80*rr(), sub:rr()<0.22, subU:0.5+0.3*rr() }); } return { stout, tall, lean, crook, arms }; }, traits(st,env){ const m=env.m, dna=this.armPlan(st.seed,'hero'); const nArm=dna.arms.filter(a=>m>=a.emergeAt).length; const sub=dna.arms.some(a=>a.sub && m>=a.emergeAt+0.05); const cr=rng('colony',st.seed), roll=cr(); const total = 1 + (roll<0.50?0 : roll<0.80?1 : roll<0.93?2 : 3+Math.floor(cr()*3)); return [ ['Arms', nArm + (sub?' (branched)':'')], ['Crown', st.diedAt>0?'—' : (env.m>=0.75 && env.rewards>0.3 ? (env.season==='Summer'?'Fruit':'Flowers') : '—')], ['Stand', total===1?'Lone':total===2?'Pair':total<=3?'Cluster':'Forest'], ['Form', st.diedAt>0?'Rib skeleton':'Ribbed column'], ]; } })); })(); ; /* species/wattle.js */ /* wattle — species index 5. GENERATED from prototypes/golden-wattle.html by extract.js. Do not edit here — edit the prototype and re-run `npm run extract`. The prototype's mount() call is redirected to SeedlingRenderer.registerSpecies via the local shim. */ (function () { const mount = (spec) => SeedlingRenderer.registerSpecies(5, spec); /* Golden Wattle — Acacia. A low-forking broad DOMED little tree (recursive staggered-lateral frame) clothed in feathery BIPINNATE leaves (a rachis of paired pinnae of tiny leaflets). Reward: green buds → fuzzy GOLDEN POM-POM flower heads (late winter/spring, S-hemisphere) → flattened legume SEED PODS (green→brown) at senescence. Coherent wind. Grows in stands/groves. Ported to the shared core. */ const SP = window.SEEDLING_SPECIES.find(x=>x.key==='wattle'); mount(Object.assign(window.SEEDLING_toSpec(SP), { title:'Golden Wattle', sub:'acacia v2', senFrom:0.82, LW:{season:1.0, stem:1.2, leaves:1.0, flowers:1.4}, LS:{season:1.0, stem:1.0, leaves:0.85, flowers:0.8}, note:'Broad domed acacia · feathery bipinnate foliage · green buds → fuzzy golden pom-pom flowers (late winter/spring) → flat seed pods · wind · stands/groves.', PAL:{ soil:'#6f5128', dry:'#7a5a30', grey:'#9a8f74', bark:'#7A6A48', barkDk:'#564934', leaf:'#5E7E3C', leafDk:'#3C5828', leafLt:'#86A65A', flower:'#F4C020', flowerLt:'#FBDD5A', flowerDk:'#D9A50E', bud:'#9DAE5A', pod:'#8A9A55', podDry:'#A2854A', podBrown:'#7a5a30' }, stageName(st,env){ if(st.diedAt>0)return'Sealed'; const m=env.m; if(env.ageF>0.84)return'Podding'; if(m<0.12)return'Sprig'; if(m<0.36)return'Young'; if(m<0.55)return'Branching'; const wintery=(env.season==='Winter'||env.season==='Spring'); if(m<0.66)return'Leafy'; return wintery?'Golden':'Leafy'; }, draw(st, C, env){ const {m,v,sen,season,browny,density,rewards,ageF,baseX,baseY}=env, P=this.PAL; const bgC = BACKDROPS[backdropFor(st.seed)]; function wattleDNA(seed){ const dd=rng('dna',seed), g=()=>(dd()+dd()+dd()-1.5)/1.5; return { dominance:0.18+0.34*dd(), divergence:0.45+0.45*dd(), droop:0.30+0.45*dd(), gnarl:0.25+0.5*dd(), taperLen:0.66+0.12*dd(), lateralN:1.6+1.1*dd(), reach:0.78+0.3*dd(), lean:g()*0.12, stature:0.86+0.18*dd(), maxOrder:5+(dd()<0.5?1:0), leafiness:0.7+0.5*dd(), flowerAb:0.55+0.6*dd(), pods:0.4+0.6*dd(), windDir:dd()<0.5?1:-1, windStr:Math.pow(dd(),2.5) }; } function paintWattle(I){ const {seed, artSeed, cx, by, S, fade, m, density, v, sen, browny, rewards} = I; const FC = c => fade>0 ? mix(c, bgC, fade) : c; const D=wattleDNA(seed); const seasonGold = season==='Winter'?1 : season==='Spring'?0.9 : season==='Autumn'?0.5 : 0.45; const foliageF=(1-0.80*sen)*(0.4+0.6*v), bloom=smooth(0.42,0.74,m)*(1-sen)*(0.55+0.45*seasonGold)*rewards; const leafKeep=clamp(1-0.82*sen, 0.12, 1); // phyllodes SHED as it dries/dies → fewer leaf tufts → branch skeleton + dry pods show through const budF=smooth(0.36,0.5,m)*(1-smooth(0.5,0.64,m)), podF=D.pods*smooth(0.66,0.95,Math.max(sen*1.2, ageF*0.9, m-0.2))*(1-0.3*sen); const bark=FC(mix(P.bark,P.grey,Math.max((1-v)*0.3,sen))), barkDk=FC(mix(P.barkDk,P.grey,Math.max((1-v)*0.3,sen))); const leafD=FC(mix(P.leafDk,P.dry,browny*0.9)), leafM=FC(mix(P.leaf,P.dry,browny*0.9)), leafL=FC(mix(P.leafLt,P.dry,browny*0.85)); const greenOf=rs=>shade(mix(leafM, rs()<0.5?leafD:leafL, rs()), rs); const windLean=D.windDir*D.windStr*0.30, rootX=cx - D.windDir*D.windStr*46*S; function bipinnateLeaf(bx, byy, baseAng, size, rs){ if(foliageF<=0.04) return; const segs=Math.max(2,Math.round(size)), droop=(0.5+0.7*rs())*(0.5+0.7*D.droop); let dir=baseAng, pos=[bx,byy]; for(let i=1;i<=segs;i++){ dir += angTo(dir,DOWN)*0.045*droop*(0.3+0.7*(i/segs)); const cd=[Math.cos(dir),Math.sin(dir)]; pos=[pos[0]+cd[0]*GRID, pos[1]+cd[1]*GRID]; C.push(pos[0],pos[1], 34, dirSym(cd[0],cd[1]), greenOf(rs), 'leaves'); if(i===1) continue; const px=-cd[1], py=cd[0]; for(const sd of [1,-1]){ let pa=Math.atan2(py*sd,px*sd); pa += angTo(pa,dir)*0.42; let pp=[pos[0],pos[1]], pdir=pa; const plen=2+Math.floor(rs()*2); for(let k=1;k<=plen;k++){ pdir += angTo(pdir,DOWN)*0.06; pp=[pp[0]+Math.cos(pdir)*GRID, pp[1]+Math.sin(pdir)*GRID]; C.push(pp[0],pp[1], 33, '.', greenOf(rs), 'leaves'); } } } } function leafTuft(x,y,baseAng,rs){ const n=2+Math.floor(rs()*2); for(let k=0;k<n;k++) bipinnateLeaf(x,y, baseAng+(k-(n-1)/2)*0.5+(rs()*2-1)*0.15, (3.5+5*m)*(0.7+0.4*rs())*S, rs); } function pomPom(x,y,green,rs){ const fc = green ? P.bud : (rs()<0.5?P.flower:P.flowerLt), R=GRID*(green?0.85:1.0)*Math.sqrt(S); C.push(x,y,91, green?'O':'+', shade(FC(green?P.bud:mix(fc,P.flowerDk,0.3)),rs), 'flowers'); const n=green?6:7; for(let k=0;k<n;k++){ const a=k/n*TAU + rs()*0.3; C.push(x+Math.cos(a)*R, y+Math.sin(a)*R, 90, '.', shade(FC(fc),rs), 'flowers'); } } function pomCluster(x,y,green,rs){ const n=1+Math.floor(rs()*2); for(let k=0;k<n;k++) pomPom(x+(rs()*2-1)*GRID*2.4, y+(rs()*2-1)*GRID*2.4, green, rs); } function seedPod(x,y,rs){ const cc=FC(mix(P.pod, mix(P.podDry,P.podBrown,sen), Math.max(sen, browny*0.7))); let dir=DOWN+(rs()*2-1)*0.5, pos=[x,y]; const len=4+Math.floor(rs()*3); for(let i=1;i<=len;i++){ dir += angTo(dir,DOWN)*0.18; pos=[pos[0]+Math.cos(dir)*GRID, pos[1]+Math.sin(dir)*GRID]; const px=-Math.sin(dir), py=Math.cos(dir), bump=(i%2?1.1:0.55); C.push(pos[0],pos[1], 86, '|', shade(cc,rs), 'flowers'); C.push(pos[0]+px*bump*GRID, pos[1]+py*bump*GRID, 86, '.', shade(cc,rs), 'flowers'); C.push(pos[0]-px*bump*GRID, pos[1]-py*bump*GRID, 86, '.', shade(mix(cc,'#000000',0.12),rs), 'flowers'); } } function sprout(rs,rx){ if(1-smooth(0.05,0.20,m) <= 0.02) return; const g=smooth(0,0.18,m), hLen=(58+50*g)*S; let dir=UP+D.lean*1.2, pos=[rx,by-3]; const steps=Math.max(3,Math.ceil(hLen/GRID)); for(let i=1;i<=steps;i++){ const u=i/steps; dir+=Math.sin(u*2.6+D.lean*4)*0.045; pos=[pos[0]+Math.cos(dir)*GRID, pos[1]+Math.sin(dir)*GRID]; C.push(pos[0],pos[1],50,dirSym(Math.cos(dir),Math.sin(dir)),shade(bark,rs),'stem'); } for(const sd of [1,-1]){ let cd=UP+sd*(0.8+0.3*D.reach); let cp=[pos[0],pos[1]]; const cl=Math.max(3,Math.round((26+20*g)*S/GRID)); for(let i=1;i<=cl;i++){ cd+=angTo(cd,UP)*0.07; cp=[cp[0]+Math.cos(cd)*GRID, cp[1]+Math.sin(cd)*GRID]; const w=Math.sin(Math.PI*i/cl)*GRID*1.4, px=-Math.sin(cd),py=Math.cos(cd); C.push(cp[0],cp[1],36,dirSym(Math.cos(cd),Math.sin(cd)),greenOf(rs),'leaves'); C.push(cp[0]+px*w,cp[1]+py*w,35,'.',greenOf(rs),'leaves'); C.push(cp[0]-px*w,cp[1]-py*w,35,'.',greenOf(rs),'leaves'); } } if(g>0.4) bipinnateLeaf(pos[0],pos[1], UP+(rs()*2-1)*0.3, (3+3*g)*S, rs); } const growWin=0.12, maxOrder=D.maxOrder; let budget=8000; const chain=(1-Math.pow(D.taperLen,maxOrder+1))/(1-D.taperLen), baseRun=((by-300)/chain)*D.stature*(0.9+0.1*v)*S; function grow(x,y,head,vigor,order,birthM,pid,sideSeq){ if(budget<=0) return; const grown=clamp((m-birthM)/growWin,0,1); if(grown<=0) return; budget--; const rs=rng('wb', seed, pid); let run=baseRun*Math.pow(D.taperLen,order)*(0.82+0.36*rs())*grown; if(order===0) run*=0.86; const steps=Math.max(1,Math.ceil(run/GRID)), woodM=smooth(0.10,0.85,m), baseThick=(maxOrder-order)/maxOrder, bz=54-order; const f1=0.5+1.4*rs(), f2=1.1+2.2*rs(), ph1=rs()*6.28, ph2=rs()*6.28, gnarlA=D.gnarl*0.045*(1+order*0.12), bears=order>=2 ? D.leafiness : (order>=1? 0.25*D.leafiness:0); const canChild = grown>=1 && order<maxOrder; const nLat = order===0 ? 2+Math.floor(rs()*3) : clamp(Math.round(D.lateralN*(1-0.35*D.dominance)+gauss(rs)*0.9),0, order<2?3:2); const lo=order===0?0.24:0.32, hi=0.92, pts=[], sides=[]; let lastSide=rs()<0.5?1:-1; for(let c=0;c<nLat;c++){ pts.push(clamp(lerp(lo,hi,(c+0.5)/Math.max(nLat,1)) + (rs()*2-1)*(0.6/Math.max(nLat,1)), 0.12, 0.95)); if(order===0 || rs()<0.72) lastSide=-lastSide; sides.push(lastSide); } pts.sort((a,b)=>a-b); let dir=head, pos=[x,y], ci=0; for(let i=1;i<=steps;i++){ const u=i/steps; const photo=lerp(0.13,0.04,clamp(order/3,0,1))*Math.max(0,1-order/3.2), grav=0.10*D.droop*smooth(1.5,maxOrder,order)*(0.25+0.75*u); dir += angTo(dir,UP)*photo + angTo(dir,DOWN)*grav + gnarlA*(Math.sin(i*0.5*f1+ph1)+0.5*Math.sin(i*0.3*f2+ph2)); const exposure=clamp((by-pos[1])/600,0,1); dir += D.windDir*D.windStr*0.06*exposure*(0.35+0.65*order/maxOrder); const dx=Math.cos(dir), dy=Math.sin(dir); pos=[pos[0]+dx*GRID, pos[1]+dy*GRID]; const sym=dirSym(dx,dy); C.push(pos[0],pos[1], bz, sym, shade(bark,rs), 'stem'); const px=-dy, py=dx; const thick=clamp(Math.round(baseThick*baseThick*2.3*woodM*(0.7+0.3*v)*(1-0.5*u)*smooth(0.12,0.85,grown)),0,2); for(let wq=1;wq<=thick;wq++){ C.push(pos[0]+px*wq*GRID, pos[1]+py*wq*GRID, bz, sym, shade(barkDk,rs), 'stem'); C.push(pos[0]-px*wq*GRID, pos[1]-py*wq*GRID, bz, sym, shade(barkDk,rs), 'stem'); } if(bears>0 && u>0.12 && i%2===0 && rs()<bears*leafKeep){ const side=((sideSeq+(i/2|0))%2===0)?1:-1; leafTuft(pos[0],pos[1], Math.atan2(py*side,px*side), rs); } if(order>=2 && i%7===4){ if(podF>0.25 && rs()<podF*0.4) seedPod(pos[0],pos[1],rs); else if(bloom>0.1 && rs()<bloom*D.flowerAb*0.3) pomCluster(pos[0]+(rs()*2-1)*GRID*2, pos[1]+(rs()*2-1)*GRID*2, budF>0.25 && rs()<0.4, rs); } while(canChild && ci<pts.length && u>=pts[ci] && budget>0){ const side=sides[ci], div=(order===0?(0.55+0.55*D.reach):D.divergence*(0.7+0.7*rs())*D.reach); let la=dir + side*div + gauss(rs)*0.14; la += angTo(la,UP)*(order===0?0.05:0.10)*(1-D.reach*0.3); const cv=vigor*(order===0?(0.72+0.26*rs()):(0.45+0.4*(1-D.dominance))*(0.8+0.3*rs())); grow(pos[0],pos[1], la, cv, order+1, birthM+growWin, pid*7+ci+2, sideSeq+ci+1); ci++; } } if(canChild && budget>0){ const leadV=vigor*lerp(0.42,0.9,D.dominance); grow(pos[0],pos[1], dir+angTo(dir,UP)*(order<=1?0.28:0.14)+gauss(rs)*0.05, leadV, order+1, birthM+growWin, pid*7+1, sideSeq+1); } else if(grown>=1){ if(rs()<leafKeep) leafTuft(pos[0],pos[1], dir, rs); const dn=Math.atan2(-Math.sin(dir),-Math.cos(dir)); if(rs()<0.6*leafKeep) leafTuft(pos[0],pos[1], dn, rs); if(podF>0.3 && rs()<podF*0.4) seedPod(pos[0],pos[1],rs); else if(bloom>0.12 && rs()<bloom*D.flowerAb*0.5) pomCluster(pos[0],pos[1], budF>0.25 && rs()<0.4, rs); } else if(order>=2){ if(rs()<leafKeep) leafTuft(pos[0],pos[1], dir, rs); } } sprout(rng('sprout',seed), rootX); grow(rootX, by-3, UP+D.lean*0.6+D.windDir*D.windStr*0.06, 1, 0, 0, 1, 0); } // ===================== SETTING: Solo / Pair / Stand / Grove ===================== const roll=rng('setting',st.seed)(); const nC = roll<0.30?0 : roll<0.62?1 : roll<0.88?2 : 3+Math.floor(rng('fn',st.seed)()*2); const comps=[]; for(let i=0;i<nC;i++){ const cseed='0x'+(hashStr(st.seed+'|c'+i)>>>0).toString(16), rc=rng('comp',st.seed,i), side=(i%2===0?1:-1), f=i/Math.max(1,nC); comps.push({seed:cseed, artSeed:cseed, cx:baseX+side*(150+i*70+rc()*46), by:baseY-(8+i*12+rc()*20), S:lerp(0.6,0.4,f)*(0.9+0.2*rc()), fade:lerp(0.40,0.60,f), m, density:clamp(density+(rc()*2-1)*0.10,0.04,1), v, sen, browny, rewards, depth:f}); } comps.sort((a,b)=>b.depth-a.depth).forEach(paintWattle);