/* ============================================================================ BROOD CROWD — RENDER-ONLY SWARM DENSITY ---------------------------------------------------------------------------- These bodies are deliberately NOT units. They never enter the simulation, pathfinder, selection, targeting, fog, save, replay or network state. A visible real Brood ground unit is the only anchor allowed to submit one deterministic cluster, so crowd art cannot reveal a hidden army or create a second source of combat truth. Three LOD meshes keep the illusion honest. Close view adds one subordinate crawler; tactical view adds a three-body knot; distant view adds four very cheap silhouettes beneath the strategic icon tier. Every tier remains one InstMesh draw, and one global frame budget is shared across all three. ============================================================================ */ let MF_BROOD_CROWD_STREAMS=null; let MF_BROOD_CROWD_EPOCH=-1; let MF_BROOD_CROWD_FRAME={time:0,span:0,perf:1,diff:1,seed:0,budget:0,density:0,queued:0}; let MF_BROOD_CROWD_STAT={ enabled:false,quality:'high',budget:0,visibleAnchors:0,eligibleAnchors:0, clusterInstances:0,visualBodies:0,drawCalls:0,triangles:0, skippedDensity:0,skippedBudget:0,near:0,mid:0,far:0 }; function mfBroodCrowdHash(v){ v=(v|0)^0x6d2b79f5; v=Math.imul(v^(v>>>15),v|1); v^=v+Math.imul(v^(v>>>7),v|61); return (v^(v>>>14))>>>0; } function mfBroodCrowdStringHash(s){ s=String(s||''); let h=2166136261; for(let i=0;i>>0; } /* One crawler is intentionally less detailed than a real Skitterling. Its small subordinate silhouette prevents a cosmetic body being mistaken for a target with its own health, while CHITIN/LEAF keep the live organic shader response and animation instead of introducing a flat sprite species. */ function mfBroodCrowdCrawler(m,x,z,yaw,scale,legs){ m.translate(x,0,z);m.rotateY(yaw);m.scale3d(scale,scale,scale); m.mat(MAT.CHITIN).team(0); m.sphere(-.25,.72,0,1.34,5,CHIT_D,.55,false); m.sphere(1.02,.64,0,.72,4,CHITIN,.66,false); m.sphere(-1.22,.65,0,.62,4,CHIT_D,.58,false); /* A small team-bearing dorsal plate reads allegiance, but most of the body keeps authored chitin colour so a dense group does not become a neon blob. */ m.mat(MAT.CHITIN).team(1); m.sphere(-.15,1.19,0,.48,4,BIO_TEAM,.34,true); m.team(0).mat(MAT.LEAF); const n=Math.max(1,legs|0); for(let k=0;k0,quality,budget,visibleAnchors:0,eligibleAnchors:0, clusterInstances:0,visualBodies:0,drawCalls:0,triangles:0, skippedDensity:0,skippedBudget:0,near:0,mid:0,far:0 }; if(!mfBroodCrowdInit()){ MF_BROOD_CROWD_STAT.enabled=false; return false; } MF_BROOD_CROWD_STREAMS.near.clear(); MF_BROOD_CROWD_STREAMS.mid.clear(); MF_BROOD_CROWD_STREAMS.far.clear(); return true; } /* Called only after the real anchor passed camera and fog visibility gates. isBrood must come from the resolved faction kit, never from an assumed team number: an AI commander can field the Brood kit in an ordinary seat. */ function mfBroodCrowdQueue(unitIndex,type,x,y,h,yaw,T,teamColour,uLod,iconQ,moving,isBrood){ MF_BROOD_CROWD_STAT.visibleAnchors++; if(!MF_BROOD_CROWD_STAT.enabled||!isBrood||!T||T.air||T.naval||T.cat==='hero'||T.hero||T.size>24) return false; MF_BROOD_CROWD_STAT.eligibleAnchors++; const F=MF_BROOD_CROWD_FRAME; const hv=mfBroodCrowdHash((unitIndex|0)^Math.imul((type|0)+17,0x45d9f3b)^F.seed); if((hv&65535)/65535>F.density){ MF_BROOD_CROWD_STAT.skippedDensity++; return false; } if(F.queued>=F.budget){ MF_BROOD_CROWD_STAT.skippedBudget++; return false; } const iq=Number.isFinite(iconQ)?iconQ:0; let tier; if(iq>.12||F.span>1800||uLod===1) tier='far'; else if(F.span>760) tier='mid'; else tier='near'; /* Close view is where a tethered cosmetic body is easiest to notice. Keep only one in five eligible anchors there; mid/far carry the mass fantasy. */ if(tier==='near'&&((hv>>>16)%5)!==0){ MF_BROOD_CROWD_STAT.skippedDensity++; return false; } const stream=MF_BROOD_CROWD_STREAMS[tier],tc=teamColour||[190,92,230]; const jitter=.88+((hv>>>24)&255)/255*.22; const sc=Math.max(.68,Math.min(1.28,(T.size||12)/16))*jitter; const turn=((hv>>>8)&1023)/1023*6.2831853; const anim=tier==='far'||F.perf<.36?0:F.time*(moving?6.2:2.05)+(hv&2047)*.0137; stream.add(x,y,h,sc,yaw+turn,tc[0],tc[1],tc[2],255,sc,anim,0); F.queued++; MF_BROOD_CROWD_STAT.clusterInstances++; MF_BROOD_CROWD_STAT.visualBodies+=MF_BROOD_CROWD_STREAMS.bodies[tier]; MF_BROOD_CROWD_STAT.triangles+=MF_BROOD_CROWD_STREAMS.tris[tier]; MF_BROOD_CROWD_STAT[tier]++; return true; } function mfBroodCrowdFlush(){ if(!MF_BROOD_CROWD_STREAMS||typeof gl==='undefined'||!gl) return; for(const tier of ['near','mid','far']){ const stream=MF_BROOD_CROWD_STREAMS[tier]; if(stream.n){ MF_BROOD_CROWD_STAT.drawCalls++; stream.flush(gl); } } } function mfBroodCrowdStats(){ return Object.assign({},MF_BROOD_CROWD_STAT); }