memoscan
← all memos

Memo 0x3b7020ad…87b6bc on Ethereum

, 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; if(edge && rng('t',seed,i,s)()<tornF) continue; // torn margin (scar) const vein = !edge && !midrib && (s%5===0); let col = edge?P.leafDk : midrib?colMid : (w<0?P.leaf:P.leafLt); if(vein) col=mix(col,P.leafDk,0.45); PUSH(pos[0]+px*w, pos[1]+py*w, z, midrib?'|':(edge?dirSym(dx,dy):(vein?dirSym(px,py):dirSym(dx,dy))), tone(col), 'leaves'); } } } } // ---- the CRANE FLOWER: spathe beak + orange sepal crest + blue tongue ---- function blade(ox,oy,ang,len,wBase,cTip,cBody,z,rs){ // tapering pointed petal / flame const px=-Math.sin(ang), py=Math.cos(ang); let p=[ox,oy]; const tn=Math.max(2,Math.ceil(len/GRID)); for(let t=1;t<=tn;t++){ p=[p[0]+Math.cos(ang)*GRID, p[1]+Math.sin(ang)*GRID]; const u=t/tn, hw=wBase*(1-u*0.9); for(let w=-hw;w<=hw;w+=GRID) PUSH(p[0]+px*w, p[1]+py*w, z, u>0.82?'.':dirSym(Math.cos(ang),Math.sin(ang)), shade(FC(u>0.68?cTip:cBody),rs),'flowers'); } } function drawBud(jx,jy,fd,R,rs){ // unopened pointed sheath spear (maroon base → green) const a=fd>0?-0.5:Math.PI+0.5, px=-Math.sin(a), py=Math.cos(a), len=R*1.9, tn=Math.ceil(len/GRID); let p=[jx,jy]; for(let t=1;t<=tn;t++){ p=[p[0]+Math.cos(a)*GRID,p[1]+Math.sin(a)*GRID]; const u=t/tn, hw=GRID*S*(2.1*Math.sin(Math.PI*clamp(u*0.78+0.12,0,1))); for(let w=-hw;w<=hw;w+=GRID){ const edge=Math.abs(w)>hw-GRID; const col=u<0.34?mix(P.spatheDk,P.spathe,u/0.34):(edge?P.leafDk:P.spathe); PUSH(p[0]+px*w,p[1]+py*w,82, edge?dirSym(Math.cos(a),Math.sin(a)):'.', shade(FC(mix(col,'#7a5a30',dry*0.5)),rs),'flowers'); } } } function drawCrane(jx,jy,fd,R,open,rs){ // beak spathe + upward orange flames + blue arrow const ba=fd>0?0.30:Math.PI-0.30, bdx=Math.cos(ba), bdy=Math.sin(ba), beak=R*(1.4+0.7*open), bpx=-Math.sin(ba), bpy=Math.cos(ba); for(let i=1;i<=Math.ceil(beak/GRID);i++){ const t=i/Math.ceil(beak/GRID), bx=jx+bdx*t*beak, by=jy+bdy*t*beak; const hh=GRID*S*(1.0+2.2*Math.sin(Math.PI*clamp(t*0.8+0.18,0,1))); for(let w=-hh;w<=hh;w+=GRID){ const edge=Math.abs(w)>hh-GRID*1.1; PUSH(bx+bpx*w, by+bpy*w, 80, edge?dirSym(bdx,bdy):'.', shade(FC(mix(edge?P.spatheDk:mix(P.spathe,P.spatheDk,0.25), '#7a5a30', dry*0.75)),rs),'flowers'); } } // boat spathe browns + persists as the husk // crest: bright orange flames when alive → a couple of short brown withered nubs once dried const withered=dry>0.6, ns=withered?2:3+Math.round(open*1.5); const flameTip =mix(P.orange,'#7a5a30', withered?0.9:dry*0.6); const flameBody=mix(mix(P.orange,P.orangeDk,0.35),'#7a5a30', withered?0.82:dry*0.35); for(let k=0;k<ns;k++) blade(jx, jy, UP+(k-(ns-1)/2)*0.24+fd*0.06, R*(0.7+1.0*open)*(1+0.22*(k%2))*(withered?0.45:1), GRID*1.3*S, flameTip, flameBody, 90, rs); if(open>0.5 && !withered) blade(jx+fd*GRID*S, jy-GRID*0.5, fd>0?-0.55:Math.PI+0.55, R*1.3*open, GRID*1.5*S, P.blueLt, P.blue, 91, rs); // blue tongue — only on a live open crane } // ---- BLOOM: cranes spread ACROSS the clump on tall ARCHING stalks of varied height ---- // The flower STRUCTURE (woody stalk + boat spathe) PERSISTS into death as a dried husk — it // does NOT vanish (spec death state = "dried spathe husk"). So the stalk presence is gated on // maturity+reward but NOT on (1-sen); only the live COLOUR (open orange crest + blue tongue) // fades with senescence/season and finally withers to brown. const seasonOK=(season==='Winter'||season==='Spring')?1:0.7; const earned = m>=0.55 ? smooth(0.05,0.55,rewards) : 0; // did it earn cranes? const standDrive = Math.max(earned*(1-winter*0.7), dead&&m>=0.55 ? 0.55 : 0); // a mature plant that died keeps standing dried stalks const liveBloom = earned*(1-sen)*(1-winter)*seasonOK; // colourful open cranes (alive + in season) if(standDrive>0.10){ const nb=clamp(1+Math.round(standDrive*2*(0.5+0.5*D.nFlowers/3)), 1, 3); for(let b=0;b<nb;b++){ const rs=rng('cr',seed,b), hr=rng('h',seed,b)(); const spread = nb>1 ? (b-(nb-1)/2)/((nb-1)/2) : 0; // -1..1 across the clump const leanDir = spread!==0 ? (spread>0?1:-1) : D.bloomSide; // outer cranes lean outward; solo leans bloomSide const bx = cx + (spread*(42+34*m) + D.fanLean*30)*S; // wider → cranes clearly off-centre, not all from the middle const open = clamp(liveBloom*1.6 - b*0.45, 0, 1); // older cranes open; newer still budding; 0 once dried const sh = (215+320*m)*(0.82+0.18*v)*(1 - D.heightVar*0.45 + D.heightVar*hr)*S; // taller → cranes ride above the fan so the arch shows; strong height variation const curl = D.stalkCurl*(0.5+0.7*Math.abs(spread))*(1+dry*0.5); // outer stalks arch more; dried stalks sag/arch further let p=[bx, cy], dir=UP + leanDir*D.stalkLean*0.4 + D.fanLean*0.5; const steps=Math.ceil(sh/GRID); const stalkCol=mix(P.stalk,'#6b4a2a',dry*0.85); // green stalk → brown dried husk for(let s=1;s<=steps;s++){ const u=s/steps; dir += leanDir*curl*0.013*smooth(0.15,1,u); // progressive arch toward the top (not a straight pole) p=[p[0]+Math.cos(dir)*GRID, p[1]+Math.sin(dir)*GRID]; for(let w=-GRID*0.8;w<=GRID*0.8;w+=GRID) PUSH(p[0]+w,p[1],50,'|',shade(FC(stalkCol),rs),'stem'); } const R=(24+22*m)*(0.85+0.15*v)*S; // dried → the empty boat spathe stands (browned crest); young & living → unopened spear bud; else open crane if(dry>0.5) drawCrane(p[0],p[1],leanDir,R, clamp(open,0.28,0.5), rs); else if(open<0.4) drawBud(p[0],p[1],leanDir,R,rs); else drawCrane(p[0],p[1],leanDir,R,open,rs); } } } // ===================== background CLUMP (varied size, scattered) + sharp hero ===================== // Solo → 6-strong clump, companions SCATTERED across the canvas with per-seed jitter and INDEPENDENT // depth (size/fade/height NOT tied to draw order) → no two seeds share a layout, no rigid L/R flanking. const roll=rng('setting',st.seed)(); const nC = roll<0.20?0 : roll<0.44?1 : roll<0.66?2 : roll<0.84?3 : roll<0.95?4 : 5; // 0..5 companions const comps=[]; for(let i=0;i<nC;i++){ const rc=rng('bcomp',st.seed,i); const frac = nC>1 ? (i+0.5)/nC : 0.5; // spread across the width… const slot = clamp(frac + (rc()*2-1)*(0.85/nC), 0.05, 0.95); // …+ strong jitter → unpredictable, not rigid L/R const cxP = lerp(FRAME+85, W-FRAME-85, slot); const depth = clamp(0.10 + 0.82*rng('bdep',st.seed,i)(), 0, 1); // INDEPENDENT of index → organic size/fade/height const birthM = 0.06 + rc()*0.42; // randomized emergence (grows in from a sprout, no pop) const cm = clamp((m-birthM)/0.46, 0, 1); if(cm<=0.03) continue; const cseed='0x'+(hashStr(st.seed+'|b'+i)>>>0).toString(16); comps.push({ seed:cseed, artSeed:cseed, cx: cxP, cy: baseY-4 - (6 + depth*42 + rc()*14), // deeper (far) plants sit higher → toward the horizon S: lerp(0.60,0.34,depth)*(0.88+0.22*rc()), dim: lerp(0.38,0.66,depth), zb: -110 - Math.round(depth*60), // always behind the hero; far ones furthest back m: cm, v, sen, browny, rewards: clamp(rewards*(0.82+0.32*rc()),0,1), density, dead, depth }); } comps.sort((a,b)=>b.depth-a.depth).forEach(paintBird); // far (faint) → near paintBird({ seed:st.seed, artSeed:st.artSeed, cx:baseX, cy:baseY-4, S:1, dim:0, zb:0, m, v, sen, browny, rewards, density, dead }); }, traits(st,env){ const roll=rng('setting',st.seed)(); const total = 1 + (roll<0.20?0 : roll<0.44?1 : roll<0.66?2 : roll<0.84?3 : roll<0.95?4 : 5); const setting = total===1?'Solo' : total===2?'Pair' : total===3?'Trio' : 'Clump'; const D=(function(seed){ const dd=rng('dna',seed); dd();dd();dd();dd();dd(); const nF=1+Math.floor(dd()*3); const side=dd()<0.5?1:-1; return {nFlowers:nF, bloomSide:side}; })(st.seed); return [ ['Leaves', clamp(Math.round(3+env.density*4),3,7)], ['Bloom', st.diedAt>0?'Dried husk':(env.m>=0.55&&env.rewards>0.3?'Crane':'—')], ['Faces', D.bloomSide>0?'East':'West'], ['Setting', setting], ]; } })); })(); ; /* species/guava.js */ /* guava — species index 8. GENERATED from prototypes/chilean-guava.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(8, spec); /* Chilean Guava — Ugni molinae. A compact upright oval myrtle SHRUB: a recursive staggered-lateral framework clothed in distinct rounded FOLIAGE CLUMPS with branches showing between them. Reward: white powderpuff FLOWERS (late spring/summer) → ruby BERRIES ripening green→red→purple (autumn/ winter). Evergreen, then drops to bare twigs in old age. Grows in thickets/hedges. Ported to core. */ const SP = window.SEEDLING_SPECIES.find(x=>x.key==='guava'); mount(Object.assign(window.SEEDLING_toSpec(SP), { title:'Chilean Guava', sub:'ugni v2', senFrom:0.82, LW:{season:1.0, stem:1.15, leaves:1.0, flowers:1.5}, LS:{season:1.0, stem:1.0, leaves:0.85, flowers:0.85}, note:'Compact upright myrtle shrub · rounded foliage clumps with twigs showing between · white powderpuff flowers → ruby berries (green→red→purple) · evergreen → bare in old age · thickets.', PAL:{ soil:'#6f5128', grey:'#9a8f74', bark:'#6E5B46', barkDk:'#46382A', leaf:'#2F5E36', leafDk:'#1C3F22', leafLt:'#5C8C4C', leafYoung:'#9A6A4E', leafYellow:'#C2A848', petal:'#F5ECEE', stamen:'#E0668A', stamenDk:'#C23A66', anther:'#E8A82C', berryGreen:'#7FA84E', berryRed:'#C2384C', berryPurple:'#5A2746', berryRipe:'#3A1730', calyx:'#7E8A4A' }, stageName(st,env){ if(st.diedAt>0)return'Sealed'; const m=env.m, season=env.season; if(env.ageF>0.84)return'Dormant'; if(m<0.12)return'Sprig'; if(m<0.34)return'Seedling'; if(m<0.55)return'Bush'; const fruity=(season==='Autumn'||season==='Winter'), flowery=(season==='Spring'||season==='Summer'); if(m<0.62)return'Mature'; return fruity?'Fruiting':(flowery?'Flowering':'Dense'); }, 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 guavaDNA(seed){ const dd=rng('dna',seed), g=()=>(dd()+dd()+dd()-1.5)/1.5; return { dominance:0.46+0.4*dd(), divergence:0.5+0.4*dd(), droop:0.12+0.28*dd(), gnarl:0.3+0.5*dd(), taperLen:0.68+0.12*dd(), lateralN:1.3+0.9*dd(), reach:0.5+0.32*dd(), lean:g()*0.09, stature:0.82+0.2*dd(), maxOrder:4+(dd()<0.5?1:0), leafiness:0.95+0.45*dd(), flowerAb:0.5+0.6*dd(), fruitAb:0.5+0.6*dd(), windDir:dd()<0.5?1:-1, windStr:Math.pow(dd(),2.6) }; } function paintGuava(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=guavaDNA(seed), yellowF=clamp(Math.max(sen*0.9,(1-v)*0.5),0,1); const seasonBloom = season==='Spring'?1 : season==='Summer'?0.85 : season==='Winter'?0.15 : 0.5; const seasonRipe = season==='Winter'?1 : season==='Autumn'?0.8 : season==='Summer'?0.35 : 0.12; const foliageF=clamp((1-sen*0.92)*(0.5+0.5*v),0,1); const bloom=smooth(0.44,0.66,