function CrowdPanel({config,onChange,time,playing,onPlay,onStep,onReset,running,ready,report,placement,onAddZone,onZoneChange,onRemoveZone}) { const number=(label,key,min,max,step='any')=>onChange(key,Number(v))}/>; return
EVENT PLANNING

Crowd stress test

Model occupancy, body shadowing and sector demand at the selected floor.

{config.enabled&&<>
{number('People (used without zones)','count',0,5000,1)} onChange('layout',v)}> {number('Active phones %','activePercent',0,100,1)}{number('Demand per active user Mbps','demandMbps',.01,1000,.1)} {number('Walking speed m/s','speed',0,3,.1)}{number('Random seed','seed',0,4294967295,1)}
Simulation time {time.toFixed(0)} s
Up to 5,000 explicit people per scenario. Playback advances one simulated second after each completed coverage calculation. Its speed depends on scene size; people and heatmaps represent the same snapshot. Changing crowd settings resets placement and time. {placement&&

{placement.people.length.toLocaleString()} placed / {placement.requested.toLocaleString()} requested{placement.unplaced?` · ${placement.unplaced} could not be placed on walkable floor`:''}. Orange: active phone. Teal: inactive.

}
Population zonesZone counts replace the global population. Each rectangle holds its own crowd; walking stays inside that zone. Overlapping zones add their populations. XY coordinates are in model metres. {config.zones.map((zone,i)=>
Zone {i+1}{[['Min X','minX'],['Max X','maxX'],['Min Y','minY'],['Max Y','maxY'],['People','count']].map(([label,key])=>onZoneChange(zone.id,key,v)}/>)}
)}
Body attenuation and phone interference
{number('Body attenuation dB per crossing','bodyLossDb',0,30,.5)}{number('Body radius m','bodyRadius',.05,.5,.01)}{number('Body height m','bodyHeight',.5,2.5,.05)}{number('Maximum cumulative body loss dB','bodyLossCapDb',0,60,1)} {number('Phone total EIRP dBm','phonePowerDbm',-40,30,1)}{number('Phone occupied bandwidth MHz','phoneBandwidthMHz',.1,400,1)}{number('Phone duty cycle %','phoneDutyPercent',0,100,1)}{number('Phone / receiver band overlap %','phoneOverlapPercent',0,100,1)}
Phone overlap defaults to 0% for isolated cellular uplink/downlink. Set overlap only for a cochannel or cross-link stress scenario. Active phones are aggregated into source tiles using free-space propagation; this is a conservative interference approximation. Body loss is an adjustable empirical assumption, not a universal human attenuation value.
}
SNR / SINR settings
{number('Receiver noise figure dB','noiseFigureDb',0,30,.5)}{number('NR subcarrier spacing kHz','subcarrierKhz',15,240,15)}{number('Wi-Fi channel bandwidth MHz','wifiBandwidthMHz',1,320,1)}{number('SNR / SINR pass threshold dB','qualityThresholdDb',-30,50,1)}{number('Minimum admission SINR dB','minSinrDb',-30,50,1)}
SNR uses thermal noise at 290 K. SINR also sums other sectors in the same cochannel group and the configured phone interference. Cellular noise is evaluated over one reference subcarrier (LTE 15 kHz); Wi-Fi uses its channel bandwidth.
{config.enabled&&report&&

Sector / nodeB capacity at {time.toFixed(0)} s

{report.population} people · {report.active} active · {report.blocked} blocked by user limits · {report.poorSignal} below admission SINR · {report.uncovered} without a serving cell

{report.rows.map(r=>r.capacityMbps?'overloaded':''}>)}
Sector / nodeBAttachedActive / limitDemand / budgetAdmittedBlockedServedPer admitted user
{r.label}{r.attached}{r.active} / {r.maxUsers}{r.demandMbps.toFixed(1)} / {r.capacityMbps} Mbps{r.admitted}{r.blocked}{r.servedMbps.toFixed(1)} Mbps{r.perUserMbps.toFixed(2)} Mbps
Each transmitter is one independent sector/nodeB budget. Association uses strongest received power; no load-balancing handover or scheduler emulation. Throughput is a demand/budget allocation, not a PHY throughput prediction.
}
; }