memoscan
← all memos

Memo 0x1a159539…266f7a on Ethereum

m)*(1-sen)*seasonBloom*rewards, fruitF=smooth(0.52,0.78,m)*rewards*clamp(seasonRipe+0.2,0,1)*(1-0.5*sen), ripe=clamp(smooth(0.55,0.85,m)*seasonRipe + sen*0.4, 0, 1); const bark=FC(mix(P.bark,P.grey,Math.max((1-v)*0.3,sen*0.6))), barkDk=FC(mix(P.barkDk,P.grey,Math.max((1-v)*0.3,sen*0.6))); const leafM=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 greenOf=rs=>shade(mix(leafM, rs()<0.5?leafD:leafL, rs()), rs); const windLean=D.windDir*D.windStr*0.24, rootX=cx - D.windDir*D.windStr*34*S; function leafClump(ccx,ccy,rad,rs){ if(foliageF<=0.05) return; const r2=rad*rad; for(let gx=-rad;gx<=rad;gx+=GRID) for(let gy=-rad;gy<=rad;gy+=GRID){ const rr=gx*gx+gy*gy; if(rr>r2) continue; const edge=rr>r2*0.5; if(rs() > foliageF*(edge?0.4:0.94)) continue; const col= edge? shade(leafD,rs) : (rs()<0.34? shade(leafL,rs) : shade(leafM,rs)); C.push(ccx+gx, ccy+gy, 56, rs()<0.55?'O':'.', col, 'leaves'); } } function berry(x,y,rp,rs){ const col = rp<0.45 ? mix(P.berryGreen,P.berryRed,rp/0.45) : mix(P.berryRed, mix(P.berryPurple,P.berryRipe,clamp(sen,0,1)), (rp-0.45)/0.55); const cc=FC(mix(col, '#7a5a30', browny*0.4)); C.push(x,y,88,'.',shade(cc,rs),'flowers'); if(rp>0.6) C.push(x+GRID*0.4,y-GRID*0.4,89,'.',shade(mix(cc,'#ffffff',0.35),rs),'flowers'); } function rewardClump(ccx,ccy,rad,kind,rp,rs){ leafClump(ccx,ccy,rad,rs); const n=2+Math.floor(rs()*3); for(let k=0;k<n;k++){ const a=rs()*TAU, d=Math.sqrt(rs())*rad*0.7, bx=ccx+Math.cos(a)*d, byy=ccy+Math.sin(a)*d; if(kind==='berry') berry(bx,byy,rp,rs); else { for(let s=0;s<5;s++){ const sa=s/5*TAU; C.push(bx+Math.cos(sa)*GRID*1.1, byy+Math.sin(sa)*GRID*1.1, 90,'.', shade(FC(rs()<0.55?P.stamen:P.anther),rs),'flowers'); } C.push(bx,byy,91,'O',shade(FC(P.petal),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=(46+44*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.04; 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'); } for(const sd of [1,-1]){ let cd=UP+sd*0.95; let cp=[pos[0],pos[1]]; const cl=Math.max(2,Math.round((18+16*g)*S/GRID)); for(let i=1;i<=cl;i++){ cd+=angTo(cd,UP)*0.08; cp=[cp[0]+Math.cos(cd)*GRID, cp[1]+Math.sin(cd)*GRID]; const w=Math.sin(Math.PI*i/cl)*GRID*1.3, px=-Math.sin(cd),py=Math.cos(cd); C.push(cp[0],cp[1],36,dirSym(Math.cos(cd),Math.sin(cd)),greenOf(rs),'leaves'); C.push(cp[0]+px*w,cp[1]+py*w,35,'.',greenOf(rs),'leaves'); C.push(cp[0]-px*w,cp[1]-py*w,35,'.',greenOf(rs),'leaves'); } } if(g>0.4) leafClump(pos[0],pos[1], (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-400)/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('gb', seed, pid); let run=baseRun*Math.pow(D.taperLen,order)*(0.82+0.36*rs())*grown; if(order===0) run*=0.92; 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.04*(1+order*0.12), bears=order>=1 ? D.leafiness : 0.3*D.leafiness; const canChild = grown>=1 && order<maxOrder; const nLat = order===0 ? 3+Math.floor(rs()*3) : clamp(Math.round(D.lateralN*(1-0.3*D.dominance)+gauss(rs)*0.9),0, order<2?3:2); const lo=order===0?0.18:0.30, hi=0.94, 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.55/Math.max(nLat,1)), 0.1, 0.96)); 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.14,0.05,clamp(order/3,0,1))*Math.max(0,1-order/3.4), grav=0.06*D.droop*smooth(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])/520,0,1); dir += D.windDir*D.windStr*0.05*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.7*woodM*(0.7+0.3*v)*(1-0.5*u)*smooth(0.12,0.85,grown)),0,1); 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(bears>0 && u>0.14 && i%5===3 && rs()<bears*0.92){ const rad=(7+9*m)*(0.72+0.5*rs())*S; if(order>=2 && fruitF>0.2 && rs()<fruitF*D.fruitAb*0.34) rewardClump(pos[0],pos[1],rad,'berry',ripe,rs); else if(order>=2 && bloom>0.15 && rs()<bloom*D.flowerAb*0.24) rewardClump(pos[0],pos[1],rad,'flower',0,rs); else leafClump(pos[0],pos[1],rad,rs); } while(canChild && ci<pts.length && u>=pts[ci] && budget>0){ const side=sides[ci], div=(order===0?(0.5+0.5*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.10:0.16)*(1-D.reach*0.3); const cv=vigor*(order===0?(0.7+0.26*rs()):(0.5+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.55,0.92,D.dominance); grow(pos[0],pos[1], dir+angTo(dir,UP)*(order<=1?0.30:0.18)+gauss(rs)*0.05, leadV, order+1, birthM+growWin, pid*7+1, sideSeq+1); } else if(grown>=1){ const rad=(8+11*m)*(0.8+0.3*rs())*S; if(fruitF>0.2 && rs()<fruitF*D.fruitAb*0.38) rewardClump(pos[0],pos[1],rad,'berry',ripe,rs); else if(bloom>0.18 && rs()<bloom*D.flowerAb*0.3) rewardClump(pos[0],pos[1],rad,'flower',0,rs); else leafClump(pos[0],pos[1],rad,rs); } else if(order>=1){ leafClump(pos[0],pos[1], (6+8*m)*S, rs); } } sprout(rng('sprout',seed), rootX); grow(rootX, by-3, UP+D.lean*0.6+D.windDir*D.windStr*0.06, 1, 0, 0, 1, 0); } // ===================== SETTING: Solo / Pair / Thicket / Hedge ===================== const roll=rng('setting',st.seed)(); const nC = roll<0.26?0 : roll<0.56?1 : roll<0.84?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); comps.push({seed:cseed, artSeed:cseed, cx:baseX+side*(140+i*70+rc()*40), by:baseY-(2+i*8+rc()*16), S:lerp(0.66,0.46,f)*(0.9+0.2*rc()), fade:lerp(0.40,0.60,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(paintGuava); paintGuava({seed:st.seed, artSeed:st.artSeed, cx:baseX, by:baseY, S:1.08, fade:0, m, density, v, sen, browny, rewards}); }, traits(st,env){ const roll=rng('setting',st.seed)(); const setting = roll<0.26?'Solo' : roll<0.56?'Pair' : roll<0.84?'Thicket' : 'Hedge'; const fruity=(env.season==='Autumn'||env.season==='Winter'); return [ ['Setting', setting], ['Reward', st.diedAt>0?'—':(env.m>=0.55 && env.rewards>0.3 ? (fruity?'Berries':'Flowers') : '—')], ['Form', st.diedAt>0?'Bare twigs':'Oval shrub'], ]; } })); })(); ; /* species/hairgrass.js */ /* hairgrass — species index 15. GENERATED from prototypes/antarctic-hair-grass.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(15, spec); /* Antarctic Hair Grass (Deschampsia antarctica) — the rarest Seedling. A wind-bleached cushion-tuft of fine arching blades (green->yellow->brown) growing from a crevice in angular gray ROCKS, dusted with SNOW, sending up delicate airy PANICLE seed-heads. The lone survivor — one of only two native flowering plants of Antarctica. */ mount({ title:'Antarctic Hair Grass', sub:'hairgrass v2', LIFESPAN:730, GRACE:150, DECAY:1/50, AGE_EQUIV:14, senFrom:0.86, note:'LEGENDARY · a lone wind-bleached tuft rooted in the rocks, dusted with snow, sending up airy golden seed-heads. One of only two native Antarctic flowering plants.', PAL:{ soil:'#8C8C88', accent:'#9DB36A', blade:'#5E8A46', bladeDk:'#3F6330', bladeLt:'#8FB45E', straw:'#C7B173', strawDk:'#9F844A', rock:'#7E868B', rockDk:'#474D51', rockLt:'#AEB4B8', crack:'#2B2F33', stalk:'#9DAE6F', spikeGreen:'#86A857', spikeStraw:'#C7A85E', awn:'#B7A36E', snow:'#F5F9FD', snowDk:'#D8E4EE' }, schemeExempt:['season','flowers'], // rocks + snow + spikelets read literal (solid), not scheme rings LW:{season:2.4, stem:1.1, leaves:1.0, flowers:1.7}, // bold season -> rocks read as SOLID stone stageName(st,env){ if(st.diedAt>0)return'Sealed'; const m=env.m; if(m<0.16)return'Sprig'; if(m<0.5)return'Tuft'; if(m<0.84)return'Cushion'; return'Seeding'; }, draw(st, C, env){ const {m,v,sen,season,browny,density,rewards,baseX,baseY}=env, P=this.PAL; const cx=baseX, cy=baseY-182; // tuft roots in the crevice on the central block const flowering = smooth(0.40,0.66,m) * (1-sen*0.85); const seasonGold = season==='Summer'?1 : season==='Autumn'?0.7 : season==='Spring'?0.5 : 0.3; const snowF = season==='Winter'?1 : season==='Autumn'?0.55 : season==='Spring'?0.4 : 0.16; // ---------- ROCKS: angular fractured granite blocks the tuft roots between ---------- function rockField(){ const rs=rng('rock',st.seed); // 1. jagged ANGULAR skyline: piecewise-linear control points (not a smooth curve) const nC=5+Math.floor(rs()*3), cpx=[], cpy=[]; for(let i=0;i<=nC;i++){ cpx.push(FRAME-4 + i*(W-2*FRAME+8)/nC); cpy.push((baseY-150) - rs()*72); } cpy[Math.round(nC/2)] = (baseY-176) - rs()*14; // central block sits a touch higher -> tuft nestles on it const topAt=x=>{ let i=0; while(i<nC-1 && cpx[i+1]<x) i++; const t=clamp((x-cpx[i])/((cpx[i+1]-cpx[i])||1),0,1); return lerp(cpy[i],cpy[i+1],t); }; // 2. Voronoi seeds -> distinct angular faceted stones, each its own tone const nS=12+Math.floor(rs()*6), seeds=[]; for(let i=0;i<nS;i++) seeds.push({x:FRAME+rs()*(W-2*FRAME), y:(baseY-115)+rs()*180, t:rs()}); for(let x=FRAME+6;x<=W-FRAME-6;x+=GRID){ const top=topAt(x); for(let y=top;y<=baseY+38;y+=GRID){ let d0=1e9,d1=1e9,b0=seeds[0],b1=seeds[0]; for(const s of seeds){ const dx=x-s.x, dy=(y-s.y)*1.25, d=dx*dx+dy*dy; if(d<d0){d1=d0;b1=b0;d0=d;b0=s;} else if(d<d1){d1=d;b1=s;} } const seam = (Math.sqrt(d1)-Math.sqrt(d0)) < GRID*0.62; // THIN Voronoi boundary -> clean fracture line const edge = y < top + GRID*1.5; // top facet catches light const crev = Math.abs(x-cx) < GRID*1.5 && y < top+52; // central crevice the tuft emerges from let col, sym='.'; if(crev){ col=P.crack; } else if(seam){ col=P.crack; // crack runs perpendicular to the seed-pair axis sym = dirSym(-(b1.y-b0.y), b1.x-b0.x); } else { col = b0.t<0.30?P.rockDk : b0.t<0.68?P.rock : P.rockLt; if(edge) col=mix(col,P.rockLt,0.5); } C.push(x,y, edge?12:(seam?11:8), sym, shade(col,rs), 'season'); if(edge && !seam && snowF>0.2 && rs()<snowF*0.75) C.push(x,y-GRID, 14, '.', shade(rs()<0.6?P.snow:P.snowDk,rs), 'season'); // snow caps the lit edges } } // 3. a few long straight MASTER CRACKS across the faces (fractured-granite feel) for(let kc=0; kc<3+Math.floor(rs()*3); kc++){ let px=FRAME+24+rs()*(W-2*FRAME-48), py=topAt(px)+12+rs()*120; let dir=DOWN+(rs()*2-1)*1.15; const len=44+rs()*120, stp=Math.ceil(len/GRID); for(let s=0;s<stp;s++){ px+=Math.cos(dir)*GRID; py+=Math.sin(dir)*GRID; if(py>baseY+36 || px<FRAME+6 || px>W-FRAME-6) break; C.push(px,py, 11, dirSym(Math.cos(dir),Math.sin(dir)), shade(P.crack,rs), 'season'); } } } rockField(); // panicles bloom GREEN; they turn straw only as they SET SEED — late maturity, autumn, or senescence const seedSet=clamp(smooth(0.80,1.0,m)*0.8 + sen*0.9 + (season==='Autumn'?0.45:0), 0, 1); const bgc=BACKDROPS[backdropFor(st.seed)]; // backdrop colour, for fading distant tufts back const fmix=(c,f)=> f>0 ? mix(c,bgc,f) : c; // ---------- one TUFT (cushion of arching blades + airy panicles); sc=size, fade=distance, tag=identity ---------- function tuft(ox, oy, sc, fade, tag){ const zo = tag==='hero' ? 0 : -13; // companions sit BEHIND the hero const nF=clamp(16+density*74,16,90)*(0.55+0.45*sc), n=Math.ceil(nF); // blade COUNT (cushion lushness) ← entries (density); floor 16 = the survivor always tufts for(let k=0;k<n;k++){ const rs=rng('blade',st.seed,tag,k); const emerge=k===n-1?clamp(nF-(n-1),0.12,1):1; const a0=(rs()*2-1); // stable fan position const ang=UP + a0*1.18; const len=(84+205*m)*sc*(0.5+0.6*rs())*emerge*(1-0.22*Math.abs(a0)); const arch=0.5+0.85*rs(), outer=Math.abs(a0); // outer = older blade const bladeBrown=clamp(smooth(0.62,1.0,outer)*0.9 + browny*0.6, 0, 1); // ONLY outer/older blades straw off let dir=ang, pos=[ox+a0*11*sc, oy]; const steps=Math.max(2,Math.ceil(len/GRID)); for(let i=1;i<=steps;i++){ const u=i/steps; dir += angTo(dir,DOWN)*arch*0.05*(0.3+0.7*u); // arch outward then over the rocks pos=[pos[0]+Math.cos(dir)*GRID, pos[1]+Math.sin(dir)*GRID]; const tipBrown=clamp(bladeBrown*(0.6+0.4*u) + browny*u*0.35, 0, 1); const green=mix(P.blade, rs()<0.5?P.bladeDk:P.bladeLt, rs()); const col=fmix(mix(green, mix(P.straw,P.strawDk,rs()), tipBrown), fade); C.push(pos[0],pos[1], 40+zo, dirSym(Math.cos(dir),Math.sin(dir)), shade(col,rs), 'leaves'); } } function spikelet(x,y,dir,rs){ const col=fmix(mix(P.spikeGreen, P.spikeStraw, clamp(seedSet*(0.55+0.45*rs()),0,1)), fade); const awnCol=fmix(mix(P.spikeGreen, P.awn, clamp(0.35+seedSet,0,1)), fade); let sp=[x,y]; const nn=3+Math.floor(rs()*2); // a small oval lemma cluster for(let i=0;i<nn;i++){ sp=[sp[0]+Math.cos(dir)*GRID*0.9, sp[1]+Math.sin(dir)*GRID*0.9]; C.push(sp[0],sp[1], 92+zo, i%2?'O':'.', shade(col,rs), 'flowers'); } let ap=[sp[0],sp[1]]; // a fine awn extending past the tip for(let i=0;i<3+Math.floor(rs()*3);i++){ ap=[ap[0]+Math.cos(dir)*GRID, ap[1]+Math.sin(dir)*GRID]; C.push(ap[0],ap[1], 90+zo, dirSym(Math.cos(dir),Math.sin(dir)), shade(awnCol,rs), 'flowers'); } } if(flowering>0.08){ const nPan=Math.max(1, Math.round((1+m*4)*flowering*(0.45+0.55*rewards)*(tag==='hero'?1:0.55))); // seed-heads ← rewards (floor keeps the survivor seeding) for(let p=0;p<nPan;p++){ const rs=rng('pan',st.seed,tag,p); const grown=clamp((m-(0.4+p*0.09))/0.12,0,1); // panicles emerge progressively if(grown<=0) continue; const sx=ox+(rs()*2-1)*(20+m*44)*sc; const sh=(190+260*m)*sc*grown*(0.7+0.4*rs()); let pos=[sx,oy-4], dir=UP+(rs()*2-1)*0.2; const steps=Math.ceil(sh/GRID); for(let s=1;s<=steps;s++){ const u=s/steps; dir += Math.sin(u*3+rs()*6)*0.02 + (rs()*2-1)*0.004; // wiry wobble pos=[pos[0]+Math.cos(dir)*GRID, pos[1]+Math.sin(dir)*GRID]; C.push(pos[0],pos[1], 70+zo, dirSym(Math.cos(dir),Math.sin(dir)), shade(fmix(P.stalk,fade),rs), 'stem'); if(u>0.5 && s%3===0){ // open panicle branches in the upper half const nb=1+Math.floor(rs()*2); for(let b=0;b<nb;b++){ const side=rs()<0.5?1:-1; const bl=(16+30*m)*sc*(1-u)*(0.6+0.6*rs()); let bp=[pos[0],pos[1]], bd=dir+side*(0.6+0.5*rs()); const bst=Math.max(2,Math.ceil(bl/GRID)); for(let bs=1;bs<=bst;bs++){ bd += angTo(bd, UP+side*0.3)*0.1; // branch arcs up bp=[bp[0]+Math.cos(bd)*GRID, bp[1]+Math.sin(bd)*GRID]; C.push(bp[0],bp[1], 71+zo, dirSym(Math.cos(bd),Math.sin(bd)), shade(fmix(P.stalk,fade),rs), 'stem'); } spikelet(bp[0],bp[1],bd,rs); } } } spikelet(pos[0],pos[1],dir,rs); // a terminal spikelet } } } // ---------- a small COLONY: companions scatter in the crevices behind the hero (varies by seed; not always solo) ---------- const cr=rng('colony',st.seed); const nComp=[0,1,1,2,2,3][Math.floor(cr()*6)]; for(let i=0;i<nComp;i++){ const side=cr()<0.5?-1:1, depth=cr(); // depth 0 = near ... 1 = far const bx=clamp(cx + side*(78+cr()*210), FRAME+72, W-FRAME-72); const by=(baseY-150) + cr()*46; // rooted lower on the rock surface than the hero tuft(bx, by, lerp(0.62,0.38,depth), lerp(0.40,0.64,depth), 'bg'+i); } tuft(cx, cy, 1, 0, 'hero'); // the hero tuft, full size and in front // ---------- SNOW: scattered flecks (more in winter), settled + drifting ---------- const sr=rng('snow',st.seed); const nSnow=Math.round(70*snowF + 14); for(let i=0;i<nSnow;i++){ const x=FRAME+8 + sr()*(W-2*FRAME-16); const y=lerp(baseY+10, 140, sr()*sr()); // biased low (settled on the ground/rocks) C.push(x,y, 96, '.', shade(sr()<0.5?P.snow:P.snowDk, sr), 'flowers'); } }, traits(st,env){ const season=seasonOf(st.now); const snow = season==='Winter'?'Snowfall' : (season==='Summer'?'Clear':'Dusting'); return [['Habitat','Rock crevice'], ['Frost', st.diedAt>0?'Frozen':snow], ['Hardiness','Extreme']]; } }); })(); ; /* species/lavender.js */ /* lavender — species index 3. GENERATED from prototypes/lavender.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(3, spec); /* Lavender — Lavandula. A radiating fountain of fine woody stems (a rounded clump), narrow opposite grey-green leaves, and vertical violet FLOWER-SPIKES of whorled florets held above the foliage. Hardy Mediterranean shrub: slow to wilt, summer bloom. Stems emerge progressively — old stems splay outward, the newest keep rising from the centre. Death = grey dried wands. Grows in a patch (Setting: Solo / Pair / Cluster / Field). Ported to the shared core. */ const SP = window.SEEDLING_SPECIES.find(x=>x.key==='lavender'); mount(Object.assign(window.SEEDLING_toSpec(SP), { title:'Lavender', sub:'lavandula v2', senFrom:0.80, schemeExempt:[], // spikes follow the per-seed scheme (woven, like the rest) LW:{season:1.0, stem:1.05, leaves:1.1, flowers:1.9}, LS:{season:1.0, stem:1.0, leaves:0.9, flowers:0.8}, note:'Hardy Mediterranean clump · radiating woody stems · narrow opposite leaves · violet flower-spikes above the foliage · summer bloom · death = grey dried wands.', PAL:{ soil:'#6f5128', grey:'#9a9180', flower:'#7A4FC0', flowerLt:'#AE8FE2', flowerDk:'#553399', bud:'#7E8C56', leafDark:'#566E50', leaf:'#88A07E', leafLt:'#B4C3A4', stem:'#6E7A54' }, stageName(st,env){ if(st.diedAt>0)return'Sealed'; const m=env.m; if(env.ageF>0.86)return'Spent'; if(m<0.12)return'Sprout'; if(m<0.42)return'Young bush'; if(m<0.6)return'Bush'; return (env.season==='Summer'||env.season==='Spring')?'Flowering':'Bush'; }, draw(st, C, env){ const {m,v,sen,season,browny,density,rewards,baseX,baseY}=env, P=this.PAL; const bgC = BACKDROPS[backdropFor(st.seed)]; const seasonOpen = season==='Summer'?1 : season==='Spring'?0.8 : season==='Autumn'?0.55 : 0.4; // ===================== one lavender bush (repeated to form a patch) ===================== function paintBush(I){ const {cx, by, S, fade, m, v, sen, browny, density, rewards, zb, pseed} = I; const FC = c => fade>0 ? mix(c, bgC, fade) : c; const flowering = smooth(0.40,0.62,m) * rewards * (1-sen*0.8); const cFlower=FC(mix(P.flower,P.grey,browny)), cFlowerLt=FC(mix(P.flowerLt,P.grey,browny)), cFlowerDk=FC(mix(P.flowerDk,P.grey,browny)); const cBud=FC(mix(P.bud,P.grey,browny*0.9)); 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 stemC=FC(mix(P.stem,P.grey,Math.max((1-v)*0.5,sen))); const greenOf=rs=>shade(mix(leafM, rs()<0.5?leafD:leafL, rs()), rs); // per-seed personality so two bushes differ structurally (not just noise) const hero=pseed==='hero'; const dd= hero?rng('dna',st.seed):rng('dna',st.seed,pseed); const D={ spread:0.50+0.55*dd(), lean:(dd()*2-1)*0.16, fanBase:16+30*dd(), vigor:0.85+0.30*dd(), bushiness:dd(), openBias:0.85+0.30*dd(), spikeF:0.80+0.40*dd() }; const MAXS=30, growWin=0.12, splayOpen=smooth(0.0,0.34,m); const lenBase=(180+440*m)*(0.72+0.28*v)*D.vigor*S; for(let k=0;k<MAXS;k++){ const rs= hero?rng('stem',st.seed,k):rng('stem',st.seed,pseed,k); const birthM=(k/MAXS)*0.70; const grown=clamp((density-birthM)/growWin, k===0?0.2:0, 1); // stem COUNT ← density; first always present if(grown<=0) continue; const outerness=lerp(1.0,0.22,k/MAXS); // OLD outer · NEW central → fresh growth from the middle const a0=(rs()*2-1)*outerness*splayOpen; const ang0=UP + a0*D.spread + D.lean; const baseX0=cx + a0*D.fanBase*S; const maxLen=lenBase*(1-0.30*Math.abs(a0))*(0.8+0.4*rs()); const len=maxLen*grown; const zf=rs()<0.5, stemZ=(zf?42:30)+zb, leafZ=(zf?44:28)+zb, flZ=(zf?94:84)+zb; const stemFlower = flowering>0.05 && grown>0.4 && rs() < (0.3+0.66*flowering); const leafTop = stemFlower?0.58:0.86; let dir=ang0, pos=[baseX0, by-3]; const steps=Math.max(3,Math.ceil(len/GRID)); let tip=[pos[0],pos[1]], tdir=dir;