|
|
@@ -303,7 +303,7 @@ footer { text-align: center; padding: 20px; font-family: var(--mono); font-size:
|
|
|
</div>
|
|
|
<div class="readout" id="r-mod"></div>
|
|
|
<div class="feedback" id="fb-mod"></div>
|
|
|
- <p class="legend">Верхний график — модулирующий сигнал F_м (зелёный). Средний — несущая f_c (синяя). Нижний — АМ-сигнал: <b style="color:#b22222">красная огибающая точно повторяет верхний график (F_м)</b>, <b style="color:#1a4a7a">заполнение под ней — несущая f_c, то же число осцилляций, что на среднем</b>. При движении F_м меняется только огибающая. При движении f_c — только число осцилляций несущей. Масштаб осцилляций условный (реальное 200:1 нечитаемо), реальные λ — в readout.</p>
|
|
|
+ <p class="legend">Верхний график — модулирующий сигнал F_м (зелёный). Средний — несущая f_c (синяя). Нижний — АМ-сигнал: <b style="color:#b22222">красная огибающая точно повторяет верхний график (F_м)</b>, <b style="color:#1a4a7a">заполнение под ней — несущая f_c, то же число осцилляций, что на среднем</b>. При движении F_м меняется только огибающая. При движении f_c — только число осцилляций несущей.</p>
|
|
|
<div class="nav"><button class="btn" onclick="goTo(3)">← Назад</button><button class="btn" onclick="goTo(5)">Далее →</button></div>
|
|
|
</section>
|
|
|
|
|
|
@@ -311,7 +311,7 @@ footer { text-align: center; padding: 20px; font-family: var(--mono); font-size:
|
|
|
<section class="scene" data-scene="5">
|
|
|
<h2>5. Частотная модуляция (ЧМ)</h2>
|
|
|
<p>В АМ меняется амплитуда несущей, а частота постоянна. В ЧМ наоборот: <b>амплитуда постоянна, а частота несущей меняется по закону модулирующего сигнала</b>. Информация заложена в отклонении частоты от среднего значения — в девиации.</p>
|
|
|
- <p><b>Девиация Δf</b> — максимальное отклонение мгновенной частоты от несущей. <b>Индекс модуляции β = Δf / F<sub>м</sub></b> — насколько «глубоко» модулируем. Полоса по Карсону: <b>B ≈ 2(Δf + F<sub>м</sub>)</b>.</p>
|
|
|
+ <p><b>Девиация Δf</b> — максимальное отклонение мгновенной частоты от несущей. <b>Индекс модуляции β = Δf / F<sub>м</sub></b>. Полоса по Карсону: <b>B ≈ 2(Δf + F<sub>м</sub>)</b>.</p>
|
|
|
<canvas id="c-fm"></canvas>
|
|
|
<div class="controls">
|
|
|
<div class="control-row">
|
|
|
@@ -332,14 +332,14 @@ footer { text-align: center; padding: 20px; font-family: var(--mono); font-size:
|
|
|
</div>
|
|
|
<div class="readout" id="r-fm"></div>
|
|
|
<div class="feedback" id="fb-fm"></div>
|
|
|
- <p class="legend">Верхний — модулирующий F_м. Средний — несущая f_c без модуляции (для сравнения). Нижний — ЧМ-сигнал: <b style="color:#6a3a8a">амплитуда постоянна</b>, но осцилляции <b>сгущаются</b> (частота выше) при положительной полуволне модулирующего и <b>разрежаются</b> (частота ниже) при отрицательной. Это и есть модуляция частоты. Масштаб осцилляций условный.</p>
|
|
|
+ <p class="legend">Верхний — модулирующий F_м. Средний — несущая f_c без модуляции (для сравнения). Нижний — ЧМ-сигнал: <b style="color:#6a3a8a">амплитуда постоянна</b>, но осцилляции <b>сгущаются</b> при положительной полуволне модулирующего и <b>разрежаются</b> при отрицательной.</p>
|
|
|
<div class="nav"><button class="btn" onclick="goTo(4)">← Назад</button><button class="btn" onclick="goTo(6)">Далее →</button></div>
|
|
|
</section>
|
|
|
|
|
|
<!-- 6: ФМ -->
|
|
|
<section class="scene" data-scene="6">
|
|
|
<h2>6. Фазовая модуляция (ФМ)</h2>
|
|
|
- <p>В ФМ <b>амплитуда тоже постоянна</b>, но меняется не частота, а <b>фаза несущей</b> — она сдвигается вперёд-назад по закону модулирующего сигнала. Внешне сигнал похож на ЧМ, но есть принципиальное отличие: в ЧМ мгновенная частота пропорциональна модулирующему (sin), а в ФМ сама фаза пропорциональна модулирующему. Значит, мгновенная частота в ФМ меняется по производной — по cos, со сдвигом π/2 относительно огибающей.</p>
|
|
|
+ <p>В ФМ <b>амплитуда тоже постоянна</b>, но меняется не частота, а <b>фаза несущей</b> — она сдвигается вперёд-назад по закону модулирующего сигнала. Внешне похоже на ЧМ, но отличие принципиальное: в ЧМ мгновенная частота пропорциональна модулирующему (sin), а в ФМ сама фаза пропорциональна модулирующему. Значит, мгновенная частота в ФМ меняется по производной — по cos, со сдвигом π/2.</p>
|
|
|
<p><b>Индекс модуляции β</b> — максимальный сдвиг фазы в радианах. Полоса: <b>B ≈ 2F<sub>м</sub>(β + 1)</b>.</p>
|
|
|
<canvas id="c-pm"></canvas>
|
|
|
<div class="controls">
|
|
|
@@ -361,56 +361,74 @@ footer { text-align: center; padding: 20px; font-family: var(--mono); font-size:
|
|
|
</div>
|
|
|
<div class="readout" id="r-pm"></div>
|
|
|
<div class="feedback" id="fb-pm"></div>
|
|
|
- <p class="legend">Верхний — модулирующий F_м. Средний — несущая f_c без модуляции. Нижний — ФМ-сигнал: амплитуда постоянна, <b style="color:#6a3a8a">фаза смещается вперёд-назад</b> — видно, как осцилляции сдвигаются вправо-влево в такт с модулирующим, но частота меняется не в фазе с ним, а со сдвигом π/2. Это отличие от ЧМ. Масштаб условный.</p>
|
|
|
+ <p class="legend">Верхний — модулирующий F_м. Средний — несущая f_c без модуляции. Нижний — ФМ-сигнал: амплитуда постоянна, <b style="color:#6a3a8a">фаза смещается вперёд-назад</b> — видно, как осцилляции сдвигаются вправо-влево в такт с модулирующим, но частота меняется не в фазе с ним, а со сдвигом π/2.</p>
|
|
|
<div class="nav"><button class="btn" onclick="goTo(5)">← Назад</button><button class="btn" onclick="goTo(7)">Далее →</button></div>
|
|
|
</section>
|
|
|
|
|
|
-<!-- 7 -->
|
|
|
+<!-- 7: дифракция -->
|
|
|
<section class="scene" data-scene="7">
|
|
|
<h2>7. Распространение: дифракция и прямая видимость</h2>
|
|
|
<p>Волна огибает препятствие, если её длина соизмерима с препятствием. Если λ много меньше — за препятствием тень.</p>
|
|
|
- <p>Геометрия: передатчик и приёмник на земле, по 10 км от препятствия. Препятствие — ножевой край на 200 м выше линии прямой видимости. Меняйте λ и смотрите, что доходит до приёмника.</p>
|
|
|
+ <p>Передатчик и приёмник на земле, по 10 км от препятствия. Меняйте λ и высоту препятствия h — смотрите, как меняется волновое поле и что доходит до приёмника.</p>
|
|
|
<canvas id="c-diff"></canvas>
|
|
|
<div class="controls">
|
|
|
<div class="control-row">
|
|
|
<label>Длина волны λ</label>
|
|
|
- <input type="range" id="s-lam" min="-1" max="5" step="0.01" value="1">
|
|
|
- <span class="value-box" id="v-lam">10 м</span>
|
|
|
+ <input type="range" id="s-lam" min="0" max="4" step="0.01" value="2">
|
|
|
+ <span class="value-box" id="v-lam">100 м</span>
|
|
|
+ </div>
|
|
|
+ <div class="control-row">
|
|
|
+ <label>Высота препятствия h</label>
|
|
|
+ <input type="range" id="s-h" min="0" max="500" step="10" value="200">
|
|
|
+ <span class="value-box" id="v-h">200 м</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="readout" id="r-diff"></div>
|
|
|
<div class="feedback" id="fb-diff"></div>
|
|
|
- <p class="legend">Вертикальный масштаб увеличен. Модель ножевого края (ITU-R P.526); реальный холм даёт меньше потерь, но качественно то же.</p>
|
|
|
+ <p class="legend">Шаг волновых фронтов = λ в масштабе экрана: при λ много меньше h фронты мелкие и за препятствием гаснут, при λ ~ h фронты крупные и огибают. Модель ножевого края (ITU-R P.526).</p>
|
|
|
<div class="nav"><button class="btn" onclick="goTo(6)">← Назад</button><button class="btn" onclick="goTo(8)">Далее →</button></div>
|
|
|
</section>
|
|
|
|
|
|
-<!-- 8 -->
|
|
|
+<!-- 8: ионосфера -->
|
|
|
<section class="scene" data-scene="8">
|
|
|
<h2>8. Ионосфера: отражение и поглощение</h2>
|
|
|
- <p>Почему ДВ/СВ/КВ идут за горизонт, а УКВ — нет. Меняйте частоту и время суток.</p>
|
|
|
+ <p>Ионосфера — ионизированные слои атмосферы. Радиоволна может отразиться от них, пройти сквозь или поглотиться — это зависит от частоты и от солнечной активности (чем она выше, тем ионизация сильнее).</p>
|
|
|
+
|
|
|
+ <div class="notebook" style="margin-bottom:12px">
|
|
|
+ <div class="nb-title">СЛОИ ИОНОСФЕРЫ</div>
|
|
|
+ <dl>
|
|
|
+ <dt>D (60–90 км)</dt>
|
|
|
+ <dd>Существует только днём. Поглощает ДВ/СВ и нижние КВ. Ночью исчезает.</dd>
|
|
|
+ <dt>E (90–120 км)</dt>
|
|
|
+ <dd>Отражает СВ и нижние КВ. Днём ионизация выше — слой плотнее и чуть выше.</dd>
|
|
|
+ <dt>F (150–400 км)</dt>
|
|
|
+ <dd>Основной слой для КВ. Ночью ниже (~250 км), днём выше (~400 км). Выше слой — дальше точка отражения и приёма.</dd>
|
|
|
+ </dl>
|
|
|
+ </div>
|
|
|
+
|
|
|
<canvas id="c-iono"></canvas>
|
|
|
<div class="controls">
|
|
|
<div class="control-row">
|
|
|
<label>Частота f</label>
|
|
|
- <input type="range" id="s-iono-f" min="5" max="10" step="0.01" value="6.5">
|
|
|
- <span class="value-box" id="v-iono-f">3 МГц</span>
|
|
|
+ <input type="range" id="s-iono-f" min="5" max="10" step="0.01" value="7">
|
|
|
+ <span class="value-box" id="v-iono-f">10 МГц</span>
|
|
|
</div>
|
|
|
<div class="control-row">
|
|
|
- <label>Время суток</label>
|
|
|
- <button class="btn small active" id="b-day">День</button>
|
|
|
- <button class="btn small" id="b-night">Ночь</button>
|
|
|
+ <label>Солнечная активность</label>
|
|
|
+ <input type="range" id="s-iono-s" min="0" max="100" step="5" value="50">
|
|
|
+ <span class="value-box" id="v-iono-s">50%</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="readout" id="r-iono"></div>
|
|
|
<div class="feedback" id="fb-iono"></div>
|
|
|
- <p class="legend">Модель пороговая: 3 МГц и 30 МГц — качественные границы, не формула. Для инженерного расчёта — МПЧ, закон секанса, солнечная активность.</p>
|
|
|
+ <p class="legend">Солнечная активность: 0% — ночь, 100% — полдень. Угол выхода луча фиксирован (50° от вертикали). При росте активности слой F поднимается — точка отражения на слое и точка приёма на Земле сдвигаются вправо. Поверхность Земли изогнута; вертикальный масштаб увеличен для наглядности.</p>
|
|
|
<div class="nav"><button class="btn" onclick="goTo(7)">← Назад</button><button class="btn" onclick="goTo(9)">Далее →</button></div>
|
|
|
</section>
|
|
|
|
|
|
<!-- 9 -->
|
|
|
<section class="scene" data-scene="9">
|
|
|
<h2>9. Тренажёр диапазонов</h2>
|
|
|
- <p>Выберите карточку, затем нажмите на корзину. Верно — карточка встанет на место с пояснением. Неверно — подсказка, попробуйте снова.</p>
|
|
|
+ <p>Выберите карточку, затем нажмите на корзину. Верно — карточка встанет на место с пояснением.</p>
|
|
|
<div id="trainer"></div>
|
|
|
<div class="feedback" id="fb-trainer" style="display:none"></div>
|
|
|
<div class="nav"><button class="btn" onclick="goTo(8)">← Назад</button><button class="btn" onclick="goTo(10)">Далее →</button></div>
|
|
|
@@ -443,11 +461,11 @@ footer { text-align: center; padding: 20px; font-family: var(--mono); font-size:
|
|
|
<div class="nb-title">ЧЕК-ЛИСТ КОНСПЕКТА</div>
|
|
|
<ul>
|
|
|
<li>λ = c / f, c = 3·10⁸ м/с</li>
|
|
|
- <li>АМ: меняется амплитуда. Огибающая = модулирующий сигнал F_м, заполнение = несущая f_c. B = 2F_м.</li>
|
|
|
+ <li>АМ: меняется амплитуда. Огибающая = модулирующий F_м, заполнение = несущая f_c. B = 2F_м.</li>
|
|
|
<li>ЧМ: меняется частота, амплитуда постоянна. Девиация Δf, индекс β = Δf/F_м. B ≈ 2(Δf + F_м).</li>
|
|
|
<li>ФМ: меняется фаза, амплитуда постоянна. Индекс β. B ≈ 2F_м(β + 1).</li>
|
|
|
- <li>Дифракция: λ соизмерима с препятствием → огибает</li>
|
|
|
- <li>Ионосфера отражает КВ, прозрачна для УКВ</li>
|
|
|
+ <li>Дифракция: λ соизмерима с h → огибает; λ ≪ h → тень</li>
|
|
|
+ <li>Ионосфера: D поглощает днём, E отражает СВ, F — основной слой КВ. Чем выше солнечная активность, тем выше F и дальше приём.</li>
|
|
|
<li>Радиогоризонт: d ≈ 3.57·(√h₁ + √h₂) км</li>
|
|
|
<li>Направленная антенна сужает зону перехвата, ППРЧ защищает от узкополосной помехи, экранирование снижает утечку</li>
|
|
|
</ul>
|
|
|
@@ -651,7 +669,6 @@ function initMod() {
|
|
|
const x0 = 55, x1 = W - 20;
|
|
|
const midX = (x0 + x1) / 2;
|
|
|
|
|
|
- // верхний: модулирующий сигнал
|
|
|
const mY = 22, mH = 80;
|
|
|
const mMid = mY + mH / 2;
|
|
|
ctx.fillStyle = '#1a2a4a';
|
|
|
@@ -687,7 +704,6 @@ function initMod() {
|
|
|
ctx.textAlign = 'right';
|
|
|
ctx.fillText('циклов: ' + nMod.toFixed(0), x1, mY - 6);
|
|
|
|
|
|
- // средний: несущая
|
|
|
const cY = 145, cH = 80;
|
|
|
const cMid = cY + cH / 2;
|
|
|
ctx.fillStyle = '#1a2a4a';
|
|
|
@@ -723,7 +739,6 @@ function initMod() {
|
|
|
ctx.textAlign = 'right';
|
|
|
ctx.fillText('осцилляций: ' + nOscDisplay.toFixed(0), x1, cY - 6);
|
|
|
|
|
|
- // нижний: АМ
|
|
|
const tY = 265, tH = 140;
|
|
|
const tMid = tY + tH / 2;
|
|
|
ctx.fillStyle = '#1a2a4a';
|
|
|
@@ -780,7 +795,6 @@ function initMod() {
|
|
|
ctx.fillText('ПЕРЕМОДУЛЯЦИЯ: огибающая пересекает ноль', x0, tY + tH + 14);
|
|
|
}
|
|
|
|
|
|
- // спектр
|
|
|
const sY = 440, sH = 80;
|
|
|
const sBase = sY + sH;
|
|
|
ctx.fillStyle = '#1a2a4a';
|
|
|
@@ -829,7 +843,6 @@ function initMod() {
|
|
|
ctx.fillText('B = 2F_м = ' + (2 * Fm).toFixed(1) + ' кГц', fcX, sBase - 66);
|
|
|
}
|
|
|
|
|
|
- // readout
|
|
|
const lamC = 300 / Fc;
|
|
|
const lamMod = 3e8 / (Fm * 1e3);
|
|
|
const lamModStr = lamMod >= 1000 ? (lamMod / 1000).toFixed(1) + ' км' : lamMod.toFixed(0) + ' м';
|
|
|
@@ -859,13 +872,7 @@ function initMod() {
|
|
|
}
|
|
|
|
|
|
/* ============================================================
|
|
|
- SCENE 5 — ЧМ (FM)
|
|
|
- Логика:
|
|
|
- - Мгновенная частота: f(t) = Fc + Δf·sin(2π·Fm·t)
|
|
|
- - Фаза: φ(t) = 2π·Fc·t - β·cos(2π·Fm·t), β = Δf/Fm
|
|
|
- - Амплитуда постоянна.
|
|
|
- Нормализованно: φ(t) = 2π·nOsc·t - β·cos(2π·nMod·t)
|
|
|
- При положительной полуволне модулирующего — частота max, сгущение.
|
|
|
+ SCENE 5 — ЧМ
|
|
|
============================================================ */
|
|
|
function initFM() {
|
|
|
const canvas = document.getElementById('c-fm');
|
|
|
@@ -887,7 +894,6 @@ function initFM() {
|
|
|
|
|
|
const x0 = 55, x1 = W - 20;
|
|
|
|
|
|
- // верхний: модулирующий
|
|
|
const mY = 22, mH = 80;
|
|
|
const mMid = mY + mH / 2;
|
|
|
ctx.fillStyle = '#1a2a4a';
|
|
|
@@ -918,7 +924,6 @@ function initFM() {
|
|
|
ctx.stroke();
|
|
|
ctx.lineWidth = 1;
|
|
|
|
|
|
- // средний: эталонная несущая (без модуляции) для сравнения
|
|
|
const cY = 145, cH = 80;
|
|
|
const cMid = cY + cH / 2;
|
|
|
ctx.fillStyle = '#1a2a4a';
|
|
|
@@ -949,7 +954,6 @@ function initFM() {
|
|
|
ctx.stroke();
|
|
|
ctx.lineWidth = 1;
|
|
|
|
|
|
- // нижний: ЧМ-сигнал
|
|
|
const tY = 265, tH = 130;
|
|
|
const tMid = tY + tH / 2;
|
|
|
ctx.fillStyle = '#1a2a4a';
|
|
|
@@ -976,7 +980,6 @@ function initFM() {
|
|
|
for (let i = 0; i <= tSamples; i++) {
|
|
|
const x = x0 + (x1 - x0) * i / tSamples;
|
|
|
const tNorm = i / tSamples;
|
|
|
- // φ(t) = 2π·nOsc·t − β·cos(2π·nMod·t)
|
|
|
const phase = 2 * Math.PI * nOscDisplay * tNorm - beta * Math.cos(2 * Math.PI * nMod * tNorm);
|
|
|
const y = tMid - ampBase * Math.sin(phase);
|
|
|
if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
|
|
|
@@ -989,7 +992,6 @@ function initFM() {
|
|
|
ctx.textAlign = 'left';
|
|
|
ctx.fillText('осцилляций: ' + nOscDisplay.toFixed(0) + ' · индекс β = Δf/F_м = ' + beta.toFixed(2), x0, tY + tH + 14);
|
|
|
|
|
|
- // readout
|
|
|
const lamC = 300 / Fc;
|
|
|
const carson = 2 * (Dev + Fm);
|
|
|
document.getElementById('v-fm-fm').textContent = Fm.toFixed(1) + ' кГц';
|
|
|
@@ -1005,8 +1007,8 @@ function initFM() {
|
|
|
|
|
|
let fb;
|
|
|
if (beta < 0.5) fb = 'Узкополосная ЧМ (β < 0.5): по спектру похожа на АМ, но амплитуда постоянна — это плюс по помехоустойчивости.';
|
|
|
- else if (beta < 5) fb = 'Обычная ЧМ (0.5 ≤ β < 5): заметно сгущение и разрежение осцилляций, амплитуда постоянна.';
|
|
|
- else fb = 'Широкополосная ЧМ (β ≥ 5): большая девиация, широкая полоса, высокая помехоустойчивость. Цена — полоса.';
|
|
|
+ else if (beta < 5) fb = 'Обычная ЧМ (0.5 ≤ β < 5): заметно сгущение и разрежение осцилляций.';
|
|
|
+ else fb = 'Широкополосная ЧМ (β ≥ 5): большая девиация, широкая полоса, высокая помехоустойчивость.';
|
|
|
document.getElementById('fb-fm').textContent = fb;
|
|
|
}
|
|
|
|
|
|
@@ -1018,13 +1020,7 @@ function initFM() {
|
|
|
}
|
|
|
|
|
|
/* ============================================================
|
|
|
- SCENE 6 — ФМ (PM)
|
|
|
- Логика:
|
|
|
- - Мгновенная фаза: φ(t) = 2π·Fc·t + β·sin(2π·Fm·t)
|
|
|
- - Мгновенная частота: f(t) = Fc + β·Fm·cos(2π·Fm·t)
|
|
|
- - Амплитуда постоянна.
|
|
|
- Нормализованно: φ(t) = 2π·nOsc·t + β·sin(2π·nMod·t)
|
|
|
- Отличие от ЧМ: в ФМ фаза меняется по sin, частота — по cos (сдвиг π/2).
|
|
|
+ SCENE 6 — ФМ
|
|
|
============================================================ */
|
|
|
function initPM() {
|
|
|
const canvas = document.getElementById('c-pm');
|
|
|
@@ -1045,7 +1041,6 @@ function initPM() {
|
|
|
|
|
|
const x0 = 55, x1 = W - 20;
|
|
|
|
|
|
- // верхний: модулирующий
|
|
|
const mY = 22, mH = 80;
|
|
|
const mMid = mY + mH / 2;
|
|
|
ctx.fillStyle = '#1a2a4a';
|
|
|
@@ -1076,7 +1071,6 @@ function initPM() {
|
|
|
ctx.stroke();
|
|
|
ctx.lineWidth = 1;
|
|
|
|
|
|
- // средний: эталонная несущая
|
|
|
const cY = 145, cH = 80;
|
|
|
const cMid = cY + cH / 2;
|
|
|
ctx.fillStyle = '#1a2a4a';
|
|
|
@@ -1107,7 +1101,6 @@ function initPM() {
|
|
|
ctx.stroke();
|
|
|
ctx.lineWidth = 1;
|
|
|
|
|
|
- // нижний: ФМ-сигнал
|
|
|
const tY = 265, tH = 130;
|
|
|
const tMid = tY + tH / 2;
|
|
|
ctx.fillStyle = '#1a2a4a';
|
|
|
@@ -1134,7 +1127,6 @@ function initPM() {
|
|
|
for (let i = 0; i <= tSamples; i++) {
|
|
|
const x = x0 + (x1 - x0) * i / tSamples;
|
|
|
const tNorm = i / tSamples;
|
|
|
- // φ(t) = 2π·nOsc·t + β·sin(2π·nMod·t)
|
|
|
const phase = 2 * Math.PI * nOscDisplay * tNorm + beta * Math.sin(2 * Math.PI * nMod * tNorm);
|
|
|
const y = tMid - ampBase * Math.sin(phase);
|
|
|
if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
|
|
|
@@ -1147,7 +1139,6 @@ function initPM() {
|
|
|
ctx.textAlign = 'left';
|
|
|
ctx.fillText('осцилляций: ' + nOscDisplay.toFixed(0) + ' · индекс β = ' + beta.toFixed(2) + ' рад', x0, tY + tH + 14);
|
|
|
|
|
|
- // readout
|
|
|
const lamC = 300 / Fc;
|
|
|
const pmBand = 2 * Fm * (beta + 1);
|
|
|
document.getElementById('v-fm-pm').textContent = Fm.toFixed(1) + ' кГц';
|
|
|
@@ -1161,9 +1152,9 @@ function initPM() {
|
|
|
'Полоса B ≈ 2F_м(β + 1) = <span class="hl">' + pmBand.toFixed(0) + ' кГц</span>';
|
|
|
|
|
|
let fb;
|
|
|
- if (beta < 0.5) fb = 'Малый индекс: фаза колеблется слабо, спектр узкий. Похоже на узкополосную ЧМ.';
|
|
|
+ if (beta < 0.5) fb = 'Малый индекс: фаза колеблется слабо, спектр узкий.';
|
|
|
else if (beta < 5) fb = 'Обычная ФМ: заметны сдвиги фазы, частота меняется по cos — со сдвигом π/2 относительно модулирующего.';
|
|
|
- else fb = 'Большой индекс: сильные фазовые сдвиги, широкая полоса. На практике применяется редко — при том же β помехоустойчивость как у ЧМ, но полоса та же.';
|
|
|
+ else fb = 'Большой индекс: сильные фазовые сдвиги, широкая полоса.';
|
|
|
document.getElementById('fb-pm').textContent = fb;
|
|
|
}
|
|
|
|
|
|
@@ -1175,18 +1166,20 @@ function initPM() {
|
|
|
}
|
|
|
|
|
|
/* ============================================================
|
|
|
- SCENE 7 — diffraction
|
|
|
+ SCENE 7 — дифракция
|
|
|
============================================================ */
|
|
|
function initDiff() {
|
|
|
const canvas = document.getElementById('c-diff');
|
|
|
- const slider = document.getElementById('s-lam');
|
|
|
+ const sLam = document.getElementById('s-lam');
|
|
|
+ const sH = document.getElementById('s-h');
|
|
|
|
|
|
function draw() {
|
|
|
- const { ctx, W, H } = prepCanvas(canvas, 280);
|
|
|
- const logLam = parseFloat(slider.value);
|
|
|
+ const { ctx, W, H } = prepCanvas(canvas, 320);
|
|
|
+ const logLam = parseFloat(sLam.value);
|
|
|
const lambda = Math.pow(10, logLam);
|
|
|
+ const h = parseFloat(sH.value);
|
|
|
|
|
|
- const v = 4 / Math.sqrt(lambda);
|
|
|
+ const v = h / (50 * Math.sqrt(lambda));
|
|
|
let L = 0;
|
|
|
if (v > -0.7) {
|
|
|
L = 6.9 + 20 * Math.log10(Math.sqrt((v - 0.1) * (v - 0.1) + 1) + v - 0.1);
|
|
|
@@ -1197,7 +1190,13 @@ function initDiff() {
|
|
|
const losY = groundY - 25;
|
|
|
const txX = 50, rxX = W - 50;
|
|
|
const obsX = W / 2;
|
|
|
- const obsTopY = losY - 150;
|
|
|
+
|
|
|
+ const vScale = 150 / 500;
|
|
|
+ const hPix = h * vScale;
|
|
|
+ const obsTopY = losY - hPix;
|
|
|
+
|
|
|
+ const pxPerMeter = (W - 100) / 20000;
|
|
|
+ const stepPix = lambda * pxPerMeter;
|
|
|
|
|
|
ctx.fillStyle = '#c8b890';
|
|
|
ctx.fillRect(0, groundY, W, H - groundY);
|
|
|
@@ -1215,46 +1214,63 @@ function initDiff() {
|
|
|
ctx.stroke();
|
|
|
ctx.setLineDash([]);
|
|
|
|
|
|
- const shadowAlpha = Math.max(0.03, Math.min(0.6, 0.6 * (1 - L / 40)));
|
|
|
-
|
|
|
- const maxR = Math.hypot(rxX - txX, groundY - losY) + 40;
|
|
|
- const steps = 44;
|
|
|
- for (let r = 40; r < maxR; r += 28) {
|
|
|
- for (let s = 0; s < steps; s++) {
|
|
|
- const th1 = (s / steps) * Math.PI;
|
|
|
- const th2 = ((s + 1) / steps) * Math.PI;
|
|
|
- const x1 = txX + r * Math.cos(th1);
|
|
|
- const y1 = losY - r * Math.sin(th1);
|
|
|
- const x2 = txX + r * Math.cos(th2);
|
|
|
- const y2 = losY - r * Math.sin(th2);
|
|
|
- if (y1 > groundY || y2 > groundY) continue;
|
|
|
- if (x1 < 2 || x1 > W - 2 || x2 < 2 || x2 > W - 2) continue;
|
|
|
-
|
|
|
- let alpha = 0.5;
|
|
|
- const mx = (x1 + x2) / 2, my = (y1 + y2) / 2;
|
|
|
- if (mx > obsX) {
|
|
|
- const yShadow = losY - 150 * (mx - txX) / (obsX - txX);
|
|
|
- if (my > yShadow) alpha = shadowAlpha;
|
|
|
+ const shadowAmp = Math.max(0.03, Math.min(1, Math.pow(10, -L / 20)));
|
|
|
+
|
|
|
+ if (stepPix < 3) {
|
|
|
+ ctx.fillStyle = 'rgba(26,42,74,0.18)';
|
|
|
+ ctx.fillRect(txX, 0, obsX - txX, groundY);
|
|
|
+ const a = 0.18 * shadowAmp;
|
|
|
+ ctx.fillStyle = 'rgba(26,42,74,' + a.toFixed(3) + ')';
|
|
|
+ ctx.fillRect(obsX, 0, rxX - obsX, groundY);
|
|
|
+ } else {
|
|
|
+ const maxR = Math.hypot(rxX - txX, groundY - losY) + stepPix;
|
|
|
+ for (let r = stepPix; r < maxR; r += stepPix) {
|
|
|
+ const segCount = Math.max(24, Math.floor(Math.PI * Math.min(r, 400) / 6));
|
|
|
+ for (let s = 0; s < segCount; s++) {
|
|
|
+ const th1 = Math.PI - (s / segCount) * Math.PI;
|
|
|
+ const th2 = Math.PI - ((s + 1) / segCount) * Math.PI;
|
|
|
+ const x1 = txX + r * Math.cos(th1);
|
|
|
+ const y1 = losY - r * Math.sin(th1);
|
|
|
+ const x2 = txX + r * Math.cos(th2);
|
|
|
+ const y2 = losY - r * Math.sin(th2);
|
|
|
+ if (y1 > groundY || y2 > groundY) continue;
|
|
|
+ if (x1 < 2 || x1 > W - 2 || x2 < 2 || x2 > W - 2) continue;
|
|
|
+
|
|
|
+ let alpha = 0.45;
|
|
|
+ const mx = (x1 + x2) / 2, my = (y1 + y2) / 2;
|
|
|
+ if (mx > obsX) {
|
|
|
+ const yShadow = (losY - hPix) + (mx - obsX) / (rxX - obsX) * hPix;
|
|
|
+ if (my > yShadow) {
|
|
|
+ alpha = 0.45 * shadowAmp;
|
|
|
+ if (alpha < 0.02) continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ctx.strokeStyle = 'rgba(26,42,74,' + alpha.toFixed(3) + ')';
|
|
|
+ ctx.lineWidth = 1.5;
|
|
|
+ ctx.beginPath();
|
|
|
+ ctx.moveTo(x1, y1);
|
|
|
+ ctx.lineTo(x2, y2);
|
|
|
+ ctx.stroke();
|
|
|
}
|
|
|
- ctx.strokeStyle = 'rgba(26,42,74,' + alpha + ')';
|
|
|
- ctx.lineWidth = 1;
|
|
|
- ctx.beginPath();
|
|
|
- ctx.moveTo(x1, y1);
|
|
|
- ctx.lineTo(x2, y2);
|
|
|
- ctx.stroke();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- ctx.fillStyle = '#8a7a5a';
|
|
|
- ctx.beginPath();
|
|
|
- ctx.moveTo(obsX - 7, groundY);
|
|
|
- ctx.lineTo(obsX, obsTopY);
|
|
|
- ctx.lineTo(obsX + 7, groundY);
|
|
|
- ctx.closePath();
|
|
|
- ctx.fill();
|
|
|
- ctx.strokeStyle = '#1a2a4a';
|
|
|
- ctx.lineWidth = 1;
|
|
|
- ctx.stroke();
|
|
|
+ if (hPix > 0) {
|
|
|
+ ctx.fillStyle = '#8a7a5a';
|
|
|
+ ctx.beginPath();
|
|
|
+ ctx.moveTo(obsX - 8, groundY);
|
|
|
+ ctx.lineTo(obsX, obsTopY);
|
|
|
+ ctx.lineTo(obsX + 8, groundY);
|
|
|
+ ctx.closePath();
|
|
|
+ ctx.fill();
|
|
|
+ ctx.strokeStyle = '#1a2a4a';
|
|
|
+ ctx.lineWidth = 1;
|
|
|
+ ctx.stroke();
|
|
|
+ ctx.fillStyle = '#3a4a6a';
|
|
|
+ ctx.font = '9px "Roboto Mono", monospace';
|
|
|
+ ctx.textAlign = 'right';
|
|
|
+ ctx.fillText('h = ' + h.toFixed(0) + ' м', obsX - 12, (groundY + obsTopY) / 2);
|
|
|
+ }
|
|
|
|
|
|
ctx.fillStyle = '#1a2a4a';
|
|
|
ctx.beginPath();
|
|
|
@@ -1276,7 +1292,7 @@ function initDiff() {
|
|
|
|
|
|
const barH = 44, barW = 12;
|
|
|
const barX = rxX + 18;
|
|
|
- const level = Math.max(0, Math.min(1, 1 - L / 40));
|
|
|
+ const level = Math.max(0, Math.min(1, shadowAmp));
|
|
|
ctx.fillStyle = '#e8dcc0';
|
|
|
ctx.fillRect(barX, losY - barH / 2, barW, barH);
|
|
|
ctx.fillStyle = L < 10 ? '#2a6a3a' : L < 25 ? '#d4a020' : '#b22222';
|
|
|
@@ -1288,189 +1304,321 @@ function initDiff() {
|
|
|
ctx.fillStyle = '#1a2a4a';
|
|
|
ctx.textAlign = 'left';
|
|
|
ctx.font = '10px "Roboto Mono", monospace';
|
|
|
- ctx.fillText('λ = ' + formatLambda(lambda), 10, 18);
|
|
|
+ ctx.fillText('λ = ' + formatLambda(lambda), 10, 16);
|
|
|
+ if (stepPix < 3) {
|
|
|
+ ctx.fillStyle = '#3a4a6a';
|
|
|
+ ctx.fillText('фронты λ неразличимы — показано сплошное поле', 10, 30);
|
|
|
+ } else {
|
|
|
+ ctx.fillStyle = '#3a4a6a';
|
|
|
+ ctx.fillText('шаг фронтов: ' + stepPix.toFixed(0) + ' px', 10, 30);
|
|
|
+ }
|
|
|
|
|
|
document.getElementById('v-lam').textContent = formatLambda(lambda);
|
|
|
+ document.getElementById('v-h').textContent = h.toFixed(0) + ' м';
|
|
|
document.getElementById('r-diff').innerHTML =
|
|
|
- 'λ = <span class="hl">' + formatLambda(lambda) + '</span><br>' +
|
|
|
+ 'λ = <span class="hl">' + formatLambda(lambda) + '</span> · h = <span class="hl">' + h.toFixed(0) + ' м</span><br>' +
|
|
|
'Параметр Френеля v = <span class="hl">' + v.toFixed(2) + '</span><br>' +
|
|
|
'Потери L = <span class="hl">' + L.toFixed(1) + ' дБ</span>';
|
|
|
|
|
|
let fb;
|
|
|
- if (L < 8) fb = 'λ соизмерима с препятствием. Дифракция: волна огибает, сигнал доходит.';
|
|
|
+ if (L < 8) fb = 'λ соизмерима с h или больше. Дифракция: волна огибает препятствие, сигнал доходит.';
|
|
|
else if (L < 18) fb = 'Частичная тень: волна огибает край, но с потерями.';
|
|
|
- else fb = 'λ много меньше препятствия. Тень: за препятствием сигнала нет.';
|
|
|
+ else fb = 'λ много меньше h. Тень: за препятствием сигнала практически нет.';
|
|
|
document.getElementById('fb-diff').textContent = fb;
|
|
|
}
|
|
|
|
|
|
currentDraw = draw;
|
|
|
- slider.oninput = draw;
|
|
|
+ sLam.oninput = draw;
|
|
|
+ sH.oninput = draw;
|
|
|
draw();
|
|
|
}
|
|
|
|
|
|
/* ============================================================
|
|
|
- SCENE 8 — ionosphere
|
|
|
+ SCENE 8 — ионосфера
|
|
|
+ Передатчик смещён влево (18% ширины) и является центром дуги Земли.
|
|
|
============================================================ */
|
|
|
function initIono() {
|
|
|
const canvas = document.getElementById('c-iono');
|
|
|
- const slider = document.getElementById('s-iono-f');
|
|
|
- const bDay = document.getElementById('b-day');
|
|
|
- const bNight = document.getElementById('b-night');
|
|
|
- let isDay = true;
|
|
|
-
|
|
|
- function updateDayBtn() {
|
|
|
- bDay.classList.toggle('active', isDay);
|
|
|
- bNight.classList.toggle('active', !isDay);
|
|
|
- }
|
|
|
+ const sF = document.getElementById('s-iono-f');
|
|
|
+ const sS = document.getElementById('s-iono-s');
|
|
|
+
|
|
|
+ const H_CANVAS = 440;
|
|
|
+ const theta = 50 * Math.PI / 180;
|
|
|
+ const tanTheta = Math.tan(theta);
|
|
|
|
|
|
function draw() {
|
|
|
- const { ctx, W, H } = prepCanvas(canvas, 300);
|
|
|
- const logf = parseFloat(slider.value);
|
|
|
+ const { ctx, W, H } = prepCanvas(canvas, H_CANVAS);
|
|
|
+ const logf = parseFloat(sF.value);
|
|
|
const f = Math.pow(10, logf);
|
|
|
+ const S = parseFloat(sS.value);
|
|
|
|
|
|
- const groundY = H - 20;
|
|
|
- const topY = 20;
|
|
|
- const altMax = 400;
|
|
|
- const yOf = km => groundY - (km / altMax) * (groundY - topY);
|
|
|
+ let mode, reflectLayer = null, hReflect = null, absorbed = false;
|
|
|
+ if (f < 3e6) {
|
|
|
+ mode = 'ДВ/СВ';
|
|
|
+ if (S > 30) {
|
|
|
+ absorbed = true;
|
|
|
+ hReflect = 75;
|
|
|
+ } else {
|
|
|
+ reflectLayer = 'E';
|
|
|
+ hReflect = 90 + 30 * S / 100;
|
|
|
+ }
|
|
|
+ } else if (f <= 3e7) {
|
|
|
+ mode = 'КВ';
|
|
|
+ reflectLayer = 'F';
|
|
|
+ hReflect = 250 + 150 * S / 100;
|
|
|
+ } else {
|
|
|
+ mode = 'УКВ+';
|
|
|
+ }
|
|
|
|
|
|
- ctx.fillStyle = '#c8b890';
|
|
|
- ctx.fillRect(0, groundY, W, H - groundY);
|
|
|
- ctx.strokeStyle = '#1a2a4a';
|
|
|
- ctx.beginPath();
|
|
|
- ctx.moveTo(0, groundY);
|
|
|
- ctx.lineTo(W, groundY);
|
|
|
- ctx.stroke();
|
|
|
+ let xRefKm = 0, xRxKm = 0;
|
|
|
+ if (hReflect !== null) {
|
|
|
+ xRefKm = hReflect * tanTheta;
|
|
|
+ xRxKm = 2 * xRefKm;
|
|
|
+ }
|
|
|
|
|
|
- const layers = [
|
|
|
- { name: 'D', from: 60, to: 90, color: 'rgba(100,100,140,0.30)', show: isDay },
|
|
|
- { name: 'E', from: 90, to: 120, color: 'rgba(100,140,100,0.30)', show: true },
|
|
|
- { name: 'F', from: 150, to: 400, color: 'rgba(140,100,100,0.20)', show: true }
|
|
|
- ];
|
|
|
- layers.forEach(l => {
|
|
|
- if (!l.show) return;
|
|
|
- const y1 = yOf(l.from), y2 = yOf(l.to);
|
|
|
- ctx.fillStyle = l.color;
|
|
|
- ctx.fillRect(0, y2, W, y1 - y2);
|
|
|
- ctx.strokeStyle = 'rgba(26,42,74,0.4)';
|
|
|
- ctx.setLineDash([4, 4]);
|
|
|
+ // передатчик — на 18% ширины; центр дуги Земли = передатчик
|
|
|
+ const txX = Math.round(W * 0.18);
|
|
|
+ const cx = txX;
|
|
|
+ const R_px = 1700;
|
|
|
+ const cy_bottom = H + R_px - 50;
|
|
|
+ const hScaleX = (W - txX - 60) / 2000; // масштаб 2000 км = оставшееся справа место
|
|
|
+ const vScale = 0.5;
|
|
|
+
|
|
|
+ const groundAt = x => cy_bottom - Math.sqrt(R_px * R_px - (x - cx) * (x - cx));
|
|
|
+ const layerY = h => H - 50 - h * vScale;
|
|
|
+
|
|
|
+ // фон (небо)
|
|
|
+ ctx.fillStyle = '#e8eef5';
|
|
|
+ ctx.fillRect(0, 0, W, H);
|
|
|
+
|
|
|
+ // слои
|
|
|
+ function fillLayer(h1, h2, color) {
|
|
|
+ const r1 = R_px + h1 * vScale;
|
|
|
+ const r2 = R_px + h2 * vScale;
|
|
|
+ ctx.fillStyle = color;
|
|
|
ctx.beginPath();
|
|
|
- ctx.moveTo(0, y2);
|
|
|
- ctx.lineTo(W, y2);
|
|
|
- ctx.stroke();
|
|
|
- ctx.setLineDash([]);
|
|
|
- ctx.fillStyle = '#1a2a4a';
|
|
|
- ctx.font = '10px "Roboto Mono", monospace';
|
|
|
- ctx.textAlign = 'left';
|
|
|
- ctx.fillText(l.name + ' (' + l.from + '–' + l.to + ' км)', 6, (y1 + y2) / 2 + 3);
|
|
|
- });
|
|
|
+ for (let x = 0; x <= W; x += 6) {
|
|
|
+ const dx1 = x - cx;
|
|
|
+ const sq1 = r1 * r1 - dx1 * dx1;
|
|
|
+ const y = cy_bottom - Math.sqrt(Math.max(0, sq1));
|
|
|
+ if (x === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
|
|
|
+ }
|
|
|
+ for (let x = W; x >= 0; x -= 6) {
|
|
|
+ const dx2 = x - cx;
|
|
|
+ const sq2 = r2 * r2 - dx2 * dx2;
|
|
|
+ const y = cy_bottom - Math.sqrt(Math.max(0, sq2));
|
|
|
+ ctx.lineTo(x, y);
|
|
|
+ }
|
|
|
+ ctx.closePath();
|
|
|
+ ctx.fill();
|
|
|
+ }
|
|
|
|
|
|
- const txX = 80, rxX = W - 80;
|
|
|
+ if (S > 30) fillLayer(60, 90, 'rgba(140,120,160,0.35)');
|
|
|
+ fillLayer(90, 120, 'rgba(100,160,100,0.30)');
|
|
|
+ fillLayer(150, 400, 'rgba(200,120,120,0.20)');
|
|
|
|
|
|
- const gwAlpha = f > 3e7 ? 0.3 : 0.85;
|
|
|
- ctx.strokeStyle = 'rgba(42,106,58,' + gwAlpha + ')';
|
|
|
- ctx.lineWidth = 2;
|
|
|
+ // подписи слоёв слева
|
|
|
+ function layerLabel(h, text, color) {
|
|
|
+ const r = R_px + h * vScale;
|
|
|
+ const dx = 60 - cx;
|
|
|
+ const yl = cy_bottom - Math.sqrt(Math.max(0, r * r - dx * dx));
|
|
|
+ ctx.fillStyle = color;
|
|
|
+ ctx.font = 'bold 11px "Roboto Mono", monospace';
|
|
|
+ ctx.textAlign = 'left';
|
|
|
+ ctx.fillText(text, 60, yl + 4);
|
|
|
+ }
|
|
|
+ if (S > 30) layerLabel(75, 'D 60–90 км', '#6a5a8a');
|
|
|
+ layerLabel(105, 'E 90–120 км', '#3a7a3a');
|
|
|
+ layerLabel(275, 'F 150–400 км', '#a06060');
|
|
|
+
|
|
|
+ // Земля
|
|
|
+ ctx.fillStyle = '#c8b890';
|
|
|
+ ctx.beginPath();
|
|
|
+ for (let x = 0; x <= W; x += 6) {
|
|
|
+ const dx = x - cx;
|
|
|
+ const sq = R_px * R_px - dx * dx;
|
|
|
+ const y = cy_bottom - Math.sqrt(Math.max(0, sq));
|
|
|
+ if (x === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
|
|
|
+ }
|
|
|
+ ctx.lineTo(W, H);
|
|
|
+ ctx.lineTo(0, H);
|
|
|
+ ctx.closePath();
|
|
|
+ ctx.fill();
|
|
|
+ ctx.strokeStyle = '#1a2a4a';
|
|
|
+ ctx.lineWidth = 1.5;
|
|
|
ctx.beginPath();
|
|
|
- for (let x = txX; x <= rxX; x += 3) {
|
|
|
- const t = (x - txX) / (rxX - txX);
|
|
|
- const amp = 4 * (1 - t);
|
|
|
- const y = groundY - 2 - amp * Math.sin((x - txX) * 0.15);
|
|
|
- if (x === txX) ctx.moveTo(x, y); else ctx.lineTo(x, y);
|
|
|
+ for (let x = 0; x <= W; x += 6) {
|
|
|
+ const dx = x - cx;
|
|
|
+ const sq = R_px * R_px - dx * dx;
|
|
|
+ const y = cy_bottom - Math.sqrt(Math.max(0, sq));
|
|
|
+ if (x === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
|
|
|
}
|
|
|
ctx.stroke();
|
|
|
|
|
|
- let reflectKm = 0, absorb = false;
|
|
|
- if (f < 3e6) { reflectKm = 110; absorb = isDay; }
|
|
|
- else if (f <= 3e7) { reflectKm = 300; }
|
|
|
- else { reflectKm = 0; }
|
|
|
+ const txY = groundAt(txX);
|
|
|
|
|
|
- if (reflectKm > 0) {
|
|
|
- const midX = (txX + rxX) / 2;
|
|
|
- const peakY = yOf(reflectKm);
|
|
|
- ctx.strokeStyle = absorb ? 'rgba(178,34,34,0.55)' : '#1a2a4a';
|
|
|
- ctx.lineWidth = 2;
|
|
|
- ctx.setLineDash(absorb ? [6, 4] : []);
|
|
|
+ // земная волна
|
|
|
+ const gwEndKm = 400;
|
|
|
+ ctx.strokeStyle = 'rgba(42,106,58,0.85)';
|
|
|
+ ctx.lineWidth = 2.5;
|
|
|
+ ctx.beginPath();
|
|
|
+ for (let d = 0; d <= gwEndKm; d += 6) {
|
|
|
+ const x = txX + d * hScaleX;
|
|
|
+ if (x > W - 4) break;
|
|
|
+ const y = groundAt(x);
|
|
|
+ if (d === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
|
|
|
+ }
|
|
|
+ ctx.stroke();
|
|
|
+ ctx.fillStyle = 'rgba(42,106,58,0.95)';
|
|
|
+ ctx.font = '10px "Roboto Mono", monospace';
|
|
|
+ ctx.textAlign = 'left';
|
|
|
+ ctx.fillText('земная волна', txX + 12, groundAt(txX + 80 * hScaleX) + 22);
|
|
|
+
|
|
|
+ // луч
|
|
|
+ if (absorbed) {
|
|
|
+ const refX = txX + xRefKm * hScaleX;
|
|
|
+ const refY = layerY(hReflect);
|
|
|
+ ctx.strokeStyle = 'rgba(178,34,34,0.85)';
|
|
|
+ ctx.lineWidth = 2.5;
|
|
|
+ ctx.setLineDash([6, 4]);
|
|
|
ctx.beginPath();
|
|
|
- ctx.moveTo(txX, groundY);
|
|
|
- ctx.lineTo(midX, peakY);
|
|
|
- ctx.lineTo(rxX, groundY);
|
|
|
+ ctx.moveTo(txX, txY);
|
|
|
+ ctx.lineTo(refX, refY);
|
|
|
ctx.stroke();
|
|
|
ctx.setLineDash([]);
|
|
|
- ctx.fillStyle = '#1a2a4a';
|
|
|
+ ctx.strokeStyle = '#b22222';
|
|
|
+ ctx.lineWidth = 2;
|
|
|
+ const sz = 8;
|
|
|
+ ctx.beginPath();
|
|
|
+ ctx.moveTo(refX - sz, refY - sz);
|
|
|
+ ctx.lineTo(refX + sz, refY + sz);
|
|
|
+ ctx.moveTo(refX - sz, refY + sz);
|
|
|
+ ctx.lineTo(refX + sz, refY - sz);
|
|
|
+ ctx.stroke();
|
|
|
+ ctx.fillStyle = '#b22222';
|
|
|
+ ctx.font = 'bold 11px "Roboto Mono", monospace';
|
|
|
+ ctx.textAlign = 'left';
|
|
|
+ ctx.fillText('поглощение в D', refX + 14, refY);
|
|
|
+ } else if (hReflect !== null) {
|
|
|
+ const refX = txX + xRefKm * hScaleX;
|
|
|
+ const refY = layerY(hReflect);
|
|
|
+ const rxXCalc = txX + xRxKm * hScaleX;
|
|
|
+ const rxX = Math.min(rxXCalc, W - 20);
|
|
|
+ const rxY = groundAt(rxX);
|
|
|
+
|
|
|
+ ctx.strokeStyle = '#1a4a7a';
|
|
|
+ ctx.lineWidth = 2.5;
|
|
|
+ ctx.beginPath();
|
|
|
+ ctx.moveTo(txX, txY);
|
|
|
+ ctx.lineTo(refX, refY);
|
|
|
+ ctx.lineTo(rxX, rxY);
|
|
|
+ ctx.stroke();
|
|
|
+
|
|
|
+ // точка отражения
|
|
|
+ ctx.fillStyle = '#b22222';
|
|
|
ctx.beginPath();
|
|
|
- ctx.arc(midX, peakY, 3, 0, Math.PI * 2);
|
|
|
+ ctx.arc(refX, refY, 5, 0, Math.PI * 2);
|
|
|
ctx.fill();
|
|
|
ctx.font = '10px "Roboto Mono", monospace';
|
|
|
+ ctx.textAlign = 'left';
|
|
|
+ ctx.fillStyle = '#b22222';
|
|
|
+ ctx.fillText('точка отражения', refX + 10, refY - 6);
|
|
|
+
|
|
|
+ // приёмник — подпись над точкой, чтобы не уходить за край
|
|
|
+ ctx.fillStyle = '#1a2a4a';
|
|
|
+ ctx.beginPath();
|
|
|
+ ctx.arc(rxX, rxY, 5, 0, Math.PI * 2);
|
|
|
+ ctx.fill();
|
|
|
ctx.textAlign = 'center';
|
|
|
- ctx.fillText('отражение от ' + (reflectKm > 200 ? 'F' : 'E'), midX, peakY - 8);
|
|
|
+ ctx.fillText('приём', rxX, rxY - 12);
|
|
|
+
|
|
|
+ // мёртвая зона
|
|
|
+ const gwEndX = txX + gwEndKm * hScaleX;
|
|
|
+ if (rxX > gwEndX + 15) {
|
|
|
+ ctx.fillStyle = 'rgba(178,34,34,0.10)';
|
|
|
+ ctx.beginPath();
|
|
|
+ for (let x = gwEndX; x <= rxX; x += 6) {
|
|
|
+ const dx = x - cx;
|
|
|
+ const sq = R_px * R_px - dx * dx;
|
|
|
+ const y = cy_bottom - Math.sqrt(Math.max(0, sq));
|
|
|
+ if (x === gwEndX) ctx.moveTo(x, y); else ctx.lineTo(x, y);
|
|
|
+ }
|
|
|
+ ctx.lineTo(rxX, H);
|
|
|
+ ctx.lineTo(gwEndX, H);
|
|
|
+ ctx.closePath();
|
|
|
+ ctx.fill();
|
|
|
+ ctx.fillStyle = '#b22222';
|
|
|
+ ctx.font = 'bold 10px "Roboto Mono", monospace';
|
|
|
+ ctx.textAlign = 'center';
|
|
|
+ const midX = (gwEndX + rxX) / 2;
|
|
|
+ const dxm = midX - cx;
|
|
|
+ const sqm = R_px * R_px - dxm * dxm;
|
|
|
+ const ym = cy_bottom - Math.sqrt(Math.max(0, sqm));
|
|
|
+ ctx.fillText('мёртвая зона', midX, ym + 22);
|
|
|
+ }
|
|
|
} else {
|
|
|
- ctx.strokeStyle = '#1a2a4a';
|
|
|
- ctx.lineWidth = 2;
|
|
|
+ ctx.strokeStyle = '#1a4a7a';
|
|
|
+ ctx.lineWidth = 2.5;
|
|
|
ctx.beginPath();
|
|
|
- ctx.moveTo(txX, groundY);
|
|
|
- ctx.lineTo(txX + 220, topY);
|
|
|
+ ctx.moveTo(txX, txY);
|
|
|
+ ctx.lineTo(txX + 200, 20);
|
|
|
ctx.stroke();
|
|
|
- ctx.fillStyle = '#1a2a4a';
|
|
|
+ const ax = txX + 200, ay = 20;
|
|
|
+ ctx.fillStyle = '#1a4a7a';
|
|
|
ctx.beginPath();
|
|
|
- ctx.moveTo(txX + 220, topY);
|
|
|
- ctx.lineTo(txX + 210, topY + 12);
|
|
|
- ctx.lineTo(txX + 230, topY + 12);
|
|
|
+ ctx.moveTo(ax, ay);
|
|
|
+ ctx.lineTo(ax - 12, ay + 14);
|
|
|
+ ctx.lineTo(ax + 4, ay + 14);
|
|
|
ctx.closePath();
|
|
|
ctx.fill();
|
|
|
ctx.textAlign = 'left';
|
|
|
- ctx.fillText('в космос', txX + 236, topY + 6);
|
|
|
- }
|
|
|
-
|
|
|
- if (isDay && f < 3e6) {
|
|
|
- const y1 = yOf(60), y2 = yOf(90);
|
|
|
- ctx.fillStyle = 'rgba(178,34,34,0.32)';
|
|
|
- ctx.fillRect(0, y2, W, y1 - y2);
|
|
|
- ctx.fillStyle = '#b22222';
|
|
|
- ctx.font = 'bold 11px "Roboto Mono", monospace';
|
|
|
- ctx.textAlign = 'center';
|
|
|
- ctx.fillText('ПОГЛОЩЕНИЕ В D-СЛОЕ', W / 2, yOf(75) + 4);
|
|
|
+ ctx.font = '10px "Roboto Mono", monospace';
|
|
|
+ ctx.fillText('проходит сквозь ионосферу', txX + 210, 26);
|
|
|
}
|
|
|
|
|
|
+ // передатчик значок
|
|
|
ctx.fillStyle = '#1a2a4a';
|
|
|
ctx.beginPath();
|
|
|
- ctx.moveTo(txX, groundY - 20);
|
|
|
- ctx.lineTo(txX - 6, groundY);
|
|
|
- ctx.lineTo(txX + 6, groundY);
|
|
|
- ctx.closePath();
|
|
|
- ctx.fill();
|
|
|
- ctx.beginPath();
|
|
|
- ctx.moveTo(rxX, groundY - 20);
|
|
|
- ctx.lineTo(rxX - 6, groundY);
|
|
|
- ctx.lineTo(rxX + 6, groundY);
|
|
|
+ ctx.moveTo(txX, txY - 20);
|
|
|
+ ctx.lineTo(txX - 8, txY);
|
|
|
+ ctx.lineTo(txX + 8, txY);
|
|
|
ctx.closePath();
|
|
|
ctx.fill();
|
|
|
ctx.font = '10px "Roboto Mono", monospace';
|
|
|
ctx.textAlign = 'center';
|
|
|
- ctx.fillText('ПРД', txX, groundY + 14);
|
|
|
- ctx.fillText('ПРМ', rxX, groundY + 14);
|
|
|
+ ctx.fillText('ПРД', txX, txY + 18);
|
|
|
|
|
|
+ // readout
|
|
|
document.getElementById('v-iono-f').textContent = formatFreq(f);
|
|
|
- let band, desc;
|
|
|
- if (f < 3e6) {
|
|
|
- band = 'ДВ/СВ';
|
|
|
- desc = isDay
|
|
|
- ? 'День: D-слой поглощает, дальний приём пропадает.'
|
|
|
- : 'Ночь: D-слой исчез, отражение от E. Дальняя связь.';
|
|
|
- } else if (f <= 3e7) {
|
|
|
- band = 'КВ';
|
|
|
- desc = 'Отражение от F-слоя, многократные скачки. Мёртвая зона.';
|
|
|
- } else {
|
|
|
- band = 'УКВ и выше';
|
|
|
- desc = 'Проходит сквозь ионосферу. Только прямая видимость.';
|
|
|
+ document.getElementById('v-iono-s').textContent = S.toFixed(0) + '%';
|
|
|
+
|
|
|
+ let status;
|
|
|
+ if (absorbed) status = 'поглощение в D-слое';
|
|
|
+ else if (hReflect !== null) status = 'отражение от ' + reflectLayer + '-слоя';
|
|
|
+ else status = 'проходит сквозь ионосферу';
|
|
|
+
|
|
|
+ let extra = '';
|
|
|
+ if (hReflect !== null && !absorbed) {
|
|
|
+ extra = 'Высота отражения: <span class="hl">' + hReflect.toFixed(0) + ' км</span><br>' +
|
|
|
+ 'Расстояние до приёма: <span class="hl">' + xRxKm.toFixed(0) + ' км</span>';
|
|
|
}
|
|
|
+
|
|
|
document.getElementById('r-iono').innerHTML =
|
|
|
- 'f = <span class="hl">' + formatFreq(f) + '</span> · ' + band + '<br>' + desc;
|
|
|
+ 'f = <span class="hl">' + formatFreq(f) + '</span> · ' + mode + '<br>' +
|
|
|
+ 'Солнце: <span class="hl">' + S.toFixed(0) + '%</span> · ' + status +
|
|
|
+ (extra ? '<br>' + extra : '');
|
|
|
+
|
|
|
+ let fb;
|
|
|
+ if (absorbed) fb = 'D-слой днём ионизирован и поглощает ДВ/СВ. Ночью D исчезает — дальний приём возвращается.';
|
|
|
+ else if (hReflect !== null && f <= 3e7) fb = 'Выше солнечная активность — выше F-слой — дальше точка отражения и приёма. Угол выхода луча фиксирован, меняется высота отражения.';
|
|
|
+ else if (hReflect !== null) fb = 'Отражение от E-слоя. Ночью слой ниже — приём ближе.';
|
|
|
+ else fb = 'УКВ и выше проходит сквозь ионосферу. Только прямая видимость.';
|
|
|
+ document.getElementById('fb-iono').textContent = fb;
|
|
|
}
|
|
|
|
|
|
- bDay.onclick = () => { isDay = true; updateDayBtn(); draw(); };
|
|
|
- bNight.onclick = () => { isDay = false; updateDayBtn(); draw(); };
|
|
|
- slider.oninput = draw;
|
|
|
- updateDayBtn();
|
|
|
currentDraw = draw;
|
|
|
+ sF.oninput = draw;
|
|
|
+ sS.oninput = draw;
|
|
|
draw();
|
|
|
}
|
|
|
|
|
|
@@ -1478,7 +1626,7 @@ function initIono() {
|
|
|
SCENE 9 — trainer
|
|
|
============================================================ */
|
|
|
const rangeData = [
|
|
|
- { id: 1, text: 'Связь с подводной лодкой', bin: 'dv', expl: 'ДВ/СДВ проникают в воду на десятки метров (скин-слой). УКВ в воде затухает за сантиметры.' },
|
|
|
+ { id: 1, text: 'Связь с подводной лодкой', bin: 'dv', expl: 'ДВ/СДВ проникают в воду на десятки метров. УКВ в воде затухает за сантиметры.' },
|
|
|
{ id: 2, text: 'Международное КВ-вещание', bin: 'kv', expl: 'КВ отражается от ионосферы (слой F) и огибает Землю многократными скачками.' },
|
|
|
{ id: 3, text: 'Местная AM-радиостанция', bin: 'sv', expl: 'СВ — сотни кГц. Земная волна вдоль поверхности. Днём D-слой поглощает дальний приём.' },
|
|
|
{ id: 4, text: 'Wi-Fi 2.4 ГГц', bin: 'svch', expl: '2.4 ГГц — СВЧ. Прямая видимость в помещении, мебель огибает плохо.' },
|
|
|
@@ -1734,7 +1882,7 @@ const testQuestions = [
|
|
|
expl: 'Ионосфера отражает, пока f ниже плазменной частоты слоя. Для УКВ f выше — проходит насквозь.'
|
|
|
},
|
|
|
{
|
|
|
- q: 'Где полоса шире — на несущей 1 МГц или 100 МГц при той же F_м (модулирующем сигнале)?',
|
|
|
+ q: 'Где полоса шире — на несущей 1 МГц или 100 МГц при той же F_м?',
|
|
|
opts: ['На 1 МГц', 'На 100 МГц', 'Одинакова: B = 2F_м', 'Зависит от глубины модуляции'],
|
|
|
correct: 2,
|
|
|
expl: 'Полоса АМ-сигнала B = 2F_м зависит от модулирующего сигнала, не от несущей.'
|
|
|
@@ -1761,13 +1909,13 @@ const testQuestions = [
|
|
|
q: 'Где заложена информация в ЧМ-сигнале?',
|
|
|
opts: ['В амплитуде несущей', 'В отклонении частоты несущей от среднего (девиации)', 'В самой несущей частоте', 'В длине волны'],
|
|
|
correct: 1,
|
|
|
- expl: 'В ЧМ амплитуда постоянна, а мгновенная частота меняется по закону модулирующего: f(t) = f_c + Δf·sin(2π·F_м·t). Информация — в девиации Δf, а не в амплитуде и не в самой несущей частоте.'
|
|
|
+ expl: 'В ЧМ амплитуда постоянна, а мгновенная частота меняется по закону модулирующего: f(t) = f_c + Δf·sin(2π·F_м·t). Информация — в девиации Δf.'
|
|
|
},
|
|
|
{
|
|
|
q: 'Чем ЧМ отличается от ФМ при одной и той же несущей и модулирующем сигнале?',
|
|
|
opts: ['В ЧМ меняется амплитуда, в ФМ — частота', 'В ЧМ мгновенная частота пропорциональна модулирующему, в ФМ — сама фаза', 'Это одно и то же, только названия разные', 'В ЧМ полоса уже, чем в АМ'],
|
|
|
correct: 1,
|
|
|
- expl: 'В ЧМ f(t) = f_c + Δf·sin(2π·F_м·t) — частота меняется по синусу. В ФМ φ(t) = 2π·f_c·t + β·sin(2π·F_м·t) — фаза меняется по синусу, а частота (производная фазы) — по косинусу, со сдвигом π/2. Внешне похожи, но физика разная.'
|
|
|
+ expl: 'В ЧМ f(t) = f_c + Δf·sin(2π·F_м·t) — частота меняется по синусу. В ФМ φ(t) = 2π·f_c·t + β·sin(2π·F_м·t) — фаза меняется по синусу, а частота (производная фазы) — по косинусу, со сдвигом π/2.'
|
|
|
}
|
|
|
];
|
|
|
|