; ; /* ============================================================================ DEVELOPMENT — research, materials, crafting, and things that wear out ---------------------------------------------------------------------------- The Armory sells permanent percentages. Once bought they are simply true forever, which is why the store stops mattering: nothing you own is ever at stake. Development is the opposite half of the economy. MATERIALS salvaged from matches — spent, not banked forever RESEARCH a tree that unlocks WHAT you may build, never a stat by itself MODULES crafted, equipped, and visible on the machine that carries them WEAR a module is consumed by use and eventually BREAKS Wear is the whole point. A module that lasts forever is just another store entry with extra steps; one that lasts eleven matches makes you decide when to spend it. And because every equipped module puts a mark on the Commander and the HQ in the field, its loss is something you SEE — the icon goes out, the bonus goes with it, and you know exactly what changed. ============================================================================ */ /* ---- MATERIALS ------------------------------------------------------------- Four, deliberately. Enough that recipes can want different things; few enough that a player can hold all four in their head. */ const MATS={ alloy :{nm:'Alloy', em:'▬', ds:'Structural plate — salvaged from wrecks'}, circuit:{nm:'Circuitry',em:'⌗', ds:'Intact logic boards — rarer than plate'}, isotope:{nm:'Isotope', em:'☢', ds:'Reactor fuel — dropped by heavy kills'}, relic :{nm:'Relic Core',em:'◈',ds:'Pre-collapse tech — only from ruins and hives'}, }; function matBag(){ META.mats=META.mats||{alloy:0,circuit:0,isotope:0,relic:0}; return META.mats; } function matHas(cost){ const b=matBag(); for(const k in cost) if((b[k]||0)'; } function matCostStr(c){ return Object.keys(c).map(k=> ''+itemArt('mat_'+k,MATS[k].em,15)+c[k]+'').join(''); } /* What a match pays out. Scaled by the threat the player chose to fight at, so the endgame ladder feeds the crafting economy instead of running beside it. */ function fieldRecoveryFromMatch(res){ const committed=typeof matchCommitted==='function'?matchCommitted(res.win):(res.win||(stats.t|0)>=180); if(!committed) return {alloy:0,circuit:0}; const lossScale=res.win?1:0.35; const reserveAlloy=Math.min(res.win?12:5,Math.floor(Math.max(0,res.fieldMass||0)/300)*lossScale)|0; const reclaimedAlloy=Math.min(res.win?8:4,Math.floor(Math.max(0,res.reclaimed||0)/180)*lossScale)|0; return { alloy:reserveAlloy+reclaimedAlloy, circuit:Math.min(res.win?8:3,Math.floor(Math.max(0,res.fieldEnergy||0)/1400)*lossScale)|0, reserveAlloy,reclaimedAlloy, }; } function matsFromMatch(res){ if(typeof matchCommitted==='function'&&!matchCommitted(res.win)) return {alloy:0,circuit:0,isotope:0,relic:0}; const t=(typeof threatSel==='function')?threatSel():1; const s=res.win?1:0.35; const recovery=fieldRecoveryFromMatch(res); return { alloy : Math.round((8+res.kills*0.004+res.built*0.5)*s*(1+t*0.14))+recovery.alloy, circuit: Math.round((3+res.kills*0.0016)*s*(1+t*0.12))+recovery.circuit, isotope: Math.round((res.win?2:0)+res.kills*0.0009*s*(1+t*0.10)), /* Xenobiology is the RIGHT to bank Relic Cores from hives. Ungated drops made the node a displayed cost with no effect — you never needed it. */ relic : (typeof devHas==='function'&&devHas('xeno')&&res.nests>0) ? Math.round(s*(1+t*0.08)) : 0, }; } /* ---- RESEARCH -------------------------------------------------------------- Three branches, and a rule: a research node never grants a stat. It grants the RIGHT to craft something, an extra slot, or better salvage. Stats come from modules, which wear out — so power always has a running cost. */ const DEVTREE=[ {id:'metallurgy', br:'FABRICATION', nm:'Field Metallurgy', ds:'Unlocks plating modules', cost:{alloy:40}, data:6, req:[]}, {id:'optics', br:'FABRICATION', nm:'Gunnery Optics', ds:'Unlocks targeting modules', cost:{alloy:30,circuit:14}, data:10, req:['metallurgy']}, {id:'servos', br:'FABRICATION', nm:'Heavy Servos', ds:'Unlocks mobility and build modules', cost:{alloy:60,circuit:20}, data:16, req:['optics']}, {id:'slot2', br:'FABRICATION', nm:'Modular Frames', ds:'A second module slot', cost:{alloy:90,circuit:40,isotope:10}, data:24, req:['servos']}, {id:'salvage', br:'DOCTRINE', nm:'Salvage Doctrine', ds:'+35% materials from every match', cost:{alloy:25}, data:6, req:[]}, {id:'logistics', br:'DOCTRINE', nm:'Forward Logistics',ds:'Modules wear 25% slower', cost:{alloy:50,circuit:18}, data:12, req:['salvage']}, {id:'breakthrough',br:'DOCTRINE', nm:'Breakthrough Protocol', ds:'Unlocks a timed assault push for infantry, armour and anti-tank platoons', cost:{alloy:72,circuit:30,isotope:5}, data:20, req:['logistics'], art:'res_ability'}, {id:'intercept', br:'DOCTRINE', nm:'Interceptor Protocol', ds:'Unlocks high-speed interception for aircraft and anti-air screens', cost:{alloy:58,circuit:42,isotope:7}, data:22, req:['logistics'], art:'res_optics'}, {id:'refit', br:'DOCTRINE', nm:'Field Refit', ds:'Unlocks repairing worn modules', cost:{alloy:70,circuit:26}, data:18, req:['logistics']}, {id:'fieldservice',br:'DOCTRINE', nm:'Field Service Net', ds:'Unlocks a selected support platoon repair-and-shield pulse', cost:{alloy:82,circuit:48,isotope:6}, data:24, req:['refit'], art:'res_servos'}, {id:'firemission',br:'DOCTRINE', nm:'Fire Mission Protocol', ds:'Unlocks the player-commanded Charged Barrage for selected artillery', cost:{alloy:90,circuit:44,isotope:8}, data:26, req:['logistics'], art:'res_refit'}, {id:'slot3', br:'DOCTRINE', nm:'Command Retrofit', ds:'A third module slot', cost:{alloy:140,circuit:70,isotope:26,relic:4}, data:40, req:['refit','slot2']}, {id:'xeno', br:'XENOLOGY', nm:'Xenobiology', ds:'Hives drop Relic Cores', cost:{isotope:8}, data:8, req:[]}, {id:'reactor', br:'XENOLOGY', nm:'Captured Reactors',ds:'Unlocks isotope modules', cost:{isotope:22,circuit:20}, data:14, req:['xeno']}, {id:'relictech', br:'XENOLOGY', nm:'Relic Reverse-Eng',ds:'Unlocks relic modules — the strongest, and the most fragile', cost:{relic:6,circuit:50}, data:26, req:['reactor']}, {id:'ability', br:'XENOLOGY', nm:'Weaponised Relics',ds:'Unlocks craftable Commander abilities', cost:{relic:12,isotope:40}, data:42, req:['relictech']}, /* Faction doctrines are deliberately separate unlock paths. The original account tree made four armies read as different paint over the same technology. These keys are faction-prefixed so ownership can never leak between lines when player-faction selection becomes available. */ {id:'asc_siege_foundry', fac:'ascendancy', br:'FABRICATION', nm:'Siege Foundry', ds:'Dominion artillery leave factories with +12% maximum health', cost:{alloy:55,circuit:18}, data:12, req:[]}, {id:'asc_iron_discipline', fac:'ascendancy', br:'DOCTRINE', nm:'Iron Discipline', ds:'Dominion formation orders use 12% tighter spacing for groups of 4 or more', cost:{alloy:42,isotope:8}, data:14, req:['asc_siege_foundry']}, {id:'asc_crown_battery', fac:'ascendancy', br:'XENOLOGY', nm:'Crown Battery', ds:'Entering Siege overcharges artillery for 8s: cooldowns recover 28% faster for 2.5 energy/s; 18s recovery', cost:{circuit:34,isotope:18,relic:1}, data:24, req:['asc_iron_discipline']}, {id:'syn_quantum_grid', fac:'syndicate', br:'FABRICATION', nm:'Quantum Grid', ds:'Syndicate weapons, shields and active fields draw 15% less grid energy', cost:{circuit:36,isotope:14}, data:12, req:[]}, {id:'syn_drone_mesh', fac:'syndicate', br:'DOCTRINE', nm:'Drone Mesh', ds:'Syndicate scouts and aircraft mark nearby enemies for 3.5s; allied hits deal +10% damage', cost:{alloy:30,circuit:42}, data:15, req:['syn_quantum_grid']}, {id:'syn_phase_lattice', fac:'syndicate', br:'XENOLOGY', nm:'Phase Lattice', ds:'Phase Arks transfer cargo at 96 range instead of requiring a 22-range landing approach', cost:{circuit:58,isotope:24,relic:1}, data:26, req:['syn_drone_mesh']}, {id:'hor_gene_splice', fac:'horde', br:'FABRICATION', nm:'Gene Splice Pit', ds:'AI DOSSIER - future Brood player tech for biomass kill-recovery; no player combat effect while Brood is AI-only', cost:{alloy:24,isotope:16}, data:12, req:[]}, {id:'hor_synaptic_tide', fac:'horde', br:'DOCTRINE', nm:'Synaptic Tide', ds:'AI DOSSIER - Tidecasters form automatically at critical Brood mass; player unlock is reserved until Brood is playable', cost:{circuit:26,isotope:28}, data:16, req:['hor_gene_splice']}, {id:'hor_living_siege', fac:'horde', br:'XENOLOGY', nm:'Living Siege', ds:'AI DOSSIER - Massflesh is currently an AI breakthrough carrier; player unlock is reserved until Brood is playable', cost:{alloy:60,isotope:32,relic:2}, data:28, req:['hor_synaptic_tide']}, ]; function devDone(){ META.res=META.res||{}; return META.res; } function devHas(id){ return !!devDone()[id]; } function devAvail(n){ return n.req.every(devHas); } function devMissing(n){ const b=matBag(), out=[]; for(const k in n.cost){ const d=n.cost[k]-(b[k]||0); if(d>0) out.push(Math.ceil(d)+' '+MATS[k].nm); } const data=n.data-(META.researchData||0); if(data>0) out.push(Math.ceil(data)+' Research Data'); return out; } function devBuy(n,silent){ if(!n||devHas(n.id)) return false; if(typeof mfFactionTechPurchasable==='function'&&!mfFactionTechPurchasable(n.id)){ if(!silent) toast('AI DOSSIER — Brood evolution becomes researchable only when the faction is playable'); return false; } if(!devAvail(n)){ if(!silent) toast('🔒 Requires '+n.req.map(r=>DEVTREE.find(x=>x.id===r).nm).join(', ')); return false; } if((META.researchData||0)x.id===id); if(!n||devHas(id)) continue; if(devBuy(n,true)) done.push(n); else next.push(id); } META.resQueue=next; if(done.length){ metaSave(); toast('🔬 RESEARCH COMPLETE — '+done.map(n=>n.nm).join(', ')); if(typeof sfx==='function') sfx('level'); if(typeof renderDevelop==='function') renderDevelop(); if(typeof renderMetaHead==='function') renderMetaHead(); } else if(q.join('|')!==next.join('|')) metaSave(); return done; } function moduleSlots(){ return 1+(devHas('slot2')?1:0)+(devHas('slot3')?1:0); } function wearRate(){ return devHas('logistics')?0.75:1; } function matYield(){ return devHas('salvage')?1.35:1; } /* Every committed operation advances account research, including a defeat. The Research Complex remains the fastest source through studies and a live network bonus, but making it the ONLY source created a progression deadlock: a newcomer who lost the lab earned zero Data and could never improve the lab. Short surrender loops still pay nothing. */ function researchDataFromMatch(win){ const studies=Math.max(0,typeof resDone!=='undefined'?resDone|0:0); let labs=0; if((stats.t|0)>=90&&typeof blds!=='undefined') for(const B of blds) if(B.alive&&B.team===0&&B.type==='techlab'&&B.prog>=1) labs++; const studyData=studies*3; const network=Math.min(3,labs); const hold=win&&labs?2:0; const committed=typeof matchCommitted==='function'?matchCommitted(win):(win||(stats.t|0)>=180); const mission=committed?(win?4:2):0; const challenge=win?clamp(difficulty|0,0,2):0; const raw=mission+studyData+network+hold+challenge; const threat=(typeof threatSel==='function')?1+Math.max(0,threatSel()-1)*0.06:1; const rewardScale=win?1:(committed?.35:0); const total=Math.min(24,Math.round(raw*threat*rewardScale)); META.researchData=(META.researchData||0)+total; const parts=win?[['Mission recovery',mission],['Studies',studyData],['Lab network',network], ['Objective hold',hold],['Challenge',challenge]].filter(p=>p[1]>0): (committed&&total?[['Committed defeat · 35% recovery',total]]:[]); return {total,parts}; } /* ---- MODULES --------------------------------------------------------------- Each one states plainly what it does, what it costs, how long it lasts, and what mark it puts in the field. `dur` is in MATCHES: a player can read "eleven matches" and plan around it in a way that an abstract charge count never allows. */ const MODULES=[ {id:'plate', nm:'Reactive Plating', em:'🛡', col:[120,200,255], req:'metallurgy', ds:'Your units +10% health', dur:12, cost:{alloy:35}, apply:()=>{ resHpMult*=1.10; }}, {id:'optic', nm:'Targeting Uplink', em:'🎯', col:[255,190,110], req:'optics', ds:'Your units +8% damage', dur:10, cost:{alloy:24,circuit:10}, apply:()=>{ armyDmgMult+=0.08; }}, {id:'range', nm:'Sensor Mast', em:'📡', col:[150,255,200], req:'optics', ds:'Your units +10% range', dur:10, cost:{alloy:20,circuit:14}, apply:()=>{ resRngMult*=1.10; }}, {id:'tempo', nm:'Drive Governors', em:'⚙', col:[200,180,255], req:'servos', ds:'Structures build 20% faster', dur:14, cost:{alloy:40,circuit:12}, apply:()=>{ bldSpeedMult*=1.20; }}, {id:'recl', nm:'Reclaim Servos', em:'♻', col:[140,235,150], req:'servos', ds:'+40% salvage reclaimed', dur:14, cost:{alloy:45,circuit:8}, apply:()=>{ salvageMult*=1.40; }}, {id:'core', nm:'Fusion Overdraw', em:'☢', col:[255,150,90], req:'reactor', ds:'+18% energy income', dur:9, cost:{isotope:16,circuit:16}, apply:()=>{ resEnergyMult*=1.18; }}, {id:'relic', nm:'Relic Lattice', em:'◈', col:[255,120,220], req:'relictech', ds:'+15% health AND +12% damage — burns out fast', dur:5, cost:{relic:3,circuit:30}, apply:()=>{ resHpMult*=1.15; armyDmgMult+=0.12; }}, {id:'emp', nm:'EMP Charge', em:'⚡', col:[120,255,255], req:'ability', ds:'Commander ability: stun everything in a wide radius', dur:6, cost:{relic:5,isotope:30}, apply:()=>{ abUnlock[4]=true; }}, ]; function modOwned(){ META.mods=META.mods||{}; return META.mods; } function modEquipped(){ META.equip=META.equip||[]; return META.equip; } function modCraft(m){ if(!devHas(m.req)){ toast('🔒 Research '+(DEVTREE.find(r=>r.id===m.req)||{}).nm+' first'); return; } if(!matHas(m.cost)){ toast('Not enough materials'); return; } matSpend(m.cost); const o=modOwned(); /* Crafting a module you already own TOPS IT UP rather than making a second copy — inventory management is not the interesting part of this system. */ o[m.id]=Math.min((o[m.id]||0)+m.dur, m.dur*2); metaSave(); toast('🔧 CRAFTED — '+m.nm+' · '+o[m.id]+' matches of wear'); sfx('level'); buzz(25); renderDevelop(); } function modRepair(m){ if(!devHas('refit')){ toast('🔒 Research Field Refit to repair modules'); return; } const half={}; for(const k in m.cost) half[k]=Math.max(1,Math.round(m.cost[k]*0.5)); if(!matHas(half)){ toast('Repair needs '+matCostStr(half)); return; } matSpend(half); const o=modOwned(); o[m.id]=Math.min((o[m.id]||0)+Math.ceil(m.dur*0.6), m.dur*2); metaSave(); toast('🔧 Refitted — '+m.nm); sfx('ui'); renderDevelop(); } function modToggle(m){ const eq=modEquipped(), i=eq.indexOf(m.id); if(i>=0){ eq.splice(i,1); } else{ if(!(modOwned()[m.id]>0)){ toast('Craft '+m.nm+' first'); return; } if(eq.length>=moduleSlots()){ toast('All '+moduleSlots()+' slots are full — unequip one first'); return; } eq.push(m.id); } metaSave(); renderDevelop(); sfx('ui'); } /* Applied once when a match starts, after the Armory perks. */ function applyModules(){ bldSpeedMult=1; for(const id of modEquipped()){ const m=MODULES.find(x=>x.id===id); if(m&&modOwned()[id]>0) try{ m.apply(); }catch(e){} } } /* Consumed once when a match ends. Anything that hits zero BREAKS: it comes off the machine, its mark goes out, and the player is told which one. */ function wearModules(){ const o=modOwned(), eq=modEquipped(); const broke=[]; const w=wearRate(); for(let i=eq.length-1;i>=0;i--){ const id=eq[i], m=MODULES.find(x=>x.id===id); if(!m) { eq.splice(i,1); continue; } o[id]=Math.max(0,(o[id]||0)-w); if(o[id]<=0){ o[id]=0; eq.splice(i,1); broke.push(m); } } metaSave(); return broke; } /* What the field renderer asks for: the marks currently riding on the player's machines. An empty list is exactly what a broken module looks like. */ function activeModuleMarks(){ const out=[]; for(const id of modEquipped()){ const m=MODULES.find(x=>x.id===id); if(m&&modOwned()[id]>0) out.push(m); } return out; } /* ---- UI --------------------------------------------------------------------- */ let devTab='research', devBranch='FABRICATION'; function devBranchNav(){ return '
'+['FABRICATION','DOCTRINE','XENOLOGY'].map(br=>{ const ns=DEVTREE.filter(n=>n.br===br), done=ns.filter(n=>devHas(n.id)).length; return ''; }).join('')+'
'; } function devResearchExtra(n,done,open){ let h=''; if(n.req.length){ h+='
REQUIRES'+n.req.map(id=>{ const r=DEVTREE.find(x=>x.id===id); return ''+(devHas(id)?'✓ ':'')+(r?r.nm:id)+''; }).join('')+'
'; } if(open&&!done){ const miss=devMissing(n); if(miss.length) h+='
NEED '+miss.join(' · ')+'
'; } if(done){ const mods=MODULES.filter(m=>m.req===n.id); if(mods.length) h+=''; else if(n.id==='slot2'||n.id==='slot3') h+=''; } return h; } function renderDevelop(){ const scr=document.getElementById('devScr'); if(!scr) return; const b=matBag(); const mr=document.getElementById('matRow'); if(mr) mr.innerHTML=Object.keys(MATS).map(k=> '
'+itemArt('mat_'+k,MATS[k].em,20)+Math.floor(b[k]||0)+'
').join('') +'
'+Math.floor(META.researchData||0)+' DATA
' +'
'+(META.cores|0)+'
'; const tr=document.getElementById('devTabs'); if(tr){ tr.innerHTML=[['research','🔬','RESEARCH'],['craft','🔧','CRAFTING'],['loadout','★','LOADOUT']] .map(([k,e,n])=>'').join(''); tr.querySelectorAll('.tabBtn').forEach(x=>mfBindTap(x,ev=>{ ev.stopPropagation(); devTab=x.dataset.k; sfx('ui'); renderDevelop(); })); } const g=document.getElementById('devBody'); if(!g) return; let h=''; if(devTab==='research'){ const total=DEVTREE.filter(n=>devHas(n.id)).length; h+='
'+total+'/'+DEVTREE.length+'ACCOUNT RESEARCH · FIELD LABS BANK ◆ DATA
'+devBranchNav(); h+='
'+devBranch+' PATH
'; for(const n of DEVTREE.filter(x=>x.br===devBranch)){ const done=devHas(n.id), open=devAvail(n); const can=open&&!done&&(META.researchData||0)>=n.data&&matHas(n.cost); h+='
' +'
'+itemArt(n.art||'res_'+n.id,'🔬',34)+'
' +'
'+n.nm+''+n.ds+'' +'
'+(done?'RESEARCHED':matCostStr(n.cost)+' ◆ '+n.data+' DATA')+'
' +'
'+(done?'✓':open?'RESEARCH':'🔒')+'
' +devResearchExtra(n,done,open); } } else if(devTab==='craft'){ const o=modOwned(); for(const m of MODULES){ const locked=!devHas(m.req), have=o[m.id]||0; h+='
' +'
'+itemArt('mod_'+m.id,m.em,34)+'
' +'
'+m.nm+''+m.ds+'' +(locked?'
REQUIRES '+((DEVTREE.find(x=>x.id===m.req)||{}).nm||m.req)+'
':'') +'
'+matCostStr(m.cost)+'lasts '+m.dur+' matches
' +(have>0?'
':'') +'
' +'
'+(locked?'🔒':(have>0?'+'+m.dur:'CRAFT'))+'
'; } } else { const eq=modEquipped(), o=modOwned(); h+='
EQUIPPED — '+eq.length+' / '+moduleSlots()+' SLOTS
'; if(!eq.length) h+='
Nothing fitted. Equipped modules appear as marks on your Commander and HQ in the field.
'; for(const m of MODULES){ const have=o[m.id]||0; if(have<=0&&eq.indexOf(m.id)<0) continue; const on=eq.indexOf(m.id)>=0; const worn=have<=m.dur*0.25; h+='
' +'
'+itemArt('mod_'+m.id,m.em,34)+'
' +'
'+m.nm+''+m.ds+'' +'
' +'
'+Math.ceil(have)+' matches left'+(worn?' · WEARING OUT':'')+'
' +'
'+(on?'FITTED':'FIT')+'
'; if(have🔧 REFIT — '+matCostStr( Object.fromEntries(Object.entries(m.cost).map(([k,v])=>[k,Math.max(1,Math.round(v*0.5))])))+''; } } g.innerHTML=h; const bind=function(el,fn){ if(typeof mfBindTap==='function') mfBindTap(el,fn); else el.addEventListener('click',fn); }; g.querySelectorAll('[data-br]').forEach(el=>bind(el,ev=>{ ev.stopPropagation(); devBranch=el.dataset.br; sfx('ui'); renderDevelop(); })); g.querySelectorAll('[data-jump]').forEach(el=>bind(el,ev=>{ ev.stopPropagation(); devTab=el.dataset.jump; sfx('ui'); renderDevelop(); })); g.querySelectorAll('[data-r]').forEach(el=>bind(el,()=>{ const n=DEVTREE.find(x=>x.id===el.dataset.r); if(n) devBuy(n); })); g.querySelectorAll('[data-c]').forEach(el=>bind(el,()=>{ const m=MODULES.find(x=>x.id===el.dataset.c); if(m) modCraft(m); })); g.querySelectorAll('[data-e]').forEach(el=>bind(el,()=>{ const m=MODULES.find(x=>x.id===el.dataset.e); if(m) modToggle(m); })); g.querySelectorAll('[data-x]').forEach(el=>bind(el,ev=>{ ev.stopPropagation(); const m=MODULES.find(x=>x.id===el.dataset.x); if(m) modRepair(m); })); } /* Called at match end, after rewards. */ function developRecord(res){ const recovery=fieldRecoveryFromMatch(res),g=matsFromMatch(res), y=matYield(); for(const k in g) g[k]=Math.round(g[k]*y); matGrant(g); const broke=wearModules(); /* metaGrant already banked Research Data. Flush now so queued nodes spend the payout in this same debrief rather than sitting READY until tapped. */ const researched=typeof devFlushQueue==='function'?devFlushQueue():[]; return {mats:g,recovery,broke,researched}; } function initDevelop(){ matBag(); devDone(); modOwned(); modEquipped(); /* Cloud merge used to pick the career with more unused Data over the one that had already spent it on nodes. Count ownership so research persists. */ if(typeof careerWeight==='function'&&!careerWeight._devProgress){ const _cw=careerWeight; careerWeight=function(m){ let nodes=0, mats=0; if(m&&m.res) for(const k in m.res) if(m.res[k]) nodes++; if(m&&m.mats) for(const k in m.mats) mats+=(m.mats[k]|0); return _cw(m)+nodes*80+mats*2; }; careerWeight._devProgress=true; } renderDevelop(); /* Field Research Archives bank Data mid-match. Spend it into the queued node immediately so the pickup is the unlock, not a number that waits. */ if(typeof applyCrate==='function'&&!applyCrate._devFlush){ const _ac=applyCrate; applyCrate=function(k,x,y){ const before=META.researchData||0; const r=_ac(k,x,y); if((META.researchData||0)>before) devFlushQueue(); return r; }; applyCrate._devFlush=true; } }