memoscan
← all memos

Memo 0x142aa6ac…35f987 on Ethereum

t templated, so nudge the base // x by a seed-stable, bell-weighted amount (mostly small, occasional larger) — the whole plant + // its companions shift together; the full-width ground line stays, so it just sits left/right of // centre. Bounded so wide canopies don't clip. Species can opt out / override via spec.basePlace. const baseDX = (spec.basePlace===false ? 0 : gauss(rng('placeX',st.seed)) * (spec.basePlace||45)); const env={ageF,m,density,structure:structureOf(spec,st),rho,rewards,reproOK,v,dead,sen,dormancy, season:seasonOf(st.now),browny:clamp(Math.max(1-v,sen),0,1),now:st.now,W,H,baseX:BASEX+baseDX,baseY:BASEY}; env.pollinators=pollinatorsOf(spec,st,env); return env; } function renderPlant(spec, st){ const env=computeEnv(spec,st); const C=makeCanvas(); spec.draw(st,C,env); let soil=''; if(!spec.noSoil){ // species can opt out of the drawn ground line const soilC=mix((spec.PAL&&spec.PAL.soil)||'#6f5128','#9a9180',0.25); for(let i=0;i<30;i++){ const x=40+i*((W-80)/29), yy=BASEY+16+Math.sin(i*1.7)*3; soil+=glyph('.',x,yy,GLY,soilC,1.1); } } const sc=schemeFor(st.seed); const plant=C.resolve(sc, rng('struct',st.artSeed), spec.LW||DEFLW, spec.schemeExempt||['flowers'], spec.LS||DEFLS); // ecosystem visitors: drawn ABOVE the plant in negative space; never the plant body (§6.3) let poll=''; if(env.pollinators.length && !spec.placesPollinators){ // species may place its own instead placePollinators(C, env.pollinators, st); // anchor to this species' actual blooms for(const p of env.pollinators) poll+=drawPollinator(p,env); } const inner=soil+plant+(poll?`<g id="lyr-pollinators">${poll}</g>`:''); return framed(BACKDROPS[backdropFor(st.seed)], inner); } // ---------- common stage / traits ---------- function defaultStage(spec, st, env){ if(st.diedAt>0) return 'Sealed'; const m=env.m, age=env.ageF; if(age>0.85) return 'Senescent'; if(m<0.12) return 'Sprout'; if(m<0.4) return 'Young'; if(m<0.62) return 'Developing'; if(m<0.82) return 'Mature'; return 'Full'; } function commonTraits(spec, st, env){ const stage = spec.stageName ? spec.stageName(st,env) : defaultStage(spec,st,env); const rows = [ ['Backdrop', BACKDROP_NAMES[backdropFor(st.seed)]], ['Scheme', SchemeName(schemeFor(st.seed))], ['Stage', stage], ['Vitality', Math.round(env.v*100)+'%'], ['Devotion', st.entryCount>=20?'High':st.entryCount>=8?'Medium':'Low'], ['Never wilted', st.everWilted?'No':'Yes'], ]; const ps=env.pollinators||[]; // earned via public love (>10 notes, in season) // exact tokenURI attribute names — match ART_SPEC §8 / CONTRACT_SPEC §8.5 so the contract emits these const cap=s=>s.charAt(0).toUpperCase()+s.slice(1); rows.push(['Pollinator', ps.length ? [...new Set(ps.map(p=>p.type))].map(cap).join('/') : 'None']); rows.push(['Pollinator Count', String(ps.length)]); const morph=ps.map(p=>p.morph).find(Boolean); // rare collectible variant, if any rows.push(['Pollinator Morph', morph?cap(morph):'None']); return rows; } // ============================================================ // mount(spec): inject chrome + wire the controller // ============================================================ function mount(spec){ document.title='Seedling · '+spec.title; const accent=(spec.PAL&&spec.PAL.accent)||'#7FA66B'; document.body.innerHTML=` <style> :root{ --bg:#11140f; --panel:#1b1f17; --line:#2c3322; --ink:#e9e6d8; --muted:#9aa088; --accent:${accent}; } *{box-sizing:border-box} body{margin:0;background:var(--bg);color:var(--ink); font:14px/1.5 ui-sans-serif,system-ui,sans-serif;display:flex;min-height:100vh} .stage{flex:1;display:flex;align-items:center;justify-content:center;padding:22px} .stage svg{height:min(90vh,880px);width:auto;box-shadow:0 20px 60px rgba(0,0,0,.45)} .panel{width:340px;background:var(--panel);border-left:1px solid var(--line);padding:20px;overflow-y:auto;display:flex;flex-direction:column;gap:16px} h1{font-size:15px;margin:0;letter-spacing:.05em;text-transform:uppercase} h1 small{display:block;color:var(--muted);font-size:11px;letter-spacing:.12em;margin-top:4px} .group{border:1px solid var(--line);border-radius:11px;padding:13px} .group h2{font-size:10.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--muted);margin:0 0 9px} textarea{width:100%;min-height:52px;resize:vertical;background:#0e120b;color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:8px;font:inherit} .btns{display:flex;flex-wrap:wrap;gap:7px;margin-top:9px} button{background:#23291b;color:var(--ink);border:1px solid var(--line);padding:8px 11px;border-radius:8px;cursor:pointer;font:inherit;transition:.15s} button:hover{border-color:var(--accent);background:#2b3322} button.primary{background:var(--accent);color:#10140c;border-color:var(--accent);font-weight:600} button:disabled{opacity:.4;cursor:not-allowed} .stat{display:flex;justify-content:space-between;align-items:center;margin:6px 0;font-size:13px} .stat b{font-variant-numeric:tabular-nums} .bar{height:7px;background:#0e120b;border-radius:5px;overflow:hidden;margin-top:3px} .bar>i{display:block;height:100%;border-radius:5px;transition:width .35s} .meta{display:grid;grid-template-columns:1fr 1fr;gap:5px 12px;font-size:12.5px} .pill{display:inline-block;padding:2px 9px;border-radius:20px;font-size:11px;background:#0e120b;border:1px solid var(--line);color:var(--muted)} .pill.alive{color:#9fd68a;border-color:#3c5a30}.pill.dead{color:#d98a8a;border-color:#5a3030} .traits{display:flex;flex-wrap:wrap;gap:6px}.traits .t{font-size:11px;padding:3px 8px;border-radius:6px;background:#0e120b;border:1px solid var(--line)} .note{color:var(--muted);font-size:11px;line-height:1.5} a.dl{color:var(--accent);font-size:12px;text-decoration:none} </style> <div class="stage"><div id="art"></div></div> <div class="panel"> <h1>Seedling <small>${spec.title} · ${spec.sub||'v1'}</small></h1> <div class="group"><h2>Write an entry (waters the plant)</h2> <textarea id="entry" placeholder="Write today's thought… what you write shapes the plant."></textarea> <div class="btns"><button class="primary" id="write">✎ Write entry</button> <button id="w7">+1 week</button><button id="w30">+1 month</button></div> ${spec.note?`<div class="note" style="margin-top:8px">${spec.note}</div>`:''} </div> <div class="group"><h2>The three inputs</h2> <div class="stat"><span>Maturity</span><b id="mVal">0%</b></div><div class="bar"><i id="mBar" style="width:0%;background:var(--accent)"></i></div> <div class="stat"><span>Vitality</span><b id="vVal">100%</b></div><div class="bar"><i id="vBar" style="width:100%;background:#E8A200"></i></div> <div class="stat"><span>Age</span><b id="aVal">0%</b></div><div class="bar"><i id="aBar" style="width:0%;background:#c47a4a"></i></div> </div> <div class="group"><h2>State</h2> <div class="meta"><span>Stage</span><b id="stage">—</b><span>Entries</span><b id="entries">0</b> <span>Age</span><b id="age">0 d</b><span>Season</span><b id="season">—</b> <span>Status</span><span id="status" class="pill alive">alive</span></div> </div> <div class="group"><h2>Live traits</h2><div class="traits" id="traits"></div></div> <div class="btns"><button id="play" disabled>▶ Memorial</button><button id="reset">↺ New seed</button></div> <a class="dl" id="download" href="#">⤓ Download SVG</a> </div>`; const LIFESPAN=spec.LIFESPAN, DECAY=spec.DECAY||1/21; let st, history; const $=id=>document.getElementById(id); function fresh(seed){ st={seed,artSeed:seed,entryCount:0,now:0,lastWatered:0,vitality:1, wateredEpochs:0,lastWaterWeek:-1,diedAt:0,everWilted:false}; history=[snap()]; } function snap(){ return {now:st.now,entryCount:st.entryCount,vitality:st.vitality, wateredEpochs:st.wateredEpochs,artSeed:st.artSeed,diedAt:st.diedAt}; } function paint(s){ $('art').innerHTML=renderPlant(spec, s||st); } function refresh(){ paint(); const env=computeEnv(spec,st), age=clamp(st.now/LIFESPAN,0,1); $('mVal').textContent=Math.round(env.m*100)+'%'; $('mBar').style.width=(env.m*100)+'%'; $('vVal').textContent=Math.round(st.vitality*100)+'%'; $('vBar').style.width=(st.vitality*100)+'%'; $('aVal').textContent=Math.round(age*100)+'%'; $('aBar').style.width=(age*100)+'%'; $('stage').textContent=spec.stageName?spec.stageName(st,env):defaultStage(spec,st,env); $('entries').textContent=st.entryCount; $('age').textContent=Math.round(st.now)+' d'; $('season').textContent=seasonOf(st.now); const dead=st.diedAt>0, status=$('status'); status.textContent=dead?('sealed · '+st.deathCause):'alive'; status.className='pill '+(dead?'dead':'alive'); $('play').disabled=!dead; ['write','w7','w30'].forEach(id=>$(id).disabled=dead); const tw=$('traits'); tw.innerHTML=''; const tr=commonTraits(spec,st,env).concat(spec.traits?spec.traits(st,env):[]); tr.forEach(([k,val])=>{const e=document.createElement('span');e.className='t';e.innerHTML=`<b>${k}</b> ${val}`;tw.appendChild(e);}); const blob=new Blob([renderPlant(spec,st)],{type:'image/svg+xml'}); $('download').href=URL.createObjectURL(blob); $('download').download='seedling-'+spec.title.toLowerCase().replace(/\W+/g,'-')+'.svg'; } function checkDeath(){ if(st.diedAt>0)return; // §3.2: deathTime monotonic in watered weeks — never moves earlier, so no retroactive death. const dt=deathTimeOf(spec,st); if(st.now>=dt){ st.diedAt=st.now; st.deathCause = spec.MONOCARPIC ? 'MonocarpicBloom' : (consistency(st)<0.6 ? 'CareShortened' : 'FullTerm'); } } function writeEntry(){ if(st.diedAt>0)return; const txt=$('entry').value.trim()||('e'+(st.entryCount+1)+'-'+Math.random()); st.artSeed=hashStr(st.artSeed+'|'+txt+'|'+st.entryCount).toString(16).padStart(8,'0'); st.entryCount++; st.lastWatered=st.now; st.vitality=1; const wk=Math.floor(st.now/EPOCH); // distinct watered weeks (anti-gaming) if(wk!==st.lastWaterWeek){ st.wateredEpochs++; st.lastWaterWeek=wk; } $('entry').value=''; history.push(snap()); refresh(); } function advance(d){ if(st.diedAt>0)return; st.now+=d; st.vitality=vitalityOf(spec,st); // §3.1 exponential recency decay if(st.vitality<0.25)st.everWilted=true; checkDeath(); history.push(snap()); refresh(); } let raf=null; function memorial(){ if(history.length<2)return; cancelAnimationFrame(raf); const F=history,n=F.length,DUR=5200,t0=performance.now(); (function step(t){ const p=clamp((t-t0)/DUR,0,1), fp=p*(n-1), i=Math.floor(fp), f=fp-i; const A=F[i],B=F[Math.min(i+1,n-1)]; paint({seed:st.seed,artSeed:(f<0.5?A.artSeed:B.artSeed),entryCount:lerp(A.entryCount,B.entryCount,f), now:lerp(A.now,B.now,f),vitality:lerp(A.vitality,B.vitality,f), wateredEpochs:lerp(A.wateredEpochs,B.wateredEpochs,f),lastWatered:lerp(A.now,B.now,f), diedAt:(p>=1?st.diedAt:0)}); if(p<1)raf=requestAnimationFrame(step); else refresh(); })(t0); } function newSeed(){ fresh('0x'+Math.floor(Math.random()*0xffffffff).toString(16).padStart(8,'0')); refresh(); } $('write').onclick=writeEntry; $('w7').onclick=()=>advance(7); $('w30').onclick=()=>advance(30); $('reset').onclick=newSeed; $('play').onclick=memorial; $('entry').addEventListener('keydown',e=>{if(e.key==='Enter'&&(e.metaKey||e.ctrlKey))writeEntry();}); // expose for headless/preview harnesses window.__seedling={spec,renderPlant,maturity,computeEnv,get st(){return st;},set st(x){st=x;},refresh,fresh,schemeFor,settingName:null}; newSeed(); // URL params for gallery/preview: ?seed=..&e=entries&d=days&v=vitality&bare(hide panel) const q=new URLSearchParams(location.search); if(q.has('e')||q.has('seed')){ fresh(q.get('seed')||st.seed); st.entryCount=q.has('e')?+q.get('e'):20; st.now=q.has('d')?+q.get('d'):220; st.lastWatered=st.now; st.vitality=q.has('v')?+q.get('v'):0.92; const weeks=Math.max(1,Math.floor(st.now/EPOCH)); // assume ≈1 watering/week up to entryCount st.wateredEpochs=q.has('w')?+q.get('w'):clamp(st.entryCount,0,weeks); st.lastWaterWeek=Math.floor(st.now/EPOCH); st.notes=q.has('notes')?+q.get('notes'):0; // public notes → pollinators (§6.4) if(st.now>=deathTimeOf(spec,st)){st.diedAt=st.now;st.deathCause='FullTerm';} refresh(); } if(q.has('bare')){ document.querySelector('.panel').style.display='none'; const stg=document.querySelector('.stage'); stg.style.padding='0'; document.querySelector('.stage svg').style.height='100vh'; } // Studio/export support: when embedded in an iframe, PUSH the rendered SVG to the parent via // postMessage. Origin-independent — works on file:// and in Safari, where the parent reading this // iframe's contentDocument cross-origin throws SecurityError. token echoes ?_= so the parent can // match this exact frame; harmless when no parent is listening. try{ if(window.parent && window.parent!==window){ const tok=q.get('_')||''; const emit=()=>{ const sv=document.querySelector('#art svg'); if(sv) window.parent.postMessage({seedlingSVG:1, token:tok, svg:sv.outerHTML}, '*'); }; emit(); requestAnimationFrame(emit); } }catch(e){} } // ---------- exports ---------- const __api={ EPOCH,K_DEV,clamp,lerp,smooth,rng,hashStr,seasonOf, consistency,maturity,structureOf,densityOf,densityCap,deathTimeOf, vitalityOf,dormancyOf,pollinatorsOf,drawPollinator,computeEnv }; // headless (Node simulation harness): if(typeof module!=='undefined'&&module.exports) module.exports=__api; // browser namespace (so self-contained species can converge onto the corrected math // without colliding with their own local helpers — see e.g. sunflower.html): if(typeof window!=='undefined') window.SeedlingCore=__api; ; /* boot.js */ /* ============================================================ Seedling — on-chain engine BOOT entrypoint. Concatenated LAST into the engine bundle (after species-params.js, seedling-core.js, and the per-species registry files). Defines the single symbol the on-chain renderer calls: SeedlingRenderer.boot(state) `state` is the JSON object SeedlingRenderer.sol injects, mirroring the clone's DiaryState (all times in SECONDS, seed/artSeed as 0x-hex): { tokenId, species, seed, artSeed, plantedAt, lastWatered, diedAt, entryCount, wateredEpochs, deathTime } boot() maps that to the engine's `st` (which counts in DAYS, EPOCH=7), then renders + loops the life-cycle animation into the page. ============================================================ */ (function () { var DAY = 86400; var EPOCH_DAYS = 7; // species index -> spec-extension ({ title, PAL, LW, LS, draw, stageName, … }), // populated by the bundled bundle/species/*.js files (extracted from the prototypes). var REGISTRY = {}; function registerSpecies(idx, ext) { REGISTRY[idx] = ext; } // contract seconds -> engine st (DAYS). Mirrors the known-good prototype harness st. function toEngineState(state, nowSec) { var planted = Number(state.plantedAt) || 0; var ageDays = Math.max(0, (nowSec - planted) / DAY); var deathDays = planted > 0 ? Math.max(1, (Number(state.deathTime) - planted) / DAY) : 365; var lastWaterDays = Number(state.lastWatered) > planted ? (Number(state.lastWatered) - planted) / DAY : ageDays; var st = { seed: state.seed, artSeed: state.artSeed || state.seed, entryCount: Number(state.entryCount) || 0, wateredEpochs: Number(state.wateredEpochs) || 0, now: ageDays, lastWatered: lastWaterDays, lastWaterWeek: Math.floor(lastWaterDays / EPOCH_DAYS), vitality: 1, notes: 0, diedAt: 0, deathCause: null, }; if (Number(state.diedAt) > 0) { st.diedAt = Math.max(0, (Number(state.diedAt) - planted) / DAY); st.deathCause = "FullTerm"; } return { st: st, deathDays: deathDays }; } // Each species file registers its FULLY-RESOLVED spec (the prototype already merges // SEEDLING_toSpec(SP) before registering), so boot just hands it to the engine as-is. function specFor(species) { return REGISTRY[species] || null; } function mountTarget() { var el = document.getElementById("art"); if (!el) { el = document.createElement("div"); el.id = "art"; document.body.appendChild(el); } // Fill the frame; the injected SVG is styled (below) to letterbox into ANY aspect. // OpenSea renders animation_url in a SQUARE, but the art is portrait (720×980) — so the // whole plant must fit via preserveAspectRatio=meet instead of overflowing → no scroll/crop. el.style.cssText = "width:100vw;height:100vh;overflow:hidden"; if (!document.getElementById("seedling-fit")) { var s = document.createElement("style"); s.id = "seedling-fit"; // Contain the whole portrait card into any frame; leftover space is TRANSPARENT (takes the // host background — dark on OpenSea) so there are never cream bars. Portrait frames get no bars. s.textContent = "html,body{margin:0;background:transparent}#art{background:transparent}#art svg{display:block;width:100%;height:100%}"; document.head.appendChild(s); } return el; } // Honest placeholder until a species' draw() is registered (extraction is the remaining // art work). Keeps the bundle runnable + the on-chain pipeline demonstrable today. function placeholder(target, species, st) { target.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 600" style="width:100%;height:100%">' + '<rect width="600" height="600" fill="#f3efe6"/>' + '<text x="300" y="290" text-anchor="middle" font-family="Georgia,serif" font-size="26" fill="#2c3a2c">Seedling engine</text>' + '<text x="300" y="330" text-anchor="middle" font-family="monospace" font-size="16" fill="#5a6a5a">species ' + species + " · entries " + st.entryCount + "</text></svg>"; } function boot(state) { var target = mountTarget(); var spec = specFor(Number(state.species)); var planted = Number(state.plantedAt) || 0; var nowSec = planted > 0 ? Math.floor(Date.now() / 1000) : 0; // Static frame (used when the species engine isn't bundled yet, or for a still). var mapped = toEngineState(state, nowSec || planted); if (!spec || typeof renderPlant !== "function") { placeholder(target, Number(state.species), mapped.st); return; } // One-way growth to NOW, then HOLD (art §7.5, revised). We sweep sprout → the plant's // CURRENT age only — never fast-forward past it — so the art shows real growth-so-far and // then freezes on the present frame. That held "now" frame is the still marketplaces poster // (the "last image"); a living plant never renders senescence/death. For the pre-reveal // teaser the contract feeds a lush mid-life state, so this grows a full green canopy + holds. var DURATION = 4200; // ms sprout→now sweep (short so the held frame settles fast) var targetDays = Math.max(0, mapped.st.now); // grow only up to the current age (NOW), never beyond var start = null; function frame(ts) { if (start === null) start = ts; var p = Math.min(1, (ts - start) / DURATION); var eased = p * p * (3 - 2 * p); // smoothstep — gentle sprout, easy settle var anim = toEngineState(state, planted + eased * targetDays * DAY).st; target.innerHTML = renderPlant(spec, anim); if (p < 1) requestAnimationFrame(frame); // stop at NOW → holds the last image (no loop, no death) } requestAnimationFrame(frame); } // 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