0.85),rl);
C.push(pos[0]+px*w, pos[1]+py*w, z, sym, col, 'leaves'); }
if(teeth && s%4===0 && u<0.92){ C.push(pos[0]+px*(hw+GRID*0.6), pos[1]+py*(hw+GRID*0.6), z+4, '.', shade(toothC,rl), 'leaves');
C.push(pos[0]-px*(hw+GRID*0.6), pos[1]-py*(hw+GRID*0.6), z+4, '.', shade(toothC,rl), 'leaves'); }
if(flecky && u>0.12 && rl()<0.10){ const fw=(rl()*2-1)*hw*0.7; C.push(pos[0]+px*fw, pos[1]+py*fw, z+3, '.', shade(FC(P.fleck),rl), 'leaves'); }
}
}
const nLeafF=clamp(2 + density*11*D.leafN + (rng('nl',seed)()*2-1)*0.7, 2, 14), nLeaf=Math.ceil(nLeafF), nPair=Math.max(1,Math.ceil(nLeaf/2));
const openF=smooth(0.12,0.7,m), spreadMax=lerp(0.28,0.62,openF);
for(let i=0;i<nLeaf;i++){ const rl=rng('leaf',seed,i), emerge=i===nLeaf-1?clamp(nLeafF-(nLeaf-1),0.06,1):1, grow=smooth(0,1,emerge);
const pair=Math.floor(i/2), side=(i%2?1:-1), tp=nPair>1? pair/(nPair-1) : 0.3, mag=lerp(spreadMax, 0.12, tp);
const wob=Math.sin(i*GA*D.chir + D.spiralPh)*0.13*openF*D.twist; // per-seed handedness + phase (was uniform)
const full=side===D.fullSide, lop=full?1+0.22*D.fullAmt:1-0.16*D.fullAmt, lopLen=full?1+0.12*D.fullAmt:1-0.08*D.fullAmt;
const ang=axisUP + side*mag*lop + wob + windLean*0.4, recurve=(1-tp)*0.7*D.recurve + wilt*1.0, vase=lerp(1.0, lerp(1.0,0.74,tp), smooth(0.15,0.6,m));
const len=(92+208*m)*D.stature*vase*(0.82+0.26*rl())*lopLen*grow*S, widthM=lerp(0.32,1.0,smooth(0.04,0.55,m));
const fat=(4.4 + 10.5*D.fat)*(0.62+0.38*v)*grow*lerp(1.12,0.76,tp)*widthM*Math.sqrt(S), z=40 + i;
drawLeaf(ang, len, fat, recurve, rl, z, D.teeth>0.4, D.flecks>0.35);
}
const flowerVigor=smooth(0.52,0.84,m)*rewards, bloomOn=flowerVigor*seasonBloom*(1-0.5*sen);
function drawCone(axis, racStart, steps, span, spikeColor, dry, wide, seedk){ const maxCone=GRID*(2.0+1.2*wide)*Math.sqrt(S), front=spikeColor*1.3;
for(let k=racStart;k<steps;k++){ const p=(k-racStart)/span, coneW=maxCone*Math.pow(clamp(1-p,0,1),0.72)+GRID*0.35, ax=axis[k][0], ay=axis[k][1], nf=Math.max(1,Math.round(coneW/GRID));
for(let j=-nf;j<=nf;j++){ if(j===0 && p<0.9) continue; const rr=rng('flo',seed, seedk + k*29 + (j+nf)*7), off=j*GRID*0.92 + (rr()*2-1)*GRID*0.18;
if(Math.abs(off)>coneW+GRID*0.4) continue; const aw=Math.abs(off)/Math.max(coneW,1), fx=ax+off, fy=ay + aw*GRID*1.1 + (rr()*2-1)*GRID*0.2;
if(rr()<0.4) C.push(ax+off*0.45, ay+aw*GRID*0.5, 88, off>0?'\\':'/', shade(FC(P.budDk),rr), 'flowers');
const green = p>0.86 || p>=front; let col;
if(green) col=shade(FC(mix(P.bud,P.budDk,0.25)),rr);
else { const u=rr(); col=shade(FC(mix(u<0.22?P.flowerDk:u<0.82?P.flower:P.flowerTip, P.capsuleDry, dry*0.7)),rr); if(u>=0.22&&u<0.82) col=shade(mix(floC,FC(P.capsuleDry),dry*0.7),rr); }
C.push(fx, fy, green?90:92, rr()<0.42?'.':(rr()<0.72?'+':'x'), col, 'flowers');
}
}
}
function drawRaceme(sx, lean, hScale, spikeColor, grow, seedk, rs){ const sh=(185+330*m)*D.stature*hScale*smooth(0.5,0.96,m)*grow*S; if(sh<GRID*5) return;
const steps=Math.ceil(sh/GRID), ph=rs()*6.28, aim=axisUP+windLean*0.7+lean; let dir=axisUP+windLean*0.4+lean*0.7, pos=[sx,cy-6*S], axis=[];
for(let s=1;s<=steps;s++){ const u=s/steps; dir += angTo(dir,aim)*0.06 + Math.sin(u*4+ph)*0.006 + sen*0.04*u;
pos=[pos[0]+Math.cos(dir)*GRID, pos[1]+Math.sin(dir)*GRID]; C.push(pos[0],pos[1], 70, '|', shade(stalkC,rs), 'stem'); axis.push([pos[0],pos[1]]); }
const infFrac=lerp(0.20, 0.40, smooth(0.5,0.96,m)), racStart=Math.floor(steps*(1-infFrac)), span=Math.max(1, steps-1-racStart);
drawCone(axis, racStart, steps, span, spikeColor, Math.max(sen,browny*0.6), hScale, seedk);
}
if(bloomOn>0.12){ const nSpikeF=clamp(smooth(0.5,0.82,m)*(1.2+3.8*D.pups), 0, 5), nSpike=Math.ceil(nSpikeF);
for(let i=0;i<nSpike;i++){ const emerge=i===nSpike-1?clamp(nSpikeF-(nSpike-1),0.04,1):1, grow=smooth(0,1,emerge), pairIdx=Math.ceil(i/2), side=(i%2?1:-1)*D.chir;
const lean=(i===0?0:side*pairIdx*0.18), hScale=(i===0?1:1-pairIdx*0.18)*(0.92+0.16*rng('sh',seed,i)()), sx=cx + (i===0?0:side*pairIdx*15*S) + (ra()*2-1)*4;
const spikeColor=smooth(0.58,0.86,m)*grow; drawRaceme(sx, lean, hScale, spikeColor, grow, 1000+i*263, rng('rac',seed,i)); }
}
const pupN=Math.floor(smooth(0.5,0.96,m)*D.pups*3.2);
for(let p=0;p<pupN;p++){ const rp=rng('pup',seed,p), side=p%2?1:-1, sx=cx + side*(42+p*26+rp()*26)*D.stature*S, sy=cy+4*S, nl=3+Math.floor(rp()*3);
for(let k=0;k<nl;k++){ const ps=(k%2?1:-1), pm=lerp(0.2,1.0,Math.floor(k/2)/Math.max(1,Math.ceil(nl/2)-1)), pang=UP + leanTilt*0.4 + ps*lerp(0.2,0.95,pm) + (rp()*2-1)*0.1;
let dir=pang, pos=[sx,sy]; const plen=(18+24*rp())*S, pst=Math.ceil(plen/GRID);
for(let s=1;s<=pst;s++){ const u=s/pst; dir+=angTo(dir,DOWN)*0.02*u; pos=[pos[0]+Math.cos(dir)*GRID, pos[1]+Math.sin(dir)*GRID];
const px=-Math.sin(dir),py=Math.cos(dir), hw=Math.max(0.5,(1.6+2.4*D.fat)*Math.pow(1-u,0.7)*Math.sqrt(S));
for(let w=-hw;w<=hw;w+=GRID){ const edge=Math.abs(w)>hw-GRID; C.push(pos[0]+px*w,pos[1]+py*w, 38, dirSym(Math.cos(dir),Math.sin(dir)), edge?shade(toothC,rp):shade(leafM,rp), 'leaves'); }
}
}
}
}
// ===================== SETTING: Solo / Pair / Clump / Colony (hero scaled up) =====================
const roll=rng('setting',st.seed)();
const nC = roll<0.18?0 : roll<0.48?1 : roll<0.80?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);
const scale=lerp(0.52,0.34,f)*(0.9+0.2*rc()), off=side*(120+i*54+rc()*40), raise=(20+i*16+rc()*22);
comps.push({seed:cseed, artSeed:cseed, cx0:baseX+off, by:baseY-raise, S:scale, fade:lerp(0.38,0.58,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(paintAloe);
paintAloe({seed:st.seed, artSeed:st.artSeed, cx0:baseX, by:baseY, S:1.2, fade:0, m, density, v, sen, browny, rewards});
},
traits(st,env){
const roll=rng('setting',st.seed)();
const setting = roll<0.18?'Solo' : roll<0.48?'Pair' : roll<0.80?'Clump' : 'Colony';
return [
['Setting', setting],
['Leaves', Math.max(2,Math.ceil(clamp(2+env.density*15,2,19)))],
['Spikes', st.diedAt>0?'—':(env.m>=0.6 && env.rewards>0.3?'Coral':'—')],
];
}
}));
})();
;
/* species/amla.js */
/* amla — species index 12. GENERATED from prototypes/amla.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(12, spec);
/* Amla (Indian Gooseberry) — Phyllanthus emblica. A DECURRENT/weeping tree: a recursive staggered-
lateral framework with a short clear trunk that forks low into a broad LOW crown weeping to a near-
ground skirt. Clothed in airy feathery FRONDS (thin arching rachis + sparse leaflets — fern-like,
see-through). Reward: pale round translucent FRUIT hung along the outer wood (autumn/winter), which
PERSISTS through senescence as the leaflets drop → a skeletal fruit-bearing tree. Coherent WIND
flags the whole tree downwind. Deciduous. Grows in groves/orchards — faded trees stand behind.
Ported to the shared core. */
const SP = window.SEEDLING_SPECIES.find(x=>x.key==='amla');
mount(Object.assign(window.SEEDLING_toSpec(SP), {
title:'Amla', sub:'phyllanthus v2',
senFrom:0.82,
LW:{season:1.0, stem:1.2, leaves:1.0, flowers:1.7},
LS:{season:1.0, stem:1.0, leaves:0.9, flowers:0.8},
note:'Weeping decurrent tree · short trunk forks low into a broad low crown · airy feathery fronds · pale round fruit hung on the outer wood (persists as leaflets drop → skeletal fruiting tree) · wind-flagged · deciduous · groves.',
PAL:{ soil:'#6f5128', grey:'#9a9180',
bark:'#8A8577', barkDk:'#6B6658',
leafDark:'#5E7A3E', leaf:'#86A256', leafLt:'#B2C77E',
fruit:'#B9C24E', fruitLt:'#D4DA72', fruitDk:'#959D3A' },
stageName(st,env){ if(st.diedAt>0)return'Sealed'; const m=env.m;
if(env.ageF>0.87)return'Senescent'; if(m<0.12)return'Sprout'; if(m<0.40)return'Sapling';
if(m<0.62)return'Young tree'; if(m<0.82)return'Branching'; return'Fruiting'; },
draw(st, C, env){
const {m,v,sen,season,browny,density,rewards,baseX,baseY}=env, P=this.PAL;
const bgC = BACKDROPS[backdropFor(st.seed)];
function amlaDNA(seed){ const dd=rng('dna',seed), g=()=>(dd()+dd()+dd()-1.5)/1.5;
return { dominance:0.14+0.40*dd(), divergence:0.50+0.55*dd(), droop:0.50+0.70*dd(), gnarl:0.22+0.55*dd(), taperLen:0.62+0.12*dd(),
lateralN:1.5+1.0*dd(), reach:0.80+0.45*dd(), lean:g()*0.16, stature:0.86+0.16*dd(), maxOrder:5+(dd()<0.5?1:0),
windDir:dd()<0.5?1:-1, windStr:Math.pow(dd(),2.6) }; }
const sLeaf = season==='Summer'||season==='Spring'?1 : season==='Autumn'?0.6 : 0.12;
const sFruit= season==='Autumn'?1 : season==='Winter'?0.8 : season==='Summer'?0.35 : 0.08;
// ===================== one amla tree (hero or faded background companion) =====================
function paintAmla(I){
const {seed, artSeed, cx, by, S, fade, zb, m, v, sen, browny, rewards, density} = I;
const FC = c => fade>0 ? mix(c, bgC, fade) : c;
const PUSH = (x,y,z,sym,col,layer)=>C.push(x,y,z+zb,sym,col,layer);
const D=amlaDNA(seed);
const foliageF=(1-sen)*(0.35+0.65*v)*sLeaf, fruitF=smooth(0.42,0.80,m)*rewards*sFruit;
const bark=FC(mix(P.bark,P.grey,Math.max((1-v)*0.35,sen))), barkDk=FC(mix(P.barkDk,P.grey,Math.max((1-v)*0.35,sen)));
const leafD=FC(mix(P.leafDark,P.grey,browny)), leafM=FC(mix(P.leaf,P.grey,browny)), leafL=FC(mix(P.leafLt,P.grey,browny*0.9));
const fruitC=FC(mix(P.fruit,P.grey,browny*0.8)), fruitLt=FC(mix(P.fruitLt,P.grey,browny*0.8)), fruitDk=FC(mix(P.fruitDk,P.grey,browny*0.8));
const greenOf=rs=>shade(mix(leafM, rs()<0.5?leafD:leafL, rs()), rs);
function frond(bx,by0,baseAng,len,rs){ if(foliageF<=0.03) return; const steps=Math.max(2,Math.ceil(len/GRID)), curl=(0.45+0.9*rs())*(0.4+0.8*D.droop), wob=(rs()*2-1)*0.25;
let dir=baseAng, pos=[bx,by0];
for(let i=1;i<=steps;i++){ const u=i/steps; dir += angTo(dir,DOWN)*curl*0.05*(0.25+0.75*u) + wob*0.01; const dx=Math.cos(dir), dy=Math.sin(dir);
pos=[pos[0]+dx*GRID, pos[1]+dy*GRID]; PUSH(pos[0],pos[1], 34, dirSym(dx,dy), greenOf(rs), 'leaves');
const taper=Math.sin(Math.PI*Math.min(u,0.999)); if(taper<0.18 || rs()<0.38) continue;
const px=-Math.sin(dir), py=Math.cos(dir), ll=GRID*(0.8+1.3*taper);
for(const sd of [1,-1]){ if(rs()<0.34) continue; let la=Math.atan2(py*sd,px*sd); la += angTo(la,DOWN)*0.18;
PUSH(pos[0]+Math.cos(la)*ll, pos[1]+Math.sin(la)*ll, 33, dirSym(Math.cos(la),Math.sin(la)), greenOf(rs), 'leaves'); } } }
function fruitBall(fx,fy,fc,rs){ PUSH(fx,fy, 84, '.', shade(fc,rs), 'flowers');
for(const [ox,oy] of [[1,0],[-1,0],[0,1],[0,-1]]) PUSH(fx+ox*GRID, fy+oy*GRID, 83, '.', shade(fc,rs), 'flowers');
for(const [ox,oy] of [[1,1],[1,-1],[-1,1],[-1,-1]]) PUSH(fx+ox*GRID, fy+oy*GRID, 82, 'O', shade(fruitDk,rs), 'flowers'); }
function fruitCluster(x,y,rs,wgt){ if(rs()>fruitF*0.8) return; const n=1+Math.floor(rs()*2*wgt);
for(let k=0;k<n;k++){ const fx=x+(rs()*2-1)*GRID*2.0*S, fy=y+GRID*(1.2+rs()*3.0)*S; fruitBall(fx,fy, rs()<0.45?fruitLt:fruitC, rs); } }
function sprout(rs,rootX){ if(1-smooth(0.05,0.20,m) <= 0.02) return; const g=smooth(0,0.18,m), hLen=(64+54*g)*S; let dir=UP+D.lean*1.3, pos=[rootX,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]; PUSH(pos[0],pos[1], 50, dirSym(Math.cos(dir),Math.sin(dir)), shade(bark,rs), 'stem'); }
const tip=[pos[0],pos[1]];
for(const sd of [1,-1]){ let cd=UP + sd*(0.85+0.30*D.reach) + D.lean*0.5, cp=[tip[0],tip[1]]; const cl=Math.max(4,Math.round((34+26*g)*S/GRID));
for(let i=1;i<=cl;i++){ const u=i/cl; 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*u)*GRID*1.6, px=-Math.sin(cd), py=Math.cos(cd);
PUSH(cp[0],cp[1], 36, dirSym(Math.cos(cd),Math.sin(cd)), greenOf(rs), 'leaves');
for(let q=GRID*0.6;q<=w;q+=GRID){ PUSH(cp[0]+px*q, cp[1]+py*q, 35, q>w-GRID?'O':'.', greenOf(rs), 'leaves'); PUSH(cp[0]-px*q, cp[1]-py*q, 35, q>w-GRID?'O':'.', greenOf(rs), 'leaves'); } } } }
const growWin=0.12, maxOrder=D.maxOrder; let budget=8000;
const chain=(1-Math.pow(D.taperLen,maxOrder+1))/(1-D.taperLen), baseRun=((by-285)/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('amlabr', seed, pid); let run=baseRun*Math.pow(D.taperLen,order)*(0.82+0.36*rs())*grown; if(order===0) run*=0.72;
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);
const trunkFrond=clamp((0.40-m)/0.22,0,1), bears = order>=1 ? 1 : trunkFrond, canChild = grown>=1 && order<maxOrder;
const nLat = order===0 ? 2+Math.floor(rs()*3) : clamp(Math.round(D.lateralN*(1-0.4*D.dominance)+gauss(rs)*0.9),0, order<2?3:2);
const lo=order===0?0.22:0.30, 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.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.0), grav=0.13*D.droop*smooth(1.2,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.068*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);
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++){ PUSH(pos[0]+px*wq*GRID, pos[1]+py*wq*GRID, bz, sym, shade(barkDk,rs), 'stem'); PUSH(pos[0]-px*wq*GRID, pos[1]-py*wq*GRID, bz, sym, shade(barkDk,rs), 'stem'); }
if(bears>0 && u>0.18 && i%5===2 && rs()<bears*0.7){ const side=((sideSeq+(i/2|0))%2===0)?1:-1, fa=Math.atan2(py*side,px*side); frond(pos[0],pos[1], fa, (32+40*m)*S, rs); }
if(order>=maxOrder-3 && i%7===0) fruitCluster(pos[0],pos[1],rs,grown);
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.06:0.12)*(1-D.reach*0.4);
const cv=vigor*(order===0?(0.82+0.24*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.4,0.9,D.dominance); grow(pos[0],pos[1], dir+angTo(dir,UP)*(order<=1?0.24:0.16)+gauss(rs)*0.06, leadV, order+1, birthM+growWin, pid*7+1, sideSeq+1); }
else if(grown>=1){ const px=-Math.sin(dir),py=Math.cos(dir), baseDn=Math.atan2(-py,-px); frond(pos[0],pos[1], baseDn, (22+24*m)*S, rs); if(rs()<0.55) frond(pos[0],pos[1], dir, (20+22*m)*S, rs); }
else if(order>=1){ frond(pos[0],pos[1], dir, (16+20*m)*S, rs); }
}
const rootX=cx - D.windDir*D.windStr*70*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);
}
// ===================== faded BACKGROUND amla trees (grove) + sharp hero =====================
const roll=rng('setting',st.seed)();
const nC = roll<0.22?0 : roll<0.56?1 : roll<0.82?2 : 3; // Solo / Pair / Stand / Grove
const comps=[];
for(let i=0;i<nC;i++){ const cseed='0x'+(hashStr(st.seed+'|a'+i)>>>0).toString(16), rc=rng('acomp',st.seed,i), side=(i%2===0?-1:1), f=i/Math.max(1,nC);
const birthM=0.10+i*0.16+rc()*0.06; // staggered emergence — the grove fills in over time
const cm=clamp((m-birthM)/0.50, 0, 1); // each background tree GROWS IN from a sprout (no pop)
if(cm<=0.03) continue;
comps.push({ seed:cseed, artSeed:cseed,
cx: baseX + side*(150+i*78+rc()*60),
by: baseY-(8+i*14+rc()*22), // lifted + offset → reads as further back
S: lerp(0.58,0.40,f)*(0.9+0.18*rc()),
fade: lerp(0.42,0.62,f), // hazed toward the backdrop
zb: -130 + i*6, // strictly behind the hero
m: cm, v, sen, browny, rewards: clamp(rewards*(0.85+0.25*rc()),0,1), density,
depth:f }); }
comps.sort((a,b)=>b.depth-a.depth).forEach(paintAmla); // far → near
paintAmla({ seed:st.seed, artSeed:st.artSeed, cx:baseX, by:baseY, S:1, fade:0, zb:0, m, v, sen, browny, rewards, density });
},
traits(st,env){
const dd=rng('dna',st.seed); for(let i=0;i<10;i++)dd(); const windDir=dd()<0.5?'→':'←', windStr=Math.pow(dd(),2.6);
const roll=rng('setting',st.seed)(), setting = roll<0.22?'Solo':roll<0.56?'Pair':roll<0.82?'Stand':'Grove';
return [
['Setting', setting],
['Wind', windStr<0.12?'Still':(windStr<0.40?'Breezy':windStr<0.70?'Windswept':'Storm-bent')],
['Branch depth', clamp(Math.round(2+env.m*3),2,5)],
['Fruiting', ((env.season==='Autumn'||env.season==='Winter')&&env.m>0.5&&st.diedAt===0)?'Yes':'No'],
];
}
}));
})();
;
/* species/bamboo.js */
/* bamboo — species index 4. GENERATED from prototypes/bamboo.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(4, spec);
/* Bamboo — a clump of fluid swaying CULMS: each rises as a sheathed conical shoot, then becomes a
pale sage-gold segmented pole with swollen NODE rings, gently S-swaying and arching at the top,
with lanceolate leaf-sprays bursting from the UPPER nodes. Culms & leaves yellow with age. RARE
gregarious FLOWERING (a badge) at end of life: pendulous stamen plumes → grain seeds → die. Clump
fans (central tall, outer leaning); grove/forest via Setting. Ported to the shared core. */
const SP = window.SEEDLING_SPECIES.find(x=>x.key==='bamboo');
mount(Object.assign(window.SEEDLING_toSpec(SP), {
title:'Bamboo', sub:'bambusa v2',
senFrom:0.82,
LW:{season:1.0, stem:1.25, leaves:1.05, flowers:1.5},
LS:{season:1.0, stem:1.0, leaves:0.85, flowers:0.8},
note:'Swaying segmented culms (sheathed shoot → noded pole) · lanceolate leaf-sprays from upper nodes · yellows with age · rare gregarious flowering → seed → death · grove/forest.',
PAL:{ soil:'#6f5128', dry:'#7a5a30',
culm:'#AEC078', culmDk:'#82984E', culmGold:'#C8B852', node:'#6B7438',
sheath:'#A89A60', sheathDk:'#7E6E3C',
leaf:'#4C8A33', leafDk:'#305A22', leafLt:'#79B451', leafYellow:'#BEB047',
stamen:'#DBC766', stamenDk:'#B89A48', seed:'#9A8A52', seedDk:'#7a5a30',
dead:'#9A8A52', deadDk:'#7a5a30' },
stageName(st,env){ if(st.diedAt>0)return'Sealed'; const m=env.m;
if(env.ageF>0.85)return'Senescent'; if(m<0.10)return'Shoot'; if(m<0.4)return'Young culm';
if(m<0.7)return'Culm'; return'Clump'; },
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 bambooDNA(seed){ const dd=rng('dna',seed), g=()=>(dd()+dd()+dd()-1.5)/1.5;
return { clumpiness:0.7+0.8*dd(), height:0.84+0.34*dd(), sway:0.5+1.0*dd(), swayFreq:1.2+1.6*dd(), arch:0.4+1.1*dd(),
nodeLen:34+26*dd(), thickness:0.6+0.7*dd(), leafiness:0.6+0.8*dd(), lean:g()*0.10, flowering:dd()<0.13,
windDir:dd()<0.5?1:-1, windStr:Math.pow(dd(),2.4) }; }
function paintBamboo(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=bambooDNA(seed), yellowF=clamp(Math.max(sen*1.1,(1-v)*0.55),0,1);
const flowering=D.flowering && (sen>0.12 || m>0.94), flowerPhase=flowering? smooth(0.12,0.62,Math.max(sen,(m-0.94)*4)) : 0;
const culmC=FC(mix(mix(P.culm,P.culmGold,yellowF*0.7), P.dead, sen*0.5)), culmDk=FC(mix(mix(P.culmDk,P.culmGold,yellowF*0.6), P.deadDk, sen*0.5)), nodeC=FC(mix(P.node, P.deadDk, sen*0.5));
const leafC=FC(mix(P.leaf, P.leafYellow, yellowF)), leafD=FC(mix(P.leafDk, P.leafYellow, yellowF*0.8)), leafL=FC(mix(P.leafLt, P.leafYellow, yellowF));
const windLean=D.windDir*D.windStr*0.34, rootX=cx - D.windDir*D.windStr*40*S, cy=by-4;
function lanceLeaf(x, y, ang, len, wide, rs){ const steps=Math.max(2,Math.ceil(len/GRID)); let dir=ang, pos=[x,y];
for(let s=1;s<=steps;s++){ const u=s/steps; dir += angTo(dir,DOWN)*(0.05+0.05*yellowF)*u; const cd=[Math.cos(dir),Math.sin(dir)];
pos=[pos[0]+cd[0]*GRID, pos[1]+cd[1]*GRID]; const px=-cd[1], py=cd[0], hw=Math.pow(Math.sin(Math.PI*Math.min(u,0.999)),0.6)*wide, sym=dirSym(cd[0],cd[1]);
for(let w=-hw;w<=hw;w+=GRID){ const edge=Math.abs(w)>hw-GRID*0.9, col= edge?shade(leafD,rs) : Math.abs(w)<GRID*0.6?shade(leafL,rs):shade(leafC,rs);
C.push(pos[0]+px*w, pos[1]+py*w, 60, sym, col, 'leaves'); } } }
function leafSpray(x, y, side, size, rs){ const n=1+Math.floor(rs()*(1+D.leafiness*2)), bx0=side>0?-0.55:Math.PI+0.55; let bx=x, by2=y, bdir=bx0; const bsteps=2+Math.floor(rs()*2);
for(let b=1;b<=bsteps;b++){ bdir+=angTo(bdir,UP)*0.12; bx+=Math.cos(bdir)*GRID; by2+=Math.sin(bdir)*GRID; C.push(bx,by2,58,dirSym(Math.cos(bdir),Math.sin(bdir)),shade(culmDk,rs),'leaves'); }
for(let k=0;k<n;k++){ const ang=bx0 + (k-(n-1)/2)*0.4 + (rs()*2-1)*0.12 - 0.1; lanceLeaf(bx, by2, ang, size*(0.8+0.5*rs()), (0.85+0.6*D.leafiness)*(0.8+0.3*rs())*Math.sqrt(S), rs); } }
function flowerPlume(x, y, side, rs){ let dir=(side>0?0.15:Math.PI-0.15), pos=[x,y]; const steps=7+Math.floor(rs()*7);
for(let s=1;s<=steps;s++){ const u=s/steps; dir += angTo(dir,DOWN)*(0.16+0.12*u);