memoscan
← all memos

Memo 0xe659666a…8025d1 on Ethereum

i<=sst;i++){ const u=i/sst; d += angTo(d,UP)*0.16; sp=[sp[0]+Math.cos(d)*GRID, sp[1]+Math.sin(d)*GRID]; const taper=Math.min(1,u*4)*(1-Math.pow(u,1.7)), hw=sW*taper, px=-Math.sin(d), py=Math.cos(d); for(let w=-hw; w<=hw; w+=GRID){ if(rs()<0.12) continue; const fx=sp[0]+px*w, fy=sp[1]+py*w, localOpen=clamp(open*(1.25-u),0,1), isOpen=rs()<localOpen*0.85; const col = isOpen ? shade(mix(cFlower, cFlowerLt, rs()*0.7), rs) : shade(mix(cBud, cFlowerDk, localOpen), rs); const sym = isOpen ? (rs()<0.5?'+':'X') : (rs()<0.6?'.':'O'); C.push(fx, fy, flZ, sym, col, 'flowers'); } } } } } // ===================== SETTING: a patch — Solo / Pair / Cluster / Field ===================== const sr=rng('setting',st.seed), roll=sr(); const nC = roll<0.18?0 : roll<0.50?1 : roll<0.82?2 : 3+Math.floor(rng('fn',st.seed)()*2); const comps=[]; for(let i=0;i<nC;i++){ const rc=rng('comp',st.seed,i); const side=(i%2===0?1:-1), f=i/Math.max(1,nC); const scale=lerp(0.72,0.46,f)*(0.9+0.2*rc()), off=side*(80+i*46+rc()*46), raise=(34+i*24+rc()*30); comps.push({cx:baseX+off, by:baseY-raise, S:scale, fade:lerp(0.5,0.74,f), m, v, sen, browny, density:clamp(density+(rc()*2-1)*0.12,0.05,1), rewards, zb:-10, pseed:'c'+i, depth:f}); } comps.sort((a,b)=>b.depth-a.depth).forEach(paintBush); // far (faint) → near paintBush({cx:baseX, by:baseY, S:1.0, fade:0, m, v, sen, browny, density, rewards, zb:0, pseed:'hero'}); }, traits(st,env){ const sr=rng('setting',st.seed), roll=sr(); const setting = roll<0.18?'Solo' : roll<0.50?'Pair' : roll<0.82?'Cluster' : 'Field'; return [ ['Setting', setting], ['Stems', clamp(Math.round(1+env.density*14),1,15)], ['Stature', Math.round((110+230*env.m)*(0.7+0.3*env.v))+' px'], ]; } })); })(); ; /* species/lotus.js */ /* lotus — species index 10. GENERATED from prototypes/sacred-lotus.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(10, spec); /* Sacred Lotus — Nelumbo nucifera. An aquatic plant: large peltate pads with a WAVY / undulate margin and radial veins, held on SHORT petioles low over the water (heavy leaves don't reach far up; the newest leaf is a small cupped emerging blade), and a solitary red-pink BLOOM raised above the foliage on its own taller peduncle — broad layered petals around a golden receptacle with a fringe of stamens. Lifecycle on the peduncle: nodding BUD → open BLOOM → green POD → dry "showerhead" seed pod. Lotuses often grow in a stand: a seed-driven COLONY renders 1 → many plants (solo to a pond bunch). Summer bloom, winter pad-dieback. */ const SP = window.SEEDLING_SPECIES.find(x=>x.key==='lotus'); mount(Object.assign(window.SEEDLING_toSpec(SP), { title:'Sacred Lotus', sub:'nelumbo v4', senFrom:0.82, note:'Aquatic · big wavy pads low on the water, side-view cup blooms (pointed petals + golden stamen crown) on stems of varied height → showerhead seed pod · flower varieties Pink / White / Rose · coherent WIND bends the stalks downwind · 1–5 flowers per root, spread both sides · solo to a full pond bunch scattered across the water.', PAL:{ soil:'#6a7a5a', stalk:'#6E9A6B', stalkDk:'#4C7754', speck:'#365E3C', pad:'#6E9A5E', padDk:'#3E6B43', padLt:'#9CC07E', padVein:'#33572F', padEdge:'#274A29', padYellow:'#C7A24E', petal:'#E0796E', petalLt:'#F7D6CE', petalDk:'#C53A30', petalVein:'#962017', center:'#E8B23A', centerDk:'#B07A1E', stamen:'#EFC85C', pod:'#7E9A55', podRim:'#5A7A3C', podDry:'#8A714A', podDryDk:'#5A4730', pit:'#3E2E1A' }, stageName(st,env){ if(st.diedAt>0)return'Dry pod'; const m=env.m; if(m<0.15)return'Sprout'; if(m<0.50)return'Floating pads'; if(m<0.72)return'Budding'; return'Bloom'; }, draw(st, C, env){ const {m,v,sen,season,browny,density,rewards,reproOK,dormancy,dead,baseX,baseY}=env, P=this.PAL; const winter=dormancy||0; const bgC = BACKDROPS[backdropFor(st.seed)]; const waterY = baseY - 8; // plants root at the bottom (like the other species), grow UP from here const seasonBloom = season==='Summer'?1 : season==='Spring'?0.7 : season==='Autumn'?0.45 : 0.30; // monsoon bias, floored // per-seed POND environment, shared by every plant in the colony const windDir = rng('windD',st.seed)()<0.5?-1:1; const windStr = Math.pow(rng('windS',st.seed)(),2.6); // 0..1, skewed LOW → mostly calm, rarely a storm function varietyPal(key){ const x=rng('variety',st.seed,key)(); // per-PLANT flower colour (so a pond can mix pink + white) if(x<0.42) return {name:'Pink', lt:'#F7D6CE', pt:'#E0796E', dk:'#C53A30', vn:'#962017'}; if(x<0.80) return {name:'White', lt:'#FFFFFF', pt:'#F6F1EA', dk:'#EBA48C', vn:'#C56A50'}; // white petals, soft rose-flushed tips + rim (reads white & pops on cream) return {name:'Rose', lt:'#F3C4D4', pt:'#D85A7A', dk:'#A82A52', vn:'#7C1B3C'}; } // ===================== one lotus plant (repeated to form a colony) ===================== function paintLotus(I){ const {cx, by, S, fade, m, v, sen, density, rewards, reproOK, browny, dead, pseed} = I; const FC = c => fade>0 ? mix(c, bgC, fade) : c; const PV = varietyPal(pseed); // this plant's petal palette (Pink / White / Rose) const collapse = clamp(1-0.45*sen-0.42*winter, 0.18, 1); // pads sink & yellow as they die back const standC = clamp(1-0.12*sen-0.42*winter, 0.55, 1); // woody flower/pod stalks stay STANDING const bloomDrive = reproOK ? clamp(rewards*(0.55+0.45*seasonBloom)*(1-sen),0,1) : 0; const podDrive = reproOK ? clamp(sen*1.2 + (season==='Autumn'?0.35:0) + smooth(0.82,1.0,m)*0.5, 0,1) : 0; // -- petiole / peduncle: erect at the base, GENTLY arcing toward `lean` near the top, with a // faint sway — so stems read natural (not ramrod-straight parallel poles). -- function rise(x0,y0,h,lean,z,col,wideF,rs){ const steps=Math.max(2,Math.ceil(h/GRID)); let pos=[x0,y0], dir=UP; const sway=(rs()*2-1), swF=0.7+rs()*0.8; for(let s=1;s<=steps;s++){ const u=s/steps; const target=UP + lean*smooth(0,1,u); // straight low → curving over near the flower dir += angTo(dir, target)*0.08 + Math.sin(u*Math.PI*swF)*0.006*sway; pos=[pos[0]+Math.cos(dir)*GRID, pos[1]+Math.sin(dir)*GRID]; const px=-Math.sin(dir), py=Math.cos(dir); C.push(pos[0],pos[1],z,'|',shade(FC(col),rs),'stem'); if(wideF) C.push(pos[0]+px*GRID,pos[1]+py*GRID,z,'|',shade(FC(mix(col,P.stalkDk,0.35)),rs),'stem'); if(s%4===0) C.push(pos[0]+px*(rs()<0.5?GRID:-GRID), pos[1]+py*(rs()<0.5?GRID:-GRID), z,'.', shade(FC(P.speck),rs),'stem'); } return pos; } // -- WAVY peltate pad: undulate margin (not a clean circle) + radial veins + sinus notch -- function drawPad(px,py,R,z,yellowF,ragged,cupped,rs){ const sq = cupped? 0.60 : 0.84; const seam = rng('pr',Math.round(px),Math.round(py),pseed)()*TAU; const Rf = a => R*(1 + 0.14*Math.sin(7*a+seam) + 0.06*Math.sin(3*a-seam*1.3)); // undulate edge const notch = seam + DOWN; // sinus toward the petiole const tone = c => shade(FC(mix(c,P.padYellow,yellowF)),rs); for(let gx=-R*1.25;gx<=R*1.25;gx+=GRID)for(let gy=-R*sq*1.25;gy<=R*sq*1.25;gy+=GRID){ const a=Math.atan2(gy/sq,gx), rr=Math.hypot(gx,gy/sq), re=Rf(a); if(rr>re)continue; const rad=rr/re, cr=rng('pf',Math.round(px),Math.round(gx),Math.round(gy)); if(Math.abs(angTo(a,notch))<0.22 && rad>0.5) continue; // sinus cut if(cr()>0.78) continue; // airy stipple (ground shows through) if(rad>0.6 && ragged>0 && cr()<ragged) continue; // collapsing margin const sheen=Math.sin(a*3+seam)*0.5*(0.6-rad); const col=mix(mix(P.pad, sheen<0?P.padDk:P.padLt, Math.abs(sheen)), P.padDk, smooth(0.7,1.0,rad)*0.3 + (cupped&&gy>0?0.22:0)); C.push(px+gx,py+gy, z, dirSym(gx,gy), tone(col),'leaves'); } const nVein=cupped?9:13; for(let k=0;k<nVein;k++){ const a=seam + k/nVein*TAU; if(Math.abs(angTo(a,notch))<0.22) continue; const ca=Math.cos(a), sa=Math.sin(a)*sq, re=Rf(a); for(let t=R*0.16;t<=re*0.92;t+=GRID){ const gx=ca*t, gy=sa*t; if(ragged>0 && t>re*0.6 && rng('vg',Math.round(px),Math.round(gx),k)()<ragged) continue; C.push(px+gx, py+gy, z+0.6, dirSym(ca,sa), tone(P.padVein),'leaves'); } } for(let a=0;a<TAU;a+=0.045){ if(Math.abs(angTo(a,notch))<0.20) continue; const re=Rf(a), ca=Math.cos(a), sa=Math.sin(a)*sq; if(ragged>0 && rng('eg',Math.round(px),Math.round(a*30))()<ragged*0.8) continue; for(const rf of [0.99,0.92]) C.push(px+ca*re*rf, py+sa*re*rf, z+0.7, dirSym(ca,sa), tone(P.padEdge),'leaves'); } if(cupped) for(let a=Math.PI*1.04;a<=Math.PI*1.96;a+=0.05){ const re=Rf(a), ca=Math.cos(a), sa=Math.sin(a)*sq; // curled upper rim C.push(px+ca*re*0.85, py+sa*re*0.85, z+0.8, dirSym(ca,sa), tone(mix(P.padEdge,'#000000',0.18)),'leaves'); } C.push(px,py,z+0.9,'O',tone(P.padEdge),'leaves'); // peltate hub C.push(px,py,z+1.0,'.',tone(P.padEdge),'leaves'); } // -- one DISTINCT pointed lotus petal: almond shape, dark outline (so petals separate), // central vein, pale base → red tip. `ang` is an absolute direction; the petal grows // from a base point. Outline sits a touch higher z so it cuts over the petal behind. -- function petal(bx,by,ang,len,halfW,z,ez,pal,rs){ const nx=Math.cos(ang), ny=Math.sin(ang), px=-ny, py=nx, tn=Math.max(3,Math.ceil(len/GRID)); for(let t=1;t<=tn;t++){ const u=t/tn; const taper=1-smooth(0.5,1.0,u); // taper over the top half const hw=halfW*Math.min(1, 2.2*Math.sin(Math.PI*u*0.5))*taper*taper; // broad lower body → pointed lanceolate tip (lotus) if(hw<GRID*0.32 && u>0.5) continue; const ox=bx+nx*GRID*t, oy=by+ny*GRID*t; for(let w=-hw;w<=hw;w+=GRID){ const aw=Math.abs(w); const edge = aw>hw-GRID*1.5 || (u>0.78 && hw<halfW*0.5); // outline: sides + the pointed tip const vein = aw<GRID*0.7 && u<0.85; // central vein const grad = u<0.28?pal.base : u<0.62?mix(pal.base,pal.body,smooth(0.28,0.62,u)) : mix(pal.body,pal.tip,smooth(0.62,1,u)); const col = edge?pal.edge : vein?mix(pal.body,pal.edge,0.4) : grad; // outlines go to a HIGH shared z (ez) so EVERY petal boundary renders over the fills → petals read as separate C.push(ox+px*w, oy+py*w, edge?ez:z, '+', shade(FC(col),rs),'flowers'); } } } // a SIDE-VIEW lotus: an egg/goblet of pointed petals opening upward from the stem tip, // top petals longest (tall egg), reflexed lower petals, paler inner cup, gold stamen centre. function drawBloom(fx,fy,R,open,rs){ const PB ={base:PV.lt, body:mix(PV.pt,PV.lt,0.45), tip:PV.dk, edge:PV.vn}; // outer: pale body, saturated tip, dark rim (per variety) const PBi={base:'#ffffff', body:mix(PV.lt,'#ffffff',0.35), tip:PV.pt, edge:PV.dk}; // inner cup, paler // SIDE VIEW: the flower is a CUP / egg opening UPWARD. Petals fan UP & OUT from a NARROW base just // above the stem (which hides behind them) — NONE point straight down (that would be a top view). // A shorter, PALER FRONT whorl sits in front of the centre so we read the cup's DEPTH; the golden // stamens nestle LOW in the middle of the cup. Broad pointed petals overlap into a full flower. const fcx=fx, fcy=fy - R*0.14, ringR=R*0.15; const sp=lerp(1.04,1.38,open); const bN=7; for(let k=0;k<bN;k++){ const t=(k/(bN-1))*2-1, ang=UP+t*sp; // BACK whorl: tall outer-face cup-wall petals (behind) petal(fcx+Math.cos(ang)*ringR, fcy+Math.sin(ang)*ringR, ang, R*(1.10-0.16*Math.abs(t)), R*0.245, 66+Math.round(Math.abs(t)*2), 82, PB, rs); } const fN=6; for(let k=0;k<fN;k++){ const t=((k+0.5)/fN)*2-1, ang=UP+t*lerp(0.60,1.04,open); // FRONT whorl: shorter, PALER inner-face petals, offset between, in FRONT → cup depth petal(fcx+Math.cos(ang)*ringR*0.66, fcy+Math.sin(ang)*ringR*0.66, ang, R*(0.76-0.12*Math.abs(t)), R*0.215, 74+Math.round((1-Math.abs(t))*2), 81, PBi, rs); } // ---- the golden POLLEN crown in the MIDDLE: a ring of stamens (filament + anther) radiating // from a small central receptacle, surrounded by the petals (the lotus signature). ---- const ccx=fcx, ccy=fcy - R*0.30, baseR=R*0.075; // pollen crown LOW in the middle of the cup const nStam=Math.round(46*(0.6+0.4*open)); // dense → a fuzzy golden cluster, not a sparse star for(let k=0;k<nStam;k++){ const sr=rng('st',Math.round(fx),Math.round(fy),k); const a=(k/nStam)*TAU + (sr()*2-1)*0.22; const up=(-Math.sin(a)+1)/2; // 1 at top → 0 at bottom const r0=baseR*(0.4+1.0*sr()); // start radius jitter → fuzzy core, no clean spokes const len=R*(0.085+0.085*sr())*(0.55+0.55*up)*(0.7+0.5*open); // upper stamens longer → a crown, not a flat disc const f0x=ccx+Math.cos(a)*r0, f0y=ccy+Math.sin(a)*r0, steps=Math.max(1,Math.ceil(len/GRID)); for(let t=1;t<=steps;t++) C.push(f0x+Math.cos(a)*GRID*t, f0y+Math.sin(a)*GRID*t, 84, '.', shade(FC(mix(P.stamen,P.center,sr()*0.5)),sr),'flowers'); C.push(f0x+Math.cos(a)*len, f0y+Math.sin(a)*len, 85,'.',shade(FC(P.centerDk),sr),'flowers'); // anther tip (pollen) } // central receptacle dome (the seed-pod head), on top of the stamens const cw=R*0.13*(0.7+0.3*open), ch=cw*0.85; for(let gx=-cw;gx<=cw;gx+=GRID)for(let gy=-ch;gy<=ch;gy+=GRID){ if((gx*gx)/(cw*cw)+(gy*gy)/(ch*ch)>1)continue; const top=clamp(-gy/ch*0.5+0.5,0,1); C.push(ccx+gx,ccy+gy,87,'+',shade(FC(mix(P.centerDk,P.center,top)),rs),'flowers'); } for(let k=0;k<5;k++){ const a=k/5*TAU; C.push(ccx+Math.cos(a)*cw*0.42, ccy+Math.sin(a)*ch*0.42, 88,'O',shade(FC(mix(P.centerDk,'#5e4e1c',0.45)),rs),'flowers'); } } // young = a tight GREEN egg (sepal cover); as it ripens the coloured petals BREAK out of the tip // (colour spreads down from the top) and tips poke out — then it opens into the bloom. // `ripe` 0→1 = green→coloured/about-to-open · `grow` 0→1 = young round egg → elongated bud. function drawBud(fx,fy,R,lean,ripe,grow,rs){ const a=UP+lean, nx=Math.cos(a), ny=Math.sin(a), px=-ny, py=nx; const len=R*(1.3+0.9*grow), maxW=R*0.58, tn=Math.max(3,Math.ceil(len/GRID)); let p=[fx,fy]; const grA=mix(P.padDk,'#46663c',0.25), grB=mix(P.pad,P.padLt,0.35), grE=P.padEdge; // green sepal cover for(let t=1;t<=tn;t++){ p=[p[0]+nx*GRID, p[1]+ny*GRID]; const u=t/tn; const hw=maxW*Math.pow(Math.sin(Math.PI*clamp(u*0.62+0.12,0,1)),0.60)*(1-0.85*smooth(0.55,1,u)); // egg: round fat base → pointed tip if(hw<GRID*0.3 && u>0.55) continue; for(let w=-hw;w<=hw;w+=GRID){ const aw=Math.abs(w), seam=aw<GRID*0.7, e=aw>hw-GRID*1.1; const pink=clamp(ripe*1.5 - (1-u)*0.95, 0, 1); // colour breaks at the TIP first, spreads down const gcol = e?grE : seam?mix(grA,grE,0.5) : (w<0?grA:grB); const pcol = e?PV.dk : seam?PV.vn : (w<0?PV.pt:PV.lt); C.push(p[0]+px*w, p[1]+py*w, 70, '.', shade(FC(mix(gcol,pcol,pink)),rs),'flowers'); } } if(ripe>0.45){ const tipN=2+Math.round(ripe*3), tbx=fx+nx*len*0.82, tby=fy+ny*len*0.82; // coloured petal tips poking from the top for(let k=0;k<tipN;k++){ const tt=(tipN>1?(k/(tipN-1)-0.5):0)*lerp(0.25,0.9,ripe), ta=a+tt, tlen=R*(0.18+0.5*ripe); let pp=[tbx,tby]; const ptn=Math.max(2,Math.ceil(tlen/GRID)); for(let s=1;s<=ptn;s++){ pp=[pp[0]+Math.cos(ta)*GRID, pp[1]+Math.sin(ta)*GRID]; const pu=s/ptn; C.push(pp[0],pp[1],71, pu>0.7?'.':dirSym(Math.cos(ta),Math.sin(ta)), shade(FC(mix(PV.pt,PV.dk,pu*0.4)),rs),'flowers'); } } } } function drawPod(fx,fy,R,dryF,lean,rs){ // the iconic flat-topped "showerhead" receptacle const topW=R*1.08, topH=R*0.40, bodyH=R*0.66, lx=Math.sin(lean)*bodyH*0.5; const cTop=mix(P.pod,P.podDry,dryF), cSide=mix(P.podRim,P.podDryDk,dryF), cFace=mix(mix(P.pod,P.podDry,dryF),'#000000',0.10); const rows=Math.ceil(bodyH/GRID); for(let r=0;r<=rows;r++){ const u=r/rows, wdt=lerp(topW*0.92, topW*0.34, Math.pow(u,0.8)), yy=fy+topH*0.55+u*bodyH, xx=fx+lx*u; for(let gx=-wdt;gx<=wdt;gx+=GRID){ const e=Math.abs(gx)>wdt-GRID*1.1; C.push(xx+gx,yy,80, e?dirSym(0,1):'\\', shade(FC(e?mix(cSide,'#000000',0.15):cSide),rs),'flowers'); } } for(let gx=-topW;gx<=topW;gx+=GRID)for(let gy=-topH;gy<=topH;gy+=GRID){ if((gx*gx)/(topW*topW)+(gy*gy)/(topH*topH)>1)continue; const e=(gx*gx)/(topW*topW)+(gy*gy)/(topH*topH)>0.8; C.push(fx+gx,fy+gy,83,'-',shade(FC(e?cSide:cFace),rs),'flowers'); } function pit(ox,oy){ C.push(ox,oy,85,'O',shade(FC(mix(P.podRim,'#000000',0.1+0.2*dryF)),rs),'flowers'); C.push(ox,oy,86,'.',shade(FC(P.pit),rs),'flowers'); } const nOut=Math.round(7+dryF*3); for(let k=0;k<nOut;k++){ const a=k/nOut*TAU + rs()*0.2; pit(fx+Math.cos(a)*topW*0.62, fy+Math.sin(a)*topH*0.62); } for(let k=0,nIn=Math.round(3+dryF);k<nIn;k++){ const a=k/nIn*TAU + 0.5; pit(fx+Math.cos(a)*topW*0.30, fy+Math.sin(a)*topH*0.30); } pit(fx,fy); } // small rhizome crown the petioles emerge from (NO long roots) for(let k=0;k<6;k++){ const rs=rng('crown',pseed,k), a=Math.PI + (k/5-0.5)*1.7; C.push(cx+Math.cos(a)*GRID*(1+k%3)*S, by+2+Math.abs(Math.sin(a))*GRID*0.5, 18, dirSym(Math.cos(a),Math.sin(a)), shade(FC(P.stalkDk),rs),'stem'); } // ---- LEAVES: SHORT petioles (heavy pads stay low), wavy pads, 1–3 per plant ---- const nF=clamp(1+density*1.6, 1, 2.4), n=Math.ceil(nF); // 1–2 big distinct leaves (reference shows two) const leafHmax=(55+110*m)*S; // SHORT relative to the heavy leaf (no towering petioles) let tallest=by, padTop=by; for(let i=0;i<n;i++){ const rs=rng('pad',st.seed,pseed,i); const emerge=i===n-1?clamp(nF-(n-1),0.2,1):1, newest=i===n-1 && n>1; const t=n>1? i/(n-1):0.5; const bx=cx+(t-0.5)*120*S+(rs()*2-1)*8*S; // leaves sit apart with a clear gap const hFrac=newest ? 0.64 : (0.88+0.12*rs()); // STAGGER heights → two pads separate, don't form a clover const leafH=leafHmax*hFrac*emerge*collapse + 10; const lean=(t-0.5)*0.55 + (rs()*2-1)*0.05 + windDir*windStr*0.14; // pads ride low on the water → only a slight wind lean const top=rise(bx, by+2, leafH, lean, 22, mix(P.stalk,P.stalkDk,0.25*(1-hFrac)), false, rs); tallest=Math.min(tallest, top[1]); const yellowF=clamp(browny*0.55+sen*0.9+winter*0.7, 0, 1)*(0.6+0.4*(1-i/n)); const ragged=clamp(sen*0.7+winter*0.5-0.15,0,0.6); const z=30+Math.round((1-hFrac)*9); const R=(58+72*m)*emerge*(0.84+0.24*rs())*S*(newest?0.7:1); // BIG pads — lotus leaves are bigger than its flowers drawPad(top[0], top[1], R, z, yellowF, ragged, newest, rs); padTop=Math.min(padTop, top[1]-R*0.80); // visual top of the foliage (for flower-height reference) } // ---- FLOWER stalks: each flower EMERGES as a short budded stem just above the pads, then its // peduncle RISES and the bud OPENS into a bloom (→ pod). Staggered birth maturities mean // flowers appear one at a time and GROW IN — no full-length stem popping out of nowhere. // A well-cared mature root carries up to 3, each at its own phase. ---- // Up to 5 flowers per plant — count = per-seed floriferousness × earned rewards (lots of per-plant // variation). Positions are SPREAD SYMMETRICALLY across the plant's width (never center+left only, // so no side is starved); central blooms emerge first, outer L/R pairs together at higher maturity. const florif = 0.7 + 0.6*rng('florif',st.seed,pseed)(); const nFlow = reproOK ? clamp(Math.round((1 + rewards*3.4)*florif), 1, 5) : 0; const fpos = nFlow>1 ? Array.from({length:nFlow},(_,i)=>(i/(nFlow-1)-0.5)*2) : [0]; // symmetric −1..1 for(let f=0; f<nFlow; f++){ const fside = fpos[f], sgn = fside<0?-1:1; const birthF = 0.50 + Math.abs(fside)*0.20; // centre 0.50 → outer 0.70; L/R pairs emerge together if(!reproOK || m < birthF) continue; const rs=rng('fl',st.seed,pseed,f); // structural draws in FIXED order (frame-stable) const posJit=(rs()*2-1), hr=rs()*(Math.abs(fside)<0.34?1:0.6), leanJit=(rs()*2-1), Rjit=rs(); const fgrow = clamp((m - birthF)/0.18, 0, 1); // 0 = just emerged (short bud) → 1 = full height const fx0 = cx + fside*122*S + posJit*16*S; // blooms fan across the plant's full span const targetH = ((by-padTop) + lerp(18+28*m, 205+225*m, Math.pow(hr,1.9))*S) * (0.85+0.18*v)*standC; const shortH = (by-padTop) + (14+12*m)*S; // just-emerged: a bud peeking above the pads const ph = lerp(shortH, Math.max(shortH,targetH), smooth(0,1,fgrow)); // the peduncle RISES as the flower grows const lean = leanJit*0.10 + fside*0.05 + windDir*windStr*(0.22+0.45*hr)*fgrow; const top = rise(fx0, by+2, ph, lean, 52+f, P.stalk, true, rng('flStem',st.seed,pseed,f)); // stem on its own stream const R = (40+42*m)*S*(0.88+0.14*Rjit); // blooms SMALLER than the pads (lotus proportions) const hRs = rng('flHead',st.seed,pseed,f); // head on its own stream → stable regardless of stem height const localPod = clamp(podDrive + f*0.06 + (dead?1:0), 0, 1); const opening = smooth(0.50,0.92,fgrow) * clamp(bloomDrive*1.7, 0, 1); // opens near full height, gated by season/rewards if(dead || localPod>0.5) drawPod(top[0], top[1], R*0.6, clamp(sen*1.1+(season==='Autumn'?0.3:0)+(dead?0.4:0),0,1), 0.04*sgn, hRs); else if(opening>0.40) drawBloom(top[0], top[1], R, opening, hRs); else { const ripe=clamp(Math.max(