memoscan
← all memos

Memo 0xc331b923…cf8630 on Ethereum

return {tip:pos, dir}; } function drawBud(bcx,bcy,R,rs){ for(let gx=-R;gx<=R;gx+=GRID) for(let gy=-R*1.3;gy<=R*1.3;gy+=GRID){ if((gx*gx)/(R*R)+(gy*gy)/(R*1.3*R*1.3)>1) continue; const edge=(gx*gx)/(R*R)+(gy*gy)/(R*1.3*R*1.3)>0.62; C.push(bcx+gx,bcy+gy,72, dirSym(0.25,1), shade(FC(mix(P.bud, P.leafDark, edge?0.4:0.1)),rs),'leaves'); } for(let k=0;k<7;k++){ const a=ra()*TAU; C.push(bcx+Math.cos(a)*R*1.15*0.7, bcy+Math.sin(a)*R*1.4, 71, '/', shade(leafD,rs),'leaves'); } } function drawBloom(bcx,bcy,R,open,faceA,rs){ const Rp=R*(0.5+0.5*open), off=ra()*TAU; for(let k=0;k<4;k++){ const bk=off+k*TAU/4, ax=Math.cos(bk), ay=Math.sin(bk), px=-ay, py=ax, du=GRID/Math.max(Rp,1); for(let u=0.12;u<=1;u+=du){ const flare=Math.pow(Math.sin(Math.PI*Math.min(u,0.999)),0.72), rag=1 - D.petalRag*Math.abs(Math.sin(u*7+bk*3)), halfW=0.62*Rp*flare*rag, cxp=bcx+ax*u*Rp, cyp=bcy+ay*u*Rp; for(let w=-halfW;w<=halfW;w+=GRID){ const edge=Math.abs(w)>halfW-GRID*1.4; const col = u<0.30 ? FC(P.blotch) : (edge? FC(P.petalEdge) : (w*ax+0>0?petalC:petalWarm)); C.push(cxp+px*w, cyp+py*w, 68, dirSym(ax,ay), shade(mix(col,FC(P.stemDry),browny*0.6),rs), 'flowers'); } } } const Rc=R*0.26; for(let gx=-Rc;gx<=Rc;gx+=GRID) for(let gy=-Rc;gy<=Rc;gy+=GRID) if(Math.hypot(gx,gy)<Rc) C.push(bcx+gx,bcy+gy,90, ra()<0.4?'.':'+', shade(FC(P.center),rs),'flowers'); for(let k=0;k<13;k++){ const a=k/13*TAU; C.push(bcx+Math.cos(a)*R*0.36, bcy+Math.sin(a)*R*0.36, 91, '.', shade(FC(P.stamen),rs),'flowers'); } } function drawCapsule(bcx,bcy,R,rs){ const Hc=R*1.45, dry=Math.max(sen,browny*0.6), cc=mix(capC,FC(P.capsuleDry),dry); for(let gy=-Hc*0.5;gy<=Hc*0.55;gy+=GRID){ const u=(gy+Hc*0.5)/Hc, halfW=R*Math.sin(Math.PI*clamp(u*0.9+0.08,0,1)); for(let gx=-halfW;gx<=halfW;gx+=GRID){ const edge=Math.abs(gx)>halfW-GRID*1.2; C.push(bcx+gx,bcy+gy,86,'|', shade(edge?mix(cc,'#000000',0.18):cc,rs),'flowers'); } } for(let k=0;k<11;k++){ const a=Math.PI + (k/10-0.5)*Math.PI; C.push(bcx+Math.cos(a)*R*0.95, bcy-Hc*0.5+Math.sin(a)*R*0.95*0.32, 88, '-', shade(mix(cc,'#000000',0.22),rs),'flowers'); } for(let k=0;k<7;k++) C.push(bcx+(rs()*2-1)*R*0.7, bcy-Hc*0.36, 89, '.', shade(FC(P.pore),rs),'flowers'); } for(let i=0;i<nStem;i++){ const rs=rng('stem',seed,i), emerge=i===nStem-1?clamp(nStemF-(nStem-1),0.02,1):1, grow=smooth(0,1,emerge); let phase=clamp(0.12 + m*0.66 - i*0.12 + (rs()*2-1)*0.05, 0, 1.25); phase=clamp(phase + sen*0.6, 0, 1.3); const sx=rootX + (i-(nStem-1)/2)*D.spread*0.62*S + (rs()*2-1)*8, hLen=((axisH+50*S) + (160+300*m)*D.stature*(0.80+0.30*rs())*(0.85+0.15*v)*S)*grow; const seasonVig=0.6+0.4*seasonBloom, canBloom=rewards*(1-sen)*seasonVig; const isBud=phase<0.44, isBloom=!isBud && phase<0.74 && canBloom>0.3, nod=isBud ? clamp(1 - smooth(0.16,0.44,phase),0,1) : 0; const {tip}=growStem(sx, hLen, nod, rs), tx=tip[0], ty=tip[1]; if(isBud){ drawBud(tx,ty,(8+8*m)*emerge*(0.7+0.3*v)*S,rs); } else if(isBloom){ const open=smooth(0.44,0.62,phase)*(0.6+0.4*canBloom); drawBloom(tx,ty,(30+34*m)*emerge*(0.72+0.28*v)*(0.6+0.4*open)*S,open,UP,rs); } else { drawCapsule(tx,ty,(13+15*m)*emerge*(0.78+0.22*v)*S,rs); } } } // ===================== SETTING: Solo / Pair / Drift / Meadow / rare Field carpet ===================== const roll=rng('setting',st.seed)(); const nC = roll<0.16?0 : roll<0.44?1 : roll<0.72?2 : roll<0.91?3+Math.floor(rng('fn',st.seed)()*2) : 8+Math.floor(rng('fn',st.seed)()*6); const field=nC>=6, 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); let scale,off,raise,fade,depth; if(field){ const slot=(i+0.5)/nC, xr=clamp(slot+(rc()*2-1)*(0.6/nC),0.03,0.97); depth=0.15+0.85*rc(); scale=lerp(0.6,0.3,depth)*(0.9+0.2*rc()); raise=(depth*135+rc()*24); fade=lerp(0.42,0.70,depth); off=lerp(70,W-70,xr)-baseX; } else { const side=(i%2===0?1:-1), f=i/Math.max(1,nC); depth=f; scale=lerp(0.72,0.44,f)*(0.9+0.2*rc()); off=side*(86+i*50+rc()*46); raise=(36+i*24+rc()*32); fade=lerp(0.38,0.58,f); } comps.push({seed:cseed, artSeed:cseed, cx:baseX+off, by:baseY-raise, S:scale, fade, m, density:clamp(density+(rc()*2-1)*(field?0.18:0.12),0.04,1), v, sen, browny, rewards, depth}); } comps.sort((a,b)=>b.depth-a.depth).forEach(paintPoppy); paintPoppy({seed:st.seed, artSeed:st.artSeed, cx:baseX, by:baseY, S:1.0, fade:0, m, density, v, sen, browny, rewards}); }, traits(st,env){ const roll=rng('setting',st.seed)(); const setting = roll<0.16?'Solo' : roll<0.44?'Pair' : roll<0.72?'Drift' : roll<0.91?'Meadow':'Field'; const dd=rng('dna',st.seed); dd();dd();dd();dd();dd();dd();dd();dd(); // advance to windStr's draw (9th) const windStr=Math.pow(dd(),2.3); return [ ['Setting', setting], ['Stems', Math.max(1,Math.ceil(clamp(0.7+env.density*5.2,0.7,6.5)))], ['Wind', windStr<0.12?'Still':windStr<0.40?'Breezy':windStr<0.70?'Wind-bent':'Storm-bent'], ['Bloom', st.diedAt>0?'Capsules':(env.m>=0.58 && env.rewards>0.3?'Scarlet':'—')], ]; } })); })(); ; /* species/saguaro.js */ /* saguaro — species index 13. GENERATED from prototypes/saguaro-cactus.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(13, spec); /* Saguaro Cactus — Carnegiea gigantea. A single tall RIBBED COLUMN with a rounded dome top — the ribs are vertical pleats that accordion in/out with water; spines sit at areoles along the ribs. ARMS appear only after long growth (high maturity): each curves OUT then bends UP at the elbow. The reward is an apex CROWN of white funnel flowers with golden stamen centres → ruby fruit that splits to black seeds. Near-unkillable (CAM desert); drought just contracts the pleats. Death = a standing pale RIB SKELETON. Solitary, but framed by desert companions (ocotillo, prickly pear, agave). */ const SP = window.SEEDLING_SPECIES.find(x=>x.key==='saguaro'); mount(Object.assign(window.SEEDLING_toSpec(SP), { title:'Saguaro Cactus', sub:'carnegiea v2', senFrom:0.86, noSoil:true, note:'Legendary · slow ribbed column · arms only after long growth · apex crown of white flowers → red fruit · pleats accordion with drought · death = standing rib skeleton.', PAL:{ soil:'#C2A573', sand:'#D8C29A', pebble:'#B59A6E', bodyLt:'#88A56F', body:'#6E8B6E', bodyDk:'#47643F', rib:'#37502F', spine:'#E0D4AE', petal:'#FCFAF4', petalEdge:'#C2C4B2', petalShade:'#B7B4A8', stamen:'#EBC53E', stamenDk:'#C99A24', floBase:'#8A9A58', bud:'#7E9560', budTip:'#5E6B3E', fruit:'#A8332A', fruitDk:'#7E2018', fruitGreen:'#85974D', seed:'#241B14', pulp:'#C86A52', skelLt:'#C3B088', skelDk:'#8C7A5A', comp:'#9BA873', compDk:'#6E7A4E' }, stageName(st,env){ if(st.diedAt>0)return'Rib skeleton'; const m=env.m; if(m<0.12)return'Seedling'; if(m<0.5)return'Column'; if(m<0.72)return'Juvenile'; if(m<0.86)return'Armed'; return'Crowned'; }, draw(st, C, env){ const {m,v,sen,season,browny,density,rewards,reproOK,dormancy,dead,baseX,baseY}=env, P=this.PAL; const SPEC=this; // nested paintSaguaro() loses `this` const bgC = BACKDROPS[backdropFor(st.seed)]; const seasonF = (season==='Spring'||season==='Summer')?1:0.35; // ===================== one saguaro (repeated to form a random stand) ===================== function paintSaguaro(I){ const {cx, by, S, fade, m, v, sen, rewards, reproOK, browny, dead, pseed} = I; const FC = c => fade>0 ? mix(c, bgC, fade) : c; const drySkel = clamp((dead?1:0) + smooth(0.86,1.0,Math.max(sen, I.ageF||0))*0.9, 0, 1); // → standing rib skeleton const accordion = 0.78 + 0.22*v; // pleats contract when thirsty (recovers fully) const tone=(c,rs)=>shade(FC(mix(mix(c,'#b9a06a',browny*0.5), c===P.rib?P.skelDk:P.skelLt, drySkel)),rs); const COLS={lt:P.bodyLt, dk:P.bodyDk, rib:P.rib}; const isAutumn=season==='Autumn'; // Flower → fruit is sequenced purely by MATURITY (monotonic m), flicker-free: a freshly-reproductive // saguaro (m≈0.75) FLOWERS white; once well-established (m≥0.80) the same crown sets RED FRUIT. // Because m only ever RISES, the flower phase ALWAYS precedes the fruit — but fruit is NOT gated on // the in-app calendar season (a plant matured via entries, which barely advance the day, would // otherwise show flowers forever and never fruit). Autumn still ripens the fruit DEEPER red below. const crownMode = (m>=0.80) ? 'fruit' : 'flower'; const lush = clamp(rewards*(1-0.6*sen), 0, 1); // smooth amount control (no mode-flipping thresholds) const crownOn = reproOK && lush>0.10 && m>=0.75; // vanishes only under severe neglect / off-season // -- a ribbed segment: a cross-row of vertical pleats, 3-D shaded (centre lit, edges in shadow) -- function ribRow(px,py,dirX,dirY,hw,nRib,z,rs,areole){ const perpX=-dirY, perpY=dirX, gl=dirSym(dirX,dirY); for(let j=0;j<nRib;j++){ const th=-Math.PI/2 + Math.PI*(nRib>1?j/(nRib-1):0.5); const off=Math.sin(th)*hw, lit=Math.cos(th), edge=Math.abs(th)>Math.PI*0.42; const col = edge?COLS.rib : mix(COLS.dk, COLS.lt, lit*lit); C.push(px+perpX*off, py+perpY*off, z, gl, tone(col,rs),'stem'); if(areole && !edge && j%2===0 && drySkel<0.5) C.push(px+perpX*off, py+perpY*off, z+1,'+', shade(FC(mix(P.spine,bgC,0.1)),rs),'stem'); } } // -- per-seed DNA: each saguaro's silhouette is distinct (girth, height, lean + a full arm plan), // so seeds aren't all the predictable symmetric two-arm Y. Shared with traits() via armPlan(). -- 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:'#56