0xffd6563c…95fesent to0x72842d09…a7a6·#25,495,191·view on Etherscan
(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); pos=[pos[0]+Math.cos(dir)*GRID, pos[1]+Math.sin(dir)*GRID];
C.push(pos[0],pos[1], 90, dirSym(Math.cos(dir),Math.sin(dir)), shade(FC(P.stamenDk),rs), 'flowers');
if(s%2===0){ if(flowerPhase<0.5){ C.push(pos[0],pos[1]+GRID*0.9, 91,'|', shade(FC(P.stamen),rs), 'flowers'); C.push(pos[0],pos[1]+GRID*1.8, 92,'.', shade(FC(P.stamen),rs), 'flowers'); }
else { const cc=FC(mix(P.seed,P.seedDk,flowerPhase*0.6)); C.push(pos[0],pos[1]+GRID*0.8, 91,'O', shade(cc,rs), 'flowers'); C.push(pos[0],pos[1]+GRID*1.5, 91,'.', shade(cc,rs), 'flowers'); } } } }
function drawCulm(sx, lean, hScale, emerge, ci){ const rs=rng('culm',seed,ci), grow=smooth(0,1,emerge), Hc=(245+520*m)*D.height*hScale*grow*(0.9+0.1*v)*S; if(Hc<GRID*4) return;
const steps=Math.ceil(Hc/GRID), cm=clamp(m*(0.4+0.6*grow),0,1), sheathTop=clamp(1.12 - cm*2.7, 0, 1.0), archDir=(Math.abs(lean)>0.01? Math.sign(lean) : (rs()<0.5?1:-1));
const swayPh=rs()*6.28, swayAmp=D.sway*(0.7+0.6*rs()), baseThick=(1.5+D.thickness*3.4)*grow*(0.7+0.3*v)*(0.8+0.2*hScale)*Math.sqrt(S), internode=D.nodeLen*(0.85+0.3*rs())*S;
let dir=UP+lean+windLean*0.5, pos=[sx,cy], run=internode*0.55; const upperNodes=[];
for(let s=1;s<=steps;s++){ const u=s/steps, exposure=clamp((cy-pos[1])/680,0,1);
dir += Math.sin(u*Math.PI*D.swayFreq + swayPh)*swayAmp*0.011 + windLean*0.05*exposure;
if(u>0.5) dir += archDir*D.arch*0.018*(u-0.5)*(0.5+0.6*cm);
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], sym=dirSym(cd[0],cd[1]);
const sheathed = u<sheathTop ? clamp((sheathTop-u)/0.14,0,1) : 0, hwCulm=Math.max(0.6, baseThick*(1-0.4*u)), hwShoot=baseThick*2.0*clamp((sheathTop-u)/Math.max(sheathTop,0.01),0.1,1), hw=sheathed>0 ? lerp(hwCulm,hwShoot,sheathed) : hwCulm;
run+=GRID; let isNode=false; if(run>=internode){ run=0; isNode=true; }
for(let w=-hw;w<=hw;w+=GRID){ const a=Math.abs(w)/Math.max(hw,1), edge=Math.abs(w)>hw-GRID*1.0;
if(sheathed>0.4){ const col= edge?shade(FC(P.sheathDk),rs):(a<0.4?shade(FC(mix(P.sheath,'#ffffff',0.08)),rs):shade(FC(P.sheath),rs)); C.push(pos[0]+px*w, pos[1]+py*w, 50, w<0?'/':'\\', mix(col, FC(P.dead), sen*0.5), 'stem'); }
else { const col= edge?shade(culmDk,rs):(a<0.34?shade(mix(culmC,'#ffffff',0.07),rs):shade(culmC,rs)); C.push(pos[0]+px*w, pos[1]+py*w, 50, sym, col, 'stem'); } }
if(isNode){ const nhw=hw+GRID*1.1; for(let w=-nhw;w<=nhw;w+=GRID) C.push(pos[0]+px*w, pos[1]+py*w, 54, sheathed>0.4?(w<0?'/':'\\'):'-', shade(sheathed>0.4?FC(P.sheathDk):nodeC,rs), 'stem');
if(sheathed<0.3){ const rx=pos[0]-cd[0]*GRID, ry=pos[1]-cd[1]*GRID; for(let w=-hw;w<=hw;w+=GRID) C.push(rx+px*w, ry+py*w, 53, '-', shade(mix(culmC,'#ffffff',0.2),rs), 'stem'); }
if(u>0.42 && sheathed<0.3) upperNodes.push([pos[0],pos[1],u]); }
}
for(const [nx,ny,u] of upperNodes){ const nr=rng('node',seed,ci*131+Math.round(u*100)), leafSize=(32+54*m)*(0.72+0.4*hScale)*S;
if(flowering && u>0.55){ flowerPlume(nx,ny, nr()<0.5?1:-1, nr); if(nr()<0.6) flowerPlume(nx,ny, nr()<0.5?-1:1, nr); if(nr()<0.35) leafSpray(nx,ny, nr()<0.5?1:-1, leafSize*0.8, nr); }
else { leafSpray(nx,ny, 1, leafSize, nr); if(nr()<0.7) leafSpray(nx,ny,-1, leafSize*0.92, nr); } }
}
const nCulmF=clamp(1 + density*4.2*D.clumpiness + (rng('nc',seed)()*2-1)*0.5, 1, 5.4), nCulm=Math.ceil(nCulmF);
for(let i=0;i<nCulm;i++){ const emerge=i===nCulm-1?clamp(nCulmF-(nCulm-1),0.05,1):1, pairIdx=Math.ceil(i/2), side=(i%2?1:-1);
const lean=(i===0?0:side*pairIdx*0.085) + D.lean, hScale=(i===0?1:1-pairIdx*0.12)*(0.9+0.2*rng('hs',seed,i)()), sx=rootX + (i===0?0:side*pairIdx*22*S) + (rng('sx',seed,i)()*2-1)*7;
drawCulm(sx, lean, hScale, emerge, i); }
}
// ===================== SETTING: Solo / Pair / Grove / Thicket / rare Forest =====================
const roll=rng('setting',st.seed)();
const nC = roll<0.18?0 : roll<0.46?1 : roll<0.74?2 : roll<0.91?3+Math.floor(rng('fn',st.seed)()*2) : 7+Math.floor(rng('fn',st.seed)()*5);
const forest=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(forest){ 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.62,0.34,depth)*(0.9+0.2*rc()); raise=(depth*150+rc()*26); fade=lerp(0.44,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.46,f)*(0.9+0.2*rc()); off=side*(96+i*54+rc()*40); raise=(26+i*18+rc()*26); fade=lerp(0.40,0.60,f); }
comps.push({seed:cseed, artSeed:cseed, cx:baseX+off, by:baseY-raise, S:scale, fade, m, density:clamp(density+(rc()*2-1)*(forest?0.16:0.12),0.04,1), v, sen, browny, rewards, depth}); }
comps.sort((a,b)=>b.depth-a.depth).forEach(paintBamboo);
paintBamboo({seed:st.seed, artSeed:st.artSeed, cx:baseX, by:baseY, S:1.16, 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.46?'Pair' : roll<0.74?'Grove' : roll<0.91?'Thicket':'Forest';
const dd=rng('dna',st.seed); for(let i=0;i<9;i++)dd(); const flowering=dd()<0.13;
return [
['Setting', setting],
['Culms', Math.max(1,Math.ceil(clamp(1+env.density*4.2,1,5)))],
['Flowering', flowering?'Gregarious':'—'],
];
}
}));
})();
;
/* species/bird.js */
/* bird — species index 7. GENERATED from prototypes/bird-of-paradise.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(7, spec);
/* Bird of Paradise — Strelitzia reginae.
A clumping distichous FAN of large oblong paddle leaves on long petioles, and the
iconic CRANE FLOWER: a horizontal boat-shaped spathe (the "beak"), a crest of
pointed ORANGE sepals spraying up, and a BLUE arrow petal (the "tongue") jutting
forward. Cranes ride tall ARCHING stalks that emerge across the clump (not all from
the middle) at varied heights, each facing outward. Per-seed personality (fan spread,
lean, bloom side, stalk curl). Grows in clumps — faded companions stand behind. */
const SP = window.SEEDLING_SPECIES.find(x=>x.key==='bird');
mount(Object.assign(window.SEEDLING_toSpec(SP), {
title:'Bird of Paradise', sub:'strelitzia v2',
senFrom:0.82,
note:'Distichous paddle-leaf fan · crane-flowers (orange crest + blue tongue from a beak spathe) on tall arching stalks across the clump · bird-pollinated · grows in clumps.',
PAL:{ soil:'#5a4a2e',
leaf:'#2E5A3C', leafDk:'#1d3d28', leafLt:'#4E7A52',
spathe:'#4a6b3a', spatheDk:'#6b3b2a',
orange:'#F28C28', orangeDk:'#C4641A', blue:'#2B4C9B', blueLt:'#3f63b8',
stalk:'#3a5a3c' },
stageName(st,env){ if(st.diedAt>0)return'Husk'; const m=env.m;
if(m<0.15)return'Sprout'; if(m<0.5)return'Fan'; if(m<0.66)return'Budding'; return'Crane'; },
draw(st, C, env){
const {m,v,sen,season,browny,density,rewards,reproOK,dormancy,dead,baseX,baseY}=env, P=this.PAL;
const bgC = BACKDROPS[backdropFor(st.seed)];
const winter=dormancy||0;
function birdDNA(seed){ const dd=rng('dna',seed), g=()=>(dd()+dd()+dd()-1.5)/1.5;
return { startSide:dd()<0.5?1:-1, // the fan leads LEFT or RIGHT (was always left)
fanSpread:0.06+0.34*dd(), // tight upright clump (filled centre) → wide splayed fan
fanAsym:g()*0.55, // lopsided: one side opens wider than the other
fanLean:g()*0.20, leafiness:0.85+0.45*dd(),
nFlowers:1+Math.floor(dd()*3), bloomSide:dd()<0.5?1:-1, stalkCurl:0.5+0.7*dd(),
stalkLean:0.10+0.22*dd(), heightVar:0.3+0.5*dd() }; }
// ===================== one clump (hero or faded background companion) =====================
function paintBird(I){
const {seed, artSeed, cx, cy, S, dim, zb, m, v, sen, browny, rewards, density, dead} = I;
const FC = c => dim>0 ? mix(c, bgC, dim) : c;
const PUSH = (x,y,z,sym,col,layer)=>C.push(x,y,z+zb,sym,col,layer);
const D = birdDNA(seed);
const dry=Math.max(sen,winter*0.5);
// ---- distichous FAN of oblong paddle leaves; each leaf GROWS IN (no pop) ----
const colMid=mix(P.leafLt,'#e7efcb',0.5);
const nF=clamp(3.4 + density*4*D.leafiness, 3, 7);
const tornF=clamp(browny*0.7 + sen*0.5, 0, 0.8);
for(let i=0;i<7;i++){ const rs=rng('leaf',seed,i);
const em=clamp(nF-i, 0, 1); if(em<=0.02) continue; // newest leaf eases in from 0
const side=(i%2?1:-1)*D.startSide, rank=Math.floor(i/2); // fan leads either side per seed
const sideSpread=D.fanSpread*(1 + D.fanAsym*side); // asymmetric → not a clean symmetric V
const ang=UP + side*(sideSpread+0.13*rank) + D.fanLean + (rs()*2-1)*0.13; // stronger per-leaf jitter
const dx=Math.cos(ang), dy=Math.sin(ang), px=-dy, py=dx;
const plen=(150+330*m)*em*(0.82+0.30*rs())*(1-0.16*winter)*S; // more leaf-length variation
const z=44+i;
const tone=c=>shade(FC(mix(mix(c,'#6b5a2e',winter*0.4),'#33301f',browny*0.7)),rs);
const steps=Math.max(3,Math.ceil(plen/GRID)); let pos=[cx + side*(7+11*rank)*S + (rs()*2-1)*5*S, cy];
for(let s=1;s<=steps;s++){ const u=s/steps; pos=[pos[0]+dx*GRID, pos[1]+dy*GRID];
if(u<0.40){ for(let w=-GRID*0.8;w<=GRID*0.8;w+=GRID) PUSH(pos[0]+px*w,pos[1]+py*w,z,dirSym(dx,dy),tone(Math.abs(w)<GRID?colMid:P.leaf),'leaves'); } // pale petiole
else { const bu=(u-0.40)/0.60, hw=GRID*(0.5+6.8*Math.pow(Math.sin(Math.PI*bu),0.62))*S; // broad oblong, rounded point
for(let w=-hw;w<=hw;w+=GRID){ const aw=Math.abs(w), edge=aw>hw-GRID*1.4, midrib=aw<GRID*1.0;