memoscan
← all memos

Memo 0xe1cc3754…fad530 on Ethereum

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,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.