0xffd6563c…95fesent to0x72842d09…a7a6·#25,495,189·view on Etherscan
ll-bleed"], ["Z", "zoom (drag to pan)"], ["Space", "play / pause"], ["← →", "scrub the timeline"], ["L", "loop"], ["R", "ping-pong"], ["A", "full life"], ["C", "seasons"], ["N", "night"], ["D", "day"], ["M", "mono"], ["I", "traits"], ["G", "grid"], ["?", "this help"]];
(function () { var h = '<div style="max-width:82%"><div style="color:#cfe6bd;margin-bottom:8px;letter-spacing:2px">CONTROLS</div>'; for (var i = 0; i < KEYS.length; i++) h += '<div><b style="display:inline-block;min-width:56px;color:#cfe6bd">' + KEYS[i][0] + "</b> " + KEYS[i][1] + "</div>"; helpEl.innerHTML = h + "</div>"; })();
// ── mode state ───────────────────────────────────────────────────────────
var M = { frame: "square", mood: "day", nightStyle: "moonlit", motion: "default", paused: false, fullbleed: false, zoom: 0, zx: 0.5, zy: 0.45, grid: false, info: false, help: false };
var DUR = 4200, LIFEDUR = 8000, YEARDUR = 7000;
var curSt = mapped.st, displaySec = planted;
function displaySecFor(clock) {
if (M.motion === "loop") return planted + ((clock % DUR) / DUR) * growSpan;
if (M.motion === "pingpong") { var p = (clock % (2 * DUR)) / DUR; return planted + (p < 1 ? p : 2 - p) * growSpan; }
if (M.motion === "fulllife") return planted + smooth(clampN(clock / LIFEDUR, 0, 1)) * (deathSec - planted);
if (M.motion === "seasons") return (nowSec || planted) + ((clock % YEARDUR) / YEARDUR) * 365 * DAY;
return planted + smooth(clampN(clock / DUR, 0, 1)) * growSpan; // default: grow → hold
}
function build(sec) { var st = toEngineState(state, sec).st; if (M.motion === "fulllife" && sec >= deathSec) { st.diedAt = deathDays; st.deathCause = "FullTerm"; } return st; }
function svgEl() { return target.querySelector("svg"); }
function hardRender(sec) { setFrame(M.frame); curSt = build(sec); target.innerHTML = renderPlant(spec, curSt); applyView(); applyMood(); if (M.info) refreshInfo(); }
function applyView() {
var svg = svgEl(); if (!svg) return;
var d = DIMS[M.frame], px = 46, py = 46, pw = d.W - 92, ph = d.H - 92, vb = [0, 0, d.W, d.H];
if (M.fullbleed) vb = [px, py, pw, ph];
if (M.zoom > 0) { var frac = [1, 0.52, 0.34][M.zoom] || 0.34, s = pw * frac, cx = px + M.zx * pw, cy = py + M.zy * ph; vb = [clampN(cx - s / 2, px, px + pw - s), clampN(cy - s / 2, py, py + ph - s), s, s]; }
svg.setAttribute("viewBox", vb.join(" ")); svg.setAttribute("preserveAspectRatio", "xMidYMid meet");
}
function applyMood() {
var svg = svgEl(); if (!svg) return;
var r = svg.getElementsByTagName("rect"), sheet = r[0], panel = r[1], bleed = M.fullbleed;
if (M.mood === "night") {
var gen = M.nightStyle === "generic", sky = gen ? { sheet: "#070b15", panel: "#0b1122" } : NIGHT_SKY;
if (sheet) sheet.setAttribute("fill", bleed ? "transparent" : sky.sheet); if (panel) panel.setAttribute("fill", sky.panel);
if (gen) { svg.style.filter = "saturate(.92) brightness(1.07)"; }
else { svg.style.filter = ""; var e = svg.querySelectorAll("[id^=lyr-] *"); for (var i = 0; i < e.length; i++) { var f = e[i].getAttribute("fill"); if (f && f !== "none") e[i].setAttribute("fill", nightGrade(f)); var st2 = e[i].getAttribute("stroke"); if (st2 && st2 !== "none") e[i].setAttribute("stroke", nightGrade(st2)); } }
} else if (M.mood === "mono") { if (sheet) sheet.setAttribute("fill", bleed ? "transparent" : "#ffffff"); svg.style.filter = "grayscale(1) contrast(1.06)"; }
else { if (sheet) sheet.setAttribute("fill", bleed ? "transparent" : "#ffffff"); svg.style.filter = ""; }
}
function row(k, v) { return '<div><span style="color:#96a08c">' + k + "</span> <b>" + v + "</b></div>"; }
function refreshInfo() {
if (!revealed) { infoEl.innerHTML = row("Status", "Revealing Soon") + row("Seed", "Mystery"); return; }
var q = Math.floor(curSt.now / 2) * 2, mat = Math.round(100 * (1 - Math.exp(-(q + 2 * (curSt.wateredEpochs || 0) * 7) / (0.7 * spec.LIFESPAN))));
infoEl.innerHTML = row("Species", esc(spec.title)) + (state.name ? row("Name", esc(state.name)) : "") + row("Age", (curSt.now | 0) + " d") + row("Entries", curSt.entryCount) + row("Watered", (curSt.wateredEpochs || 0) + " wk") + row("Season", seasonName(curSt.now)) + row("Maturity", mat + "%") + row("State", curSt.diedAt ? "sealed" : "living");
}
// ── clock (default no-interaction path = grow→hold, then idle: no re-render) ──
var t0 = null, lastPaint = 0, lastSec = null;
function tick(ts) {
if (t0 === null) t0 = ts;
if (!M.paused) displaySec = displaySecFor(ts - t0);
if (ts - lastPaint > 70 && displaySec !== lastSec) { lastPaint = ts; lastSec = displaySec; hardRender(displaySec); }
requestAnimationFrame(tick);
}
// ── drag-to-pan when zoomed ──
var _drag = null;
target.addEventListener("pointerdown", function (e) { if (M.zoom > 0) { _drag = { x: e.clientX, y: e.clientY }; try { target.setPointerCapture(e.pointerId); } catch (_) {} } });
target.addEventListener("pointermove", function (e) { if (!_drag) return; var r = target.getBoundingClientRect(), frac = [1, 0.52, 0.34][M.zoom] || 0.34; M.zx = clampN(M.zx - (e.clientX - _drag.x) / r.width * frac, 0, 1); M.zy = clampN(M.zy - (e.clientY - _drag.y) / r.height * frac, 0, 1); _drag = { x: e.clientX, y: e.clientY }; applyView(); });
target.addEventListener("pointerup", function () { _drag = null; });
function scrub(dir) { M.paused = true; if (M.motion === "seasons") M.motion = "default"; var span = M.motion === "fulllife" ? deathSec - planted : growSpan; displaySec = clampN(displaySec + dir * span * 0.04, planted, planted + span * 1.05); lastSec = displaySec; hardRender(displaySec); }
// ── keys (desktop; needs iframe focus on marketplaces; mobile = passive default) ──
function onKey(e) {
var k = (e.key || "").toLowerCase();
if (k === "arrowleft") { e.preventDefault(); scrub(-1); return; }
if (k === "arrowright") { e.preventDefault(); scrub(1); return; }
if (k === " ") { e.preventDefault(); M.paused = !M.paused; if (!M.paused) t0 = null; hintEl.style.opacity = "0"; return; }
if (k === "?" || k === "/") { e.preventDefault(); M.help = !M.help; helpEl.style.display = M.help ? "flex" : "none"; return; }
switch (k) {
case "s": M.frame = "square"; M.motion = "default"; M.mood = "day"; M.fullbleed = false; M.zoom = 0; M.zx = 0.5; M.zy = 0.45; M.paused = false; t0 = null; hardRender(displaySec); break;
case "p": M.frame = M.frame === "portrait" ? "square" : "portrait"; hardRender(displaySec); break;
case "w": M.fullbleed = !M.fullbleed; applyView(); applyMood(); break;
case "z": M.zoom = (M.zoom + 1) % 3; if (M.zoom === 0) { M.zx = 0.5; M.zy = 0.45; } applyView(); break;
case "l": M.motion = M.motion === "loop" ? "default" : "loop"; M.paused = false; t0 = null; break;
case "r": M.motion = M.motion === "pingpong" ? "default" : "pingpong"; M.paused = false; t0 = null; break;
case "a": if (revealed) { M.motion = M.motion === "fulllife" ? "default" : "fulllife"; M.paused = false; t0 = null; } break; // teaser: full-life disabled
case "c": M.motion = M.motion === "seasons" ? "default" : "seasons"; M.paused = false; t0 = null; break;
case "d": M.mood = "day"; hardRender(displaySec); break;
case "n": if (M.mood !== "night") { M.mood = "night"; M.nightStyle = "moonlit"; } else { M.nightStyle = M.nightStyle === "moonlit" ? "generic" : "moonlit"; } hardRender(displaySec); break;
case "m": M.mood = M.mood === "mono" ? "day" : "mono"; hardRender(displaySec); break;
case "g": M.grid = !M.grid; gridEl.style.display = M.grid ? "block" : "none"; break;
case "i": M.info = !M.info; infoEl.style.display = M.info ? "block" : "none"; if (M.info) refreshInfo(); break;
}
hintEl.style.opacity = "0";
}
window.addEventListener("keydown", onKey);
setTimeout(function () { hintEl.style.opacity = "0"; }, 4200);
requestAnimationFrame(tick);
}
// public entrypoint + registry hook for the bundled species files
if (typeof window !== "undefined") {
window.SeedlingRenderer = { boot: boot, registerSpecies: registerSpecies, _registry: REGISTRY };
}
})();
;
/* species/aloe.js */
/* aloe — species index 2. GENERATED from prototypes/aloe-vera.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(2, spec);
/* Aloe Vera — a stemless rosette of thick succulent toothed leaves: it starts as a 2-leaf V, builds
a narrow distichous fan, then opens into a spiral vase-rosette (newest leaves central & upright,
oldest outer, long & recurved). A mature rosette throws up several thin green RACEMES, each topped
by a cone of small coral florets that colour from the base up (green bud tip). Clonal PUPS crowd the
base; clumps via Setting. Hardy; wind barely leans it. Ported to the shared core. */
const SP = window.SEEDLING_SPECIES.find(x=>x.key==='aloe');
mount(Object.assign(window.SEEDLING_toSpec(SP), {
title:'Aloe Vera', sub:'aloe v9',
senFrom:0.82,
LW:{season:1.0, stem:1.2, leaves:1.25, flowers:1.45},
LS:{season:1.0, stem:1.0, leaves:0.9, flowers:0.7},
note:'Succulent rosette (2-leaf V → fan → spiral vase) · toothed flecked blades · fanned racemes of coral florets (colour rises from the base) · clonal pups · clumps.',
PAL:{ soil:'#6f5128', dry:'#7a5a30',
leaf:'#79A074', leafDk:'#496B47', leafLt:'#A7C58D', tooth:'#3A5638', fleck:'#E2EFD2',
bud:'#8FA653', budDk:'#5E7338', stalk:'#7E9B54',
flower:'#E8602E', flowerDk:'#C5331A', flowerTip:'#F4C24A',
capsule:'#90A158', capsuleDry:'#9C7E40' },
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'Sprout'; if(m<0.34)return'Fan';
if(m<0.58)return'Rosette'; if(m<0.78)return'Mature'; return'Flowering'; },
draw(st, C, env){
const {m,v,sen,season,browny,density,rewards,baseX,baseY}=env, P=this.PAL;
const bgC = BACKDROPS[backdropFor(st.seed)], GA=Math.PI*(3-Math.sqrt(5));
const seasonBloom = (season==='Winter'||season==='Spring')?1 : season==='Autumn'?0.7 : 0.58;
function aloeDNA(seed){ const dd=rng('dna',seed), g=()=>(dd()+dd()+dd()-1.5)/1.5;
return { leafN:0.8+0.55*dd(), fat:0.65+0.6*dd(), recurve:0.45+0.85*dd(), twist:0.5+0.9*dd(), teeth:0.45+0.7*dd(),
flecks:dd(), stature:0.85+0.30*dd(), flowerHue:g()*0.5, nRaceme:dd()<0.62?1:2, pups:0.25+0.75*dd(),
windDir:dd()<0.5?1:-1, windStr:Math.pow(dd(),2.4),
// — posture DNA — the rosette spiral used to be globally one-handed (sin(i*GA) is seed-independent),
// so EVERY plant's crown drifted the same way → "always leans right". These break that symmetry:
chir:dd()<0.5?1:-1, // spiral handedness — flips which way the phyllotaxis winds
spiralPh:dd()*TAU, // per-seed phyllotaxis phase → a different wobble signature each seed
lean:g(), // bell-weighted BIDIRECTIONAL posture (most upright; some tilt L or R)
leanCurve:0.6+0.8*dd(), // how strongly the lean reads through blade curvature vs a rigid tilt
fullSide:dd()<0.5?1:-1, // which flank carries fuller / longer blades (no rosette is a mirror)
fullAmt:Math.pow(dd(),1.3) }; // strength of that lopsidedness (skewed subtle, occasionally bold)
}
function paintAloe(I){
const {seed, artSeed, cx0, by, S, fade, m, density, v, sen, browny, rewards} = I;
const FC = c => fade>0 ? mix(c, bgC, fade) : c;
const ra=rng('struct',artSeed), D=aloeDNA(seed), wilt=clamp(Math.max(1-v,sen),0,1);
const leafD=FC(mix(P.leafDk,P.dry,browny*0.9)), leafM=FC(mix(P.leaf,P.dry,browny*0.9)), leafL=FC(mix(P.leafLt,P.dry,browny*0.85));
const toothC=FC(mix(P.tooth,P.dry,browny*0.7)), stalkC=FC(mix(P.stalk,P.dry,Math.max((1-v)*0.5,sen)));
const floC=FC(mix(P.flower, P.flowerDk, clamp(-D.flowerHue,0,1)*0.5));
const windLean=D.windDir*D.windStr*0.26, cx=cx0 - D.windDir*D.windStr*26*S, cy=by-14*S;
const leanTilt=D.lean*0.20, axisUP=UP+leanTilt; // central growth axis — tilts L/R per seed (±~11°)
function leafHalfW(u, fat){ const body=Math.pow(1-u,0.56), attach=u<0.12?lerp(0.78,1,u/0.12):1; return Math.max(0.5, fat*body*attach); }
function drawLeaf(ang, len, fat, recurve, rl, z, teeth, flecky){ const steps=Math.max(3,Math.ceil(len/GRID)); let dir=ang, pos=[cx,cy];
for(let s=1;s<=steps;s++){ const u=s/steps; dir += angTo(dir,DOWN)*recurve*0.022*u*u + windLean*0.018*u + leanTilt*D.leanCurve*0.020*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=leafHalfW(u,fat), sym=dirSym(cd[0],cd[1]);
for(let w=-hw;w<=hw;w+=GRID){ const a=Math.abs(w)/Math.max(hw,1), edge=Math.abs(w)>hw-GRID*1.05;
const col = edge ? shade(mix(leafD,'#243520',0.5),rl) : a<0.22 ? shade(leafL,rl) : shade(mix(leafM,leafD,a*0.85),rl);
C.push(pos[0]+px*w, pos[1]+py*w, z, sym, col, 'leaves'); }
if(teeth && s%4===0 && u<0.92){ C.push(pos[0]+px*(hw+GRID*0.6), pos[1]+py*(hw+GRID*0.6), z+4, '.', shade(toothC,rl), 'leaves');
C.push(pos[0]-px*(hw+GRID*0.6), pos[1]-py*(hw+GRID*0.6), z+4, '.', shade(toothC,rl), 'leaves'); }
if(flecky && u>0.12 && rl()<0.10){ const fw=(rl()*2-1)*hw*0.7; C.push(pos[0]+px*fw, pos[1]+py*fw, z+3, '.', shade(FC(P.fleck),rl), 'leaves'); }
}
}
const nLeafF=clamp(2 + density*11*D.leafN + (rng('nl',seed)()*2-1)*0.7, 2, 14), nLeaf=Math.ceil(nLeafF), nPair=Math.max(1,Math.ceil(nLeaf/2));
const openF=smooth(0.12,0.7,m), spreadMax=lerp(0.28,0.62,openF);
for(let i=0;i<nLeaf;i++){ const rl=rng('leaf',seed,i), emerge=i===nLeaf-1?clamp(nLeafF-(nLeaf-1),0.06,1):1, grow=smooth(0,1,emerge);
const pair=Math.floor(i/2), side=(i%2?1:-1), tp=nPair>1? pair/(nPair-1) : 0.3, mag=lerp(spreadMax, 0.12, tp);
const wob=Math.sin(i*GA*D.chir + D.spiralPh)*0.13*openF*D.twist; // per-seed handedness + phase (was uniform)
const full=side===D.fullSide, lop=full?1+0.22*D.fullAmt:1-0.16*D.fullAmt, lopLen=full?1+0.12*D.fullAmt:1-0.08*D.fullAmt;
const ang=axisUP + side*mag*lop + wob + windLean*0.4, recurve=(1-tp)*0.7*D.recurve + wilt*1.0, vase=lerp(1.0, lerp(1.0,0.74,tp), smooth(0.15,0.6,m));
const len=(92+208*m)*D.stature*vase*(0.82+0.26*rl())*lopLen*grow*S, widthM=lerp(0.32,1.0,smooth(0.04,0.55,m));
const fat=(4.4 + 10.5*D.fat)*(0.62+0.38*v)*grow*lerp(1.12,0.76,tp)*widthM*Math.sqrt(S), z=40 + i;
drawLeaf(ang, len, fat, recurve, rl, z, D.teeth>0.4, D.flecks>0.35);
}
const flowerVigor=smooth(0.52,0.84,m)*rewards, bloomOn=flowerVigor*seasonBloom*(1-0.5*sen);
function drawCone(axis, racStart, steps, span, spikeColor, dry, wide, seedk){ const maxCone=GRID*(2.0+1.2*wide)*Math.sqrt(S), front=spikeColor*1.3;
for(let k=racStart;k<steps;k++){ const p=(k-racStart)/span, coneW=maxCone*Math.pow(clamp(1-p,0,1),0.72)+GRID*0.35, ax=axis[k][0], ay=axis[k][1], nf=Math.max(1,Math.round(coneW/GRID));
for(let j=-nf;j<=nf;j++){ if(j===0 && p<0.9) continue; const rr=rng('flo',seed, seedk + k*29 + (j+nf)*7), off=j*GRID*0.92 + (rr()*2-1)*GRID*0.18;
if(Math.abs(off)>coneW+GRID*0.4) continue; const aw=Math.abs(off)/Math.max(coneW,1), fx=ax+off, fy=ay + aw*GRID*1.1 + (rr()*2-1)*GRID*0.2;
if(rr()<0.4) C.push(ax+off*0.45, ay+aw*GRID*0.5, 88, off>0?'\\':'/', shade(FC(P.budDk),rr), 'flowers');
const green = p>0.86 || p>=front; let col;
if(green) col=shade(FC(mix(P.bud,P.budDk,0.25)),rr);
else { const u=rr(); col=shade(FC(mix(u<0.22?P.flowerDk:u<0.82?P.flower:P.flowerTip, P.capsuleDry, dry*0.7)),rr); if(u>=0.22&&u<0.82) col=shade(mix(floC,FC(P.capsuleDry),dry*0.7),rr); }
C.push(fx, fy, green?90:92, rr()<0.42?'.':(rr()<0.72?'+':'x'), col, 'flowers');
}
}
}
function drawRaceme(sx, lean, hScale, spikeColor, grow, seedk, rs){ const sh=(185+330*m)*D.stature*hScale*smooth(0.5,0.96,m)*grow*S; if(sh<GRID*5) return;
const steps=Math.ceil(sh/GRID), ph=rs()*6.28, aim=axisUP+windLean*0.7+lean; let dir=axisUP+windLean*0.4+lean*0.7, pos=[sx,cy-6*S], axis=[];
for(let s=1;s<=steps;s++){ const u=s/steps; dir += angTo(dir,aim)*0.06 + Math.sin(u*4+ph)*0.006 + sen*0.04*u;
pos=[pos[0]+Math.cos(dir)*GRID, pos[1]+Math.sin(dir)*GRID]; C.push(pos[0],pos[1], 70, '|', shade(stalkC,rs), 'stem'); axis.push([pos[0],pos[1]]); }
const infFrac=lerp(0.20, 0.40, smooth(0.5,0.96,m)), racStart=Math.floor(steps*(1-infFrac)), span=Math.max(1, steps-1-racStart);
drawCone(axis, racStart, steps, span, spikeColor, Math.max(sen,browny*0.6), hScale, seedk);
}
if(bloomOn>0.12){ const nSpikeF=clamp(smooth(0.5,0.82,m)*(1.2+3.8*D.pups), 0, 5), nSpike=Math.ceil(nSpikeF);
for(let i=0;i<nSpike;i++){ const emerge=i===nSpike-1?clamp(nSpikeF-(nSpike-1),0.04,1):1, grow=smooth(0,1,emerge), pairIdx=Math.ceil(i/2), side=(i%2?1:-1)*D.chir;
const lean=(i===0?0:side*pairIdx*0.18), hScale=(i===0?1:1-pairIdx*0.18)*(0.92+0.16*rng('sh',seed,i)()), sx=cx + (i===0?0:side*pairIdx*15*S) + (ra()*2-1)*4;
const spikeColor=smooth(0.58,0.86,m)*grow; drawRaceme(sx, lean, hScale, spikeColor, grow, 1000+i*263, rng('rac',seed,i)); }
}
const pupN=Math.floor(smooth(0.5,0.96,m)*D.pups*3.2);
for(let p=0;p<pupN;p++){ const rp=rng('pup',seed,p), side=p%2?1:-1, sx=cx + side*(42+p*26+rp()*26)*D.stature*S, sy=cy+4*S, nl=3+Math.floor(rp()*3);
for(let k=0;k<nl;k++){ const ps=(k%2?1:-1), pm=lerp(0.2,1.0,Math.floor(k/2)/Math.max(1,Math.ceil(nl/2)-1)), pang=UP + leanTilt*0.4 + ps*lerp(0.2,0.95,pm) + (rp()*2-1)*0.1;
let dir=pang, pos=[sx,sy]; const plen=(18+24*rp())*S, pst=Math.ceil(plen/GRID);
for(let s=1;s<=pst;s++){ const u=s/pst; dir+=angTo(dir,DOWN)*0.02*u; pos=[pos[0]+Math.cos(dir)*GRID, pos[1]+Math.sin(dir)*GRID];
const px=-Math.sin(dir),py=Math.cos(dir), hw=Math.max(0.5,(1.6+2.4*D.fat)*Math.pow(1-u,0.7)*Math.sqrt(S));
for(let w=-hw;w<=hw;w+=GRID){ const edge=Math.abs(w)>hw-GRID; C.push(pos[0]+px*w,pos[1]+py*w, 38, dirSym(Math.cos(dir),Math.sin(dir)), edge?shade(toothC,rp):shade(leafM,rp), 'leaves'); }
}
}
}
}
// ===================== SETTING: Solo / Pair / Clump / Colony (hero scaled up) =====================
const roll=rng('setting',st.seed)();
const nC = roll<0.18?0 : roll<0.48?1 : roll<0.80?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);
const scale=lerp(0.52,0.34,f)*(0.9+0.2*rc()), off=side*(120+i*54+rc()*40), raise=(20+i*16+rc()*22);
comps.push({seed:cseed, artSeed:cseed, cx0:baseX+off, by:baseY-raise, S:scale, fade:lerp(0.38,0.58,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(paintAloe);
paintAloe({seed:st.seed, artSeed:st.artSeed, cx0:baseX, by:baseY, S:1.2, 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.48?'Pair' : roll<0.80?'Clump' : 'Colony';
return [
['Setting', setting],
['Leaves', Math.max(2,Math.ceil(clamp(2+env.density*15,2,19)))],
['Spikes', st.diedAt>0?'—':(env.m>=0.6 && env.rewards>0.3?'Coral':'—')],
];
}
}));
})();
;
/* species/amla.js */
/* amla — species index 12. GENERATED from prototypes/amla.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(12, spec);
/* Amla (Indian Gooseberry) — Phyllanthus emblica. A DECURRENT/weeping tree: a recursive staggered-
lateral framework with a short clear trunk that forks low into a broad LOW crown weeping to a near-
ground skirt. Clothed in airy feathery FRONDS (thin arching rachis + sparse leaflets — fern-like,
see-through). Reward: pale round translucent FRUIT hung along the outer wood (autumn/winter), which
PERSISTS through senescence as the leaflets drop → a skeletal fruit-bearing tree. Coherent WIND
flags the whole tree downwind. Deciduous. Grows in groves/orchards — faded trees stand behind.
Ported to the shared core. */
const SP = window.SEEDLING_SPECIES.find(x=>x.key==='amla');
mount(Object.assign(window.SEEDLING_toSpec(SP), {
title:'Amla', sub:'phyllanthus v2',
senFrom:0.82,
LW:{season:1.0, stem:1.2, leaves:1.0, flowers:1.7},
LS:{season:1.0, stem:1.0, leaves:0.9, flowers:0.8},
note:'Weeping decurrent tree · short trunk forks low into a broad low crown · airy feathery fronds · pale round fruit hung on the outer wood (persists as leaflets drop → skeletal fruiting tree) · wind-flagged · deciduous · groves.',
PAL:{ soil:'#6f5128', grey:'#9a9180',
bark:'#8A8577', barkDk:'#6B6658',
leafDark:'#5E7A3E', leaf:'#86A256', leafLt:'#B2C77E',
fruit:'#B9C24E', fruitLt:'#D4DA72', fruitDk:'#959D3A' },
stageName(st,env){ if(st.diedAt>0)return'Sealed'; const m=env.m;
if(env.ageF>0.87)return'Senescent'; if(m<0.12)return'Sprout'; if(m<0.40)return'Sapling';
if(m<0.62)return'Young tree'; if(m<0.82)return'Branching'; return'Fruiting'; },
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 amlaDNA(seed){ const dd=rng('dna',seed), g=()=>(dd()+dd()+dd()-1.5)/1.5;
return { dominance:0.14+0.40*dd(), divergence:0.50+0.55*dd(), droop:0.50+0.70*dd(), gnarl:0.22+0.55*dd(), taperLen:0.62+0.12*dd(),
lateralN:1.5+1.0*dd(), reach:0.80+0.45*dd(), lean:g()*0.16, stature:0.86+0.16*dd(), maxOrder:5+(dd()<0.5?1:0),
windDir:dd()<0.5?1:-1, windStr:Math.pow(dd(),2.6) }; }
const sLeaf = season==='Summer'||season==='Spring'?1 : season==='Autumn'?0.6 : 0.12;
const sFruit= season==='Autumn'?1 : season==='Winter'?0.8 : season==='Summer'?0.35 : 0.08;
// ===================== one amla tree (hero or faded background companion) =====================
function paintAmla(I){
const {seed, artSeed, cx, by, S, fade, zb, m, v, sen, browny, rewards, density} = I;
const FC = c => fade>0 ? mix(c, bgC, fade) : c;
const PUSH = (x,y,z,sym,col,layer)=>C.push(x,y,z+zb,sym,col,layer);
const D=amlaDNA(seed);
const foliageF=(1-sen)*(0.35+0.65*v)*sLeaf, fruitF=smooth(0.42,0.80,m)*rewards*sFruit;
const bark=FC(mix(P.bark,P.grey,Math.max((1-v)*0.35,sen))), barkDk=FC(mix(P.barkDk,P.grey,Math.max((1-v)*0.35,sen)));
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 fruitC=FC(mix