Spaces:
Paused
Paused
| // HEL (High-Energy Laser) Physics and Computation Module | |
| APP.core.hel = {}; | |
| // Get current knob values from UI | |
| APP.core.hel.getKnobs = function () { | |
| const { $ } = APP.core.utils; | |
| const helPower = $("#helPower"); | |
| const helAperture = $("#helAperture"); | |
| const helM2 = $("#helM2"); | |
| const helJitter = $("#helJitter"); | |
| const helDuty = $("#helDuty"); | |
| const helMode = $("#helMode"); | |
| const atmVis = $("#atmVis"); | |
| const atmCn2 = $("#atmCn2"); | |
| const seaSpray = $("#seaSpray"); | |
| const aoQ = $("#aoQ"); | |
| const rangeBase = $("#rangeBase"); | |
| if (!helPower) return {}; | |
| return { | |
| PkW: +helPower.value, | |
| aperture: +helAperture.value, | |
| M2: +helM2.value, | |
| jitter_urad: +helJitter.value, | |
| duty: (+helDuty.value) / 100, | |
| mode: helMode ? helMode.value : "cw", | |
| vis_km: +atmVis.value, | |
| cn2: +atmCn2.value, | |
| spray: +seaSpray.value, | |
| ao: +aoQ.value, | |
| baseRange: +rangeBase.value | |
| }; | |
| }; | |
| // Compute max power at target considering atmospheric effects | |
| APP.core.hel.maxPowerAtTarget = function (range_m) { | |
| const k = APP.core.hel.getKnobs(); | |
| if (!k.PkW) return { Ptar: 0, Pout: k.PkW || 0, trans: 0, turb: 0, beam: 0 }; | |
| const { clamp } = APP.core.utils; | |
| // Atmospheric transmission (Beer-Lambert approximation) | |
| const sigma_km = 3.912 / Math.max(1, k.vis_km); | |
| const range_km = range_m / 1000; | |
| const trans = Math.exp(-sigma_km * range_km); | |
| // Turbulence factor (simplified Cn² model) | |
| const cn2_factor = clamp(1 - (k.cn2 / 10) * 0.3, 0.5, 1); | |
| const ao_correction = 1 + (k.ao / 10) * 0.25; | |
| const turb = cn2_factor * ao_correction; | |
| // Sea spray attenuation | |
| const spray_factor = 1 - (k.spray / 10) * 0.15; | |
| // Beam quality degradation with range | |
| const beam_spread = 1 + (k.M2 - 1) * (range_km / 5); | |
| const jitter_loss = 1 / (1 + (k.jitter_urad / 10) * (range_km / 3)); | |
| const beam = jitter_loss / beam_spread; | |
| // Total power at target | |
| const Pout = k.PkW * k.duty; | |
| const Ptar = Pout * trans * turb * spray_factor * beam; | |
| return { | |
| Ptar: Math.max(0, Ptar), | |
| Pout, | |
| trans, | |
| turb: turb * spray_factor, | |
| beam | |
| }; | |
| }; | |
| // Estimate required power based on target features | |
| APP.core.hel.requiredPowerFromFeatures = function (feat) { | |
| if (!feat) return 35; // Default | |
| let base = 30; // kW base requirement | |
| // Adjust based on material | |
| const material = (feat.material || "").toLowerCase(); | |
| if (material.includes("metal") || material.includes("aluminum")) base *= 1.2; | |
| if (material.includes("composite") || material.includes("carbon")) base *= 0.8; | |
| if (material.includes("plastic") || material.includes("polymer")) base *= 0.6; | |
| // Adjust based on reflectivity | |
| const refl = feat.reflectivity; | |
| if (typeof refl === "number") { | |
| base *= (1 + refl * 0.5); // Higher reflectivity = more power needed | |
| } | |
| // Adjust based on size | |
| const size = feat.physical_size; | |
| if (typeof size === "number") { | |
| base *= Math.max(0.5, Math.min(2, size / 2)); // Assuming size in meters | |
| } | |
| return Math.round(base); | |
| }; | |
| // Calculate required dwell time | |
| APP.core.hel.requiredDwell = function (range_m, reqP_kW, maxP_kW, baseDwell_s) { | |
| if (maxP_kW <= 0) return Infinity; | |
| if (maxP_kW >= reqP_kW) return baseDwell_s; | |
| // Dwell inflates quadratically as power drops below requirement | |
| const ratio = reqP_kW / maxP_kW; | |
| return baseDwell_s * ratio * ratio; | |
| }; | |
| // Calculate probability of kill | |
| APP.core.hel.pkillFromMargin = function (margin_kW, baseDwell_s, reqDwell_s) { | |
| if (margin_kW <= 0) return 0; | |
| if (reqDwell_s <= 0) return 0; | |
| const dwellRatio = baseDwell_s / reqDwell_s; | |
| const marginFactor = Math.min(1, margin_kW / 50); // Normalize margin | |
| return Math.min(0.99, dwellRatio * marginFactor * 0.95); | |
| }; | |
| // Recompute HEL synthesis for all detections | |
| APP.core.hel.recomputeHEL = async function () { | |
| const { state } = APP.core; | |
| const { log } = APP.ui.logging; | |
| const knobs = APP.core.hel.getKnobs(); | |
| if (!state.detections || state.detections.length === 0) return; | |
| for (const det of state.detections) { | |
| // Get range from GPT or use baseline | |
| const range = det.gpt_distance_m || knobs.baseRange || 1500; | |
| // Compute power at target | |
| const power = APP.core.hel.maxPowerAtTarget(range); | |
| det.maxP_kW = power.Ptar; | |
| // Required power from features | |
| det.reqP_kW = APP.core.hel.requiredPowerFromFeatures(det.features); | |
| // Dwell calculation | |
| det.baseDwell_s = det.baseDwell_s || 5.0; | |
| det.reqDwell_s = APP.core.hel.requiredDwell(range, det.reqP_kW, det.maxP_kW, det.baseDwell_s); | |
| // P(kill) | |
| const margin = det.maxP_kW - det.reqP_kW; | |
| det.pkill = APP.core.hel.pkillFromMargin(margin, det.baseDwell_s, det.reqDwell_s); | |
| // Store range | |
| det.baseRange_m = range; | |
| } | |
| log("HEL synthesis updated for all targets.", "t"); | |
| }; | |
| // External hook for HEL synthesis (can be replaced by user) | |
| APP.core.hel.externalHEL = async function (detections, knobs) { | |
| // Default implementation - can be replaced by user | |
| console.log("externalHEL called for", detections.length, "objects", knobs); | |
| return { | |
| targets: {}, | |
| system: { maxP_kW: 0, reqP_kW: 0, margin_kW: 0, medianRange_m: 0 } | |
| }; | |
| }; | |
| // Sync knob display values in the UI | |
| APP.core.hel.syncKnobDisplays = function () { | |
| const { $ } = APP.core.utils; | |
| const helPower = $("#helPower"); | |
| const helAperture = $("#helAperture"); | |
| const helM2 = $("#helM2"); | |
| const helJitter = $("#helJitter"); | |
| const helDuty = $("#helDuty"); | |
| const atmVis = $("#atmVis"); | |
| const atmCn2 = $("#atmCn2"); | |
| const seaSpray = $("#seaSpray"); | |
| const aoQ = $("#aoQ"); | |
| const rangeBase = $("#rangeBase"); | |
| const detHz = $("#detHz"); | |
| const assessWindow = $("#assessWindow"); | |
| const policyMode = $("#policyMode"); | |
| if (helPower) { | |
| const v = $("#helPowerVal"); | |
| if (v) v.textContent = helPower.value; | |
| } | |
| if (helAperture) { | |
| const v = $("#helApertureVal"); | |
| if (v) v.textContent = (+helAperture.value).toFixed(2); | |
| } | |
| if (helM2) { | |
| const v = $("#helM2Val"); | |
| if (v) v.textContent = (+helM2.value).toFixed(1); | |
| } | |
| if (helJitter) { | |
| const v = $("#helJitterVal"); | |
| if (v) v.textContent = (+helJitter.value).toFixed(1); | |
| } | |
| if (helDuty) { | |
| const v = $("#helDutyVal"); | |
| if (v) v.textContent = helDuty.value; | |
| } | |
| if (atmVis) { | |
| const v = $("#atmVisVal"); | |
| if (v) v.textContent = atmVis.value; | |
| } | |
| if (atmCn2) { | |
| const v = $("#atmCn2Val"); | |
| if (v) v.textContent = atmCn2.value; | |
| } | |
| if (seaSpray) { | |
| const v = $("#seaSprayVal"); | |
| if (v) v.textContent = seaSpray.value; | |
| } | |
| if (aoQ) { | |
| const v = $("#aoQVal"); | |
| if (v) v.textContent = aoQ.value; | |
| } | |
| if (rangeBase) { | |
| const v = $("#rangeBaseVal"); | |
| if (v) v.textContent = rangeBase.value; | |
| } | |
| if (detHz) { | |
| const v = $("#detHzVal"); | |
| if (v) v.textContent = detHz.value; | |
| } | |
| if (assessWindow) { | |
| const v = $("#assessWindowVal"); | |
| if (v) v.textContent = (+assessWindow.value).toFixed(1); | |
| } | |
| // Update chips | |
| const chipPolicy = $("#chipPolicy"); | |
| const chipHz = $("#chipHz"); | |
| if (chipPolicy && policyMode) chipPolicy.textContent = `POLICY:${policyMode.value.toUpperCase()}`; | |
| if (chipHz && detHz) chipHz.textContent = `DET:${detHz.value}Hz`; | |
| // Update telemetry footer | |
| const telemetry = $("#telemetry"); | |
| if (telemetry && helPower && atmVis && atmCn2 && aoQ && detHz) { | |
| telemetry.textContent = `VIS=${atmVis.value}km · DET=${detHz.value}Hz`; | |
| } | |
| }; | |