0xffd6563c…95fesent to0x72842d09…a7a6·#25,495,201·view on Etherscan
function bud(x,y,rs){ C.push(x,y,80,'O',FC(shade(P.bud,rs)),'flowers'); C.push(x,y-GRID*0.6,81,'.',FC(shade(mix(P.bud,P.springRed,springFlush),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=(52+46*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'); }
if(bareWinter){ bud(pos[0],pos[1],rs); return; }
// juvenile seedling carries a small SPRAY of first palmate leaves (not a bare whip with one leaf)
const nl=1+Math.round(clamp(g,0,1)*2.4); // 1–3 first leaves
for(let k=0;k<nl;k++){ const a=UP + (k-(nl-1)/2)*0.52 + (rs()*2-1)*0.16; leafSpray(pos[0],pos[1], a, (6.5+6*g)*S, rs); }
if(g>0.5){ const md=steps>3?Math.floor(steps*0.6):2, mp=[rx+Math.cos(UP)*GRID*md, by-3+Math.sin(UP)*GRID*md]; // a lower pair along the stem
for(const sd of [1,-1]) if(rs()<0.7) leafSpray(mp[0],mp[1], UP+sd*0.7, (5+5*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('mb', seed, pid); let run=baseRun*Math.pow(D.taperLen,order)*(0.82+0.36*rs())*grown; if(order===0) run*=0.82;
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 : 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.22:0.30, hi=0.93, 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.1, 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.12,0.035,clamp(order/3,0,1))*Math.max(0,1-order/3.2), grav=0.12*D.droop*smooth(1.4,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*1.9*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(order>=2 && u>0.1 && i%3===1){
if(bareWinter){ if(rs()<0.22) bud(pos[0],pos[1],rs); }
else if(rs()<bears){ const side=((sideSeq+(i/2|0))%2===0)?1:-1; leafSpray(pos[0],pos[1], Math.atan2(py*side,px*side), (12+11*m)*(0.85+0.3*rs())*S, rs); }
if(springFlush>0.3 && rewards>0.25 && rs()<0.05) flowerCluster(pos[0],pos[1],rs);
if((fadeAut>0.2 || (springFlush<0.2 && leafOut>0.6 && doy>150)) && rewards>0.25 && rs()<0.05*D.samaraAb) samara(pos[0],pos[1],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.08)*(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.38,0.9,D.dominance); grow(pos[0],pos[1], dir+angTo(dir,UP)*(order<=1?0.26:0.12)+gauss(rs)*0.06, leadV, order+1, birthM+growWin, pid*7+1, sideSeq+1); }
else if(grown>=1){ if(bareWinter){ bud(pos[0],pos[1],rs); } else { leafSpray(pos[0],pos[1], dir, (15+13*m)*S, rs); if(rs()<0.5) leafSpray(pos[0],pos[1], dir+0.7, (13+11*m)*S, rs); } }
else if(order>=2 && !bareWinter){ leafSpray(pos[0],pos[1], dir, (13+10*m)*S, rs); }
}
const rootX=cx - D.windDir*D.windStr*48*S;
sprout(rng('sprout',seed), rootX);
grow(rootX, by-3, UP+D.lean*0.6+D.windDir*D.windStr*0.07, 1, 0, 0, 1, 0);
}
// ===================== SETTING: maples are specimen trees (mostly Solo) =====================
const roll=rng('setting',st.seed)();
const nC = roll<0.5?0 : roll<0.82?1 : 2+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*(170+i*70+rc()*40), by:baseY-(4+i*10+rc()*16), S:lerp(0.58,0.42,f)*(0.9+0.2*rc()), dim:lerp(0.42,0.60,f),
m:clamp(m+(rc()*2-1)*0.06,0.04,1), density, v, sen, rewards, depth:f}); }
comps.sort((a,b)=>b.depth-a.depth).forEach(paintMaple);
paintMaple({seed:st.seed, artSeed:st.artSeed, cx:baseX, by:baseY, S:1.0, dim:0, m, density, v, sen, rewards});
},
traits(st,env){
const D=mapleDNA(st.seed);
const roll=rng('setting',st.seed)();
const setting = roll<0.5?'Solo' : roll<0.82?'Pair' : 'Grove';
const doy=(((env.now+70)%365)+365)%365;
const colour = (doy<56||doy>352)?'Bare' : doy<96?'Spring red' : doy<240?'Green' : doy<300?'Turning' : 'Autumn fire';
const cult = D.cultivar>0.55?'Purple' : D.cultivar>0.25?'Bronze' : 'Green';
const auh = D.autumnHue>0.6?'Scarlet' : D.autumnHue>0.3?'Orange' : 'Gold';
return [
['Setting', setting],
['Colour now', colour],
['Lobes', D.lobes],
['Cultivar', cult],
['Autumn', auh],
['Wind', D.windStr<0.12?'Still':D.windStr<0.40?'Breezy':D.windStr<0.70?'Windswept':'Storm-bent'],
];
}
}));
})();
;
/* species/poppy.js */
/* poppy — species index 1. GENERATED from prototypes/field-poppy.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(1, spec);
/* Field Poppy — Papaver rhoeas. A leafy clump of deeply pinnately-lobed (fern-like) leaves, out of
which slender hairy stems BOLT and each runs its own lifecycle: a nodding green bud → straightens
→ 4 papery scarlet petals with a dark blotch + stamen ring → a ribbed pepper-pot seed CAPSULE. An
older plant carries all stages at once. Coherent WIND leans the whole plant. Monocarpic. Scatters
in drifts → a rare Field carpet. Ported to the shared core. */
const SP = window.SEEDLING_SPECIES.find(x=>x.key==='poppy');
mount(Object.assign(window.SEEDLING_toSpec(SP), {
title:'Field Poppy', sub:'papaver v2',
senFrom:0.80,
note:'Lobed fern-like rosette · slender hairy stems bolt and bloom · nodding bud → 4 papery scarlet petals → ribbed capsule (all stages at once) · wind-leaned · scatters into drifts/fields.',
PAL:{ soil:'#6f5128', dry:'#7a5a30',
stem:'#5E7B3A', stemDry:'#9A8A52',
leafDark:'#46662F', leafMid:'#6E8B4E', leafLight:'#8BA86A',
petal:'#E0382C', petal2:'#C42718', petalEdge:'#F0664E', blotch:'#2A0E07',
center:'#1D1208', stamen:'#241606',
bud:'#7E924E', capsule:'#8FA05C', capsuleDry:'#A6924E', pore:'#241a0c' },
stageName(st,env){ if(st.diedAt>0)return'Sealed'; const m=env.m;
if(env.ageF>0.85)return'Senescent'; if(m<0.12)return'Sprout'; if(m<0.40)return'Rosette';
if(m<0.58)return'Budding'; if(m<0.80)return'Blooming'; return'Seeding'; },
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)];
const seasonBloom = season==='Summer'?1 : season==='Spring'?0.9 : season==='Autumn'?0.7 : 0.55;
function poppyDNA(seed){ const dd=rng('dna',seed), g=()=>(dd()+dd()+dd()-1.5)/1.5;
return { stems:0.7+0.9*dd(), spread:26+26*dd(), stature:0.84+0.30*dd(), waviness:0.5+1.0*dd(),
petalRag:0.10+0.30*dd(), petalHue:g()*0.5, leafCut:0.55+0.45*dd(), bristly:0.5+0.5*dd(),
windDir:dd()<0.5?1:-1, windStr:Math.pow(dd(),2.3) }; }
// ===================== one poppy plant (repeated to scatter into a drift/field) =====================
function paintPoppy(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 ra=rng('struct',artSeed), D=poppyDNA(seed);
const stemC=FC(mix(P.stem,P.stemDry,Math.max((1-v)*0.5,sen)));
const leafD=FC(mix(P.leafDark,P.stemDry,browny)), leafM=FC(mix(P.leafMid,P.stemDry,browny)), leafL=FC(mix(P.leafLight,P.stemDry,browny*0.9));
const petalC=FC(mix(P.petal,P.petal2,clamp(-D.petalHue,0,1)*0.7)), petalWarm=FC(mix(mix(P.petal,P.petal2,clamp(-D.petalHue,0,1)*0.7),'#F08A3C',clamp(D.petalHue,0,1)*0.6));
const capC=FC(mix(P.capsule,P.capsuleDry,Math.max(sen,browny*0.55)));
const greenOf=rs=>shade(mix(leafM, rs()<0.5?leafD:leafL, rs()),rs);
const windLean=D.windDir*D.windStr*0.42, rootX=cx - D.windDir*D.windStr*46*S;
function lobedLeaf(bx,byy,ang,len,rl){
const steps=Math.max(3,Math.ceil(len/GRID)), droop=clamp(sen+(1-v)*0.4,0,1)*0.7;
let dx=Math.cos(ang),dy=Math.sin(ang); dx=lerp(dx,0,droop); dy=lerp(dy,1,droop); const n=Math.hypot(dx,dy)||1; dx/=n; dy/=n;
let dir=Math.atan2(dy,dx), pos=[bx,byy];
for(let s=1;s<=steps;s++){ const u=s/steps; dir += Math.sin(u*3.0+rl()*6)*0.04; 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], 42, dirSym(cd[0],cd[1]), shade(leafM,rl), 'leaves');
const px=-cd[1], py=cd[0], ev=Math.sin(Math.PI*Math.min(u,0.999)), lobeLen=GRID*(0.8+(2.4*D.leafCut)*ev);
if(s%2===0 && ev>0.18){ for(const sd of [1,-1]){ let la=Math.atan2(py*sd,px*sd); la += angTo(la,dir)*0.30;
const ll=lobeLen*(0.7+0.5*rl()), lsteps=Math.max(1,Math.round(ll/GRID));
for(let t=1;t<=lsteps;t++){ const lp=[pos[0]+Math.cos(la)*GRID*t, pos[1]+Math.sin(la)*GRID*t], edge=t>=lsteps;
C.push(lp[0],lp[1], 41, edge?'X':dirSym(Math.cos(la),Math.sin(la)), edge?shade(leafD,rl):greenOf(rl), 'leaves'); } } }
}
}
const foliageF=(1-0.5*sen), axisH=(78+150*m)*(0.85+0.30*D.stature)*foliageF*S;
{ const rl=rng('axis',seed); let dir=UP+windLean*0.4, pos=[rootX,by-4]; const steps=Math.max(2,Math.ceil(axisH/GRID));
for(let s=1;s<=steps;s++){ const u=s/steps; dir += Math.sin(u*2+rl()*3)*0.02 + windLean*0.03*u;
pos=[pos[0]+Math.cos(dir)*GRID, pos[1]+Math.sin(dir)*GRID]; C.push(pos[0],pos[1],46,dirSym(Math.cos(dir),Math.sin(dir)),shade(stemC,rl),'stem'); } }
const nFolF=clamp(3 + density*6*foliageF + (rng('nl',seed)()*2-1)*0.6, 2.5, 10), nFol=Math.ceil(nFolF);
for(let i=0;i<nFol;i++){ const rl=rng('leaf',seed,i), side=i%2?1:-1, emerge=i===nFol-1?clamp(nFolF-(nFol-1),0.2,1):1, u=(nFol>1? i/(nFol-1):0);
const nodeX=rootX + windLean*38*u*S + (rl()*2-1)*5, nodeY=by-4 - u*axisH*0.96, spread=lerp(1.18,0.46,u);
const ang=UP + side*spread + windLean*0.3 + (rl()*2-1)*0.12, len=(46+78*m)*lerp(1.15,0.5,u)*(0.78+0.34*rl())*emerge*S;
lobedLeaf(nodeX,nodeY,ang,len,rl); }
const boltF=smooth(0.24,0.62,m), nStemF=clamp(boltF*(0.8 + density*4.2*D.stems) + (rng('ns',seed)()*2-1)*0.4, 0, 5), nStem=Math.ceil(nStemF);
function growStem(sx, hLen, nod, rs){ const steps=Math.max(3,Math.ceil(hLen/GRID)), wob=(rs()*2-1)*0.6, ph=rs()*6.28, aim=UP+windLean*0.7;
let dir=UP+windLean*0.5, pos=[sx,by-4];
for(let s=1;s<=steps;s++){ const u=s/steps, exposure=clamp((by-pos[1])/520,0,1);
dir += angTo(dir,aim)*0.06 + Math.sin(u*5+ph+wob*6)*0.009*D.waviness + sen*0.045*exposure;
if(nod>0 && u>0.66) dir += angTo(dir,DOWN)*nod*0.17*smooth(0.66,1,u);
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], 50, dirSym(cd[0],cd[1]), shade(stemC,rs), 'stem');
if(s%3===0 && rs()<0.5*D.bristly){ const px=-cd[1], py=cd[0], sd=rs()<0.5?1:-1; C.push(pos[0]+px*sd*GRID, pos[1]+py*sd*GRID, 48, '-', shade(leafD,rs), 'leaves'); } }
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 armPl