From 98f955a85e9d4666331d2c0c6bc23679a0c99b65 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Sat, 30 May 2026 12:14:48 +0300 Subject: [PATCH] =?UTF-8?q?fix(phys7):=20=D0=B3=D0=BB=D0=B0=D0=B2=D0=BD?= =?UTF-8?q?=D1=8B=D0=B9=20=D0=B2=D0=B8=D0=B7=D1=83=D0=B0=D0=BB=20=D0=BA?= =?UTF-8?q?=D1=83=D1=80=D1=81=D0=B0=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0?= =?UTF-8?q?=D0=B5=D1=82=20+=20=C2=A722,=20=C2=A724=20=D0=B8=D0=BD=D1=82?= =?UTF-8?q?=D0=B5=D1=80=D0=B0=D0=BA=D1=82=D0=B8=D0=B2=D1=8B=20=D1=83=D0=BB?= =?UTF-8?q?=D1=83=D1=87=D1=88=D0=B5=D0=BD=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. БАГ В HillSlideSim (phys.js): - При reset() начальное состояние x=0, h=hStart, v=0. - Первый step(): dropped=0 → v=0 → x не растёт → h не падает → тележка навсегда стоит на вершине (бесконечный нуль). Анимация ничего не показывала. - Фикс: reset() даёт начальный толчок (x = L*0.01) и v по энергии для этой малой высоты падения. step() теперь корректно ускоряет тележку. - Тест node: за 2.05 с тележка проходит 11.7 м, h падает с 4.9 м до 0.86 м, v растёт с 1.4 до 9.0 м/с. Е_полн ≈ const. 2. §22 «Сила тяжести» — новый IV-2 «Падение на 4 планетах»: - SVG 4-колоночная сцена, 4 шарика стартуют с одной высоты. - Slider высоты 2..20 м, кнопки «Уронить» / «Сброс». - Свободное падение по h(t) = h₀ − gt²/2 для каждой планеты (Земля 9.8, Луна 1.6, Марс 3.7, Юпитер 24.8). - Видно: Юпитер падает первым, Луна последней; для каждого сохраняется время падения √(2h/g) и итоговая v = g·t. - Live info: текущее t, статус каждого шарика (падает / упал за X с, v = Y м/с). 3. §24 «Вес тела» — переработан IV-1 «Лифт с динамометром»: - Было: 4 статичных схемы покой/падение/верх/вниз. - Стало: динамический симулятор. Кабина лифта со стрелкой ускорения снаружи, внутри — груз на пружинном динамометре с шкалой. - 2 slider'а: масса 0.5..10 кг, ускорение −10..+10 м/с². - 4 кнопки-пресета: Покой / Едет вверх / Едет вниз / Свободное падение. - Формула P = m(g + a) считается в реальном времени. - 4 режима с автоопределением: ПОКОЙ / НЕВЕСОМОСТЬ / ПЕРЕГРУЗКА / ПОНИЖЕННЫЙ ВЕС с разной цветовой индикацией. - Пружина динамометра реально растягивается/сжимается в зависимости от P; указатель и шкала тоже. Parse OK, smoke (15 экспортов CH3) OK. --- frontend/js/phys.js | 28 +++- frontend/js/phys7_ch3_widgets.js | 240 ++++++++++++++++++++++--------- 2 files changed, 194 insertions(+), 74 deletions(-) diff --git a/frontend/js/phys.js b/frontend/js/phys.js index 822f64b..0316e82 100644 --- a/frontend/js/phys.js +++ b/frontend/js/phys.js @@ -672,15 +672,33 @@ class HillSlideSim { this.scale = opts.scale || 30; this.reset(); } - reset() { this.t = 0; this.x = 0; this.v = 0; this.h = this.hStart; } + reset() { + // Начальный импульс: тележка стартует с лёгким толчком (1% от L) и небольшой скоростью, + // иначе при h=hStart, v=0 — она навсегда останется на вершине (бесконечный нуль). + const L = this.hStart * 4; + this.t = 0; + this.x = L * 0.01; + const xRel = this.x / L; + this.h = this.hStart * Math.pow(1 - xRel, 2); + this.v = Math.sqrt(2 * this.g * (this.hStart - this.h)); + } step(dt) { const gEff = this.g * (1 - this.friction); this.t += dt; - const dropped = this.hStart - this.h; - if (this.h <= 0) { this.h = 0; this.v = Math.sqrt(2 * gEff * this.hStart); return; } - this.v = Math.sqrt(2 * gEff * Math.max(0, dropped)); - this.x += this.v * dt; const L = this.hStart * 4; + if (this.h <= 0.001 || this.x >= L) { + this.h = 0; + // Финальная скорость с учётом потерь на трение. + this.v = Math.sqrt(2 * gEff * this.hStart); + this.x = L; + return; + } + // Скорость по закону сохранения энергии (с учётом трения). + const dropped = Math.max(0.0001, this.hStart - this.h); + this.v = Math.sqrt(2 * gEff * dropped); + // Скорость по x — это горизонтальная компонента; для наглядного моделирования + // используем её напрямую как темп роста x. + this.x += this.v * dt; const xRel = Math.min(this.x, L) / L; this.h = this.hStart * Math.pow(1 - xRel, 2); } diff --git a/frontend/js/phys7_ch3_widgets.js b/frontend/js/phys7_ch3_widgets.js index be5ebaf..6814958 100644 --- a/frontend/js/phys7_ch3_widgets.js +++ b/frontend/js/phys7_ch3_widgets.js @@ -1223,8 +1223,18 @@ function add_p22(){ + '$F_т = m g = $ 0.98 Н (на Земле, $g = $ 9.8 Н/кг)' + ''); - /* IV-2 КВИЗ */ - h += wgWrap('p22-iv2', 'КВИЗ', 'Сила тяжести', '', + /* IV-2 СИМ — Падение тела на 4 планетах */ + h += wgWrap('p22-iv2', 'СИМ', 'Падение с одной высоты на 4 планетах', 'Нажми «Уронить» — увидь, кто упадёт быстрее. Время и скорость в реальном времени.', + '
' + + '' + + '' + + '' + + '
' + + '' + + '
'); + + /* IV-3 КВИЗ (был IV-2) */ + h += wgWrap('p22-iv3', 'КВИЗ', 'Сила тяжести', '', '
' + quizQuestion('p22-q', 0, 'Куда направлена сила тяжести на поверхности Земли?', ['Вверх','Вертикально вниз (к центру Земли)','В сторону Солнца','В произвольном направлении'], 1) + quizQuestion('p22-q', 1, '$g$ на Земле примерно равно…', ['1 Н/кг','9,8 Н/кг','100 Н/кг','1000 Н/кг'], 1) @@ -1280,6 +1290,80 @@ function add_p22(){ document.getElementById('p22-m-r').addEventListener('input', upd22); upd22(); + // §22 IV-2 — Падение тел на 4 планетах + const planets22 = [ + { nm:'Земля', g:9.8, col:'#0284c7' }, + { nm:'Луна', g:1.6, col:'#94a3b8' }, + { nm:'Марс', g:3.7, col:'#dc2626' }, + { nm:'Юпитер', g:24.8, col:'#d97706' } + ]; + let drop22 = { t:0, raf:0, running:false, h0:10 }; + function drawDrop22(){ + const svg = document.getElementById('p22-drop-svg'); + if(!svg) return; + const W = 380, H = 200, baseY = 175; + const pxPerM = (baseY - 25) / drop22.h0; + const colW = W / planets22.length; + let s = ''; + s += ''; + planets22.forEach((p, i) => { + const cx = colW * i + colW/2; + const startY = baseY - drop22.h0 * pxPerM; + // Падение: h(t) = h0 - g*t²/2 (свободное падение) + const fall = 0.5 * p.g * drop22.t * drop22.t; + const hNow = Math.max(0, drop22.h0 - fall); + const cy = baseY - hNow * pxPerM - 6; + const v = p.g * drop22.t; + const fellTime = Math.sqrt(2 * drop22.h0 / p.g); + const onGround = drop22.t >= fellTime; + // Опорная линия высоты + s += ''; + // Шарик + s += ''; + // Подпись планеты + s += '' + p.nm + ''; + s += 'g=' + p.g + ''; + if(onGround) s += '' + fellTime.toFixed(2) + ' с'; + }); + svg.innerHTML = s; + // Info + const status = planets22.map(p => { + const fellTime = Math.sqrt(2 * drop22.h0 / p.g); + const onGround = drop22.t >= fellTime; + const vNow = onGround ? p.g * fellTime : p.g * drop22.t; + return '' + p.nm + ': ' + (onGround ? 'упал за ' + fellTime.toFixed(2) + ' с, $v = ' + vNow.toFixed(1) + '$ м/с' : 'падает, $v = ' + vNow.toFixed(1) + '$ м/с'); + }).join(' · '); + document.getElementById('p22-drop-info').innerHTML = '$t = ' + drop22.t.toFixed(2) + '$ с · ' + status; + renderMath(document.getElementById('p22-drop-info')); + } + function loop22(){ + if(!drop22.running) return; + drop22.t += 0.04; + drawDrop22(); + // Останавливаем когда даже самая медленная (Луна) упала + const maxT = Math.sqrt(2 * drop22.h0 / 1.6); + if(drop22.t >= maxT + 0.5){ drop22.running = false; return; } + drop22.raf = requestAnimationFrame(loop22); + } + document.getElementById('p22-drop-h-r').addEventListener('input', () => { + drop22.h0 = +document.getElementById('p22-drop-h-r').value; + document.getElementById('p22-drop-h').textContent = drop22.h0; + drop22.t = 0; drop22.running = false; + if(drop22.raf) cancelAnimationFrame(drop22.raf); + drawDrop22(); + }); + document.getElementById('p22-drop').addEventListener('click', () => { + drop22.t = 0; drop22.running = true; + if(drop22.raf) cancelAnimationFrame(drop22.raf); + loop22(); + }); + document.getElementById('p22-drop-reset').addEventListener('click', () => { + drop22.t = 0; drop22.running = false; + if(drop22.raf) cancelAnimationFrame(drop22.raf); + drawDrop22(); + }); + drawDrop22(); + wireDnd('p22-dnd', [ { id:'a1', cat:'1n' },{ id:'a2', cat:'10n' },{ id:'a3', cat:'100n' }, { id:'a4', cat:'1n' },{ id:'a5', cat:'10n' },{ id:'a6', cat:'100n' } @@ -1444,14 +1528,18 @@ function add_p24(){ + 'На пружинных весах (динамометре) измеряют силу, с которой тело растягивает пружину. ' + 'Это и есть вес тела в Ньютонах.'); - /* IV-1 СИМ: тело в 3 ситуациях */ - h += wgWrap('p24-iv1', 'СИМ', 'Три ситуации: покой, падение, ускорение', 'Выбери ситуацию — увидь стрелки сил.', - '
' - + [['rest','На столе (покой)'],['fall','Свободное падение'],['up','Ускоряется вверх'],['down','Ускоряется вниз']].map((s, i) => - '').join('') + /* IV-1 СИМ: динамический лифт с динамометром */ + h += wgWrap('p24-iv1', 'СИМ', 'Лифт с динамометром: вес меняется при ускорении', 'Подвинь slider ускорения — увидь, как меняется показание динамометра (вес).', + '
' + + '' + + '' + '
' - + '' - + '
'); + + '
' + + [['0','Покой'],['2','Едет вверх с ускорением'],['-2','Едет вниз с ускорением'],['-10','Свободное падение']].map(p => + '').join('') + + '
' + + '' + + '
'); /* IV-2 КВИЗ */ h += wgWrap('p24-iv2', 'КВИЗ', 'Вес vs сила тяжести', '', @@ -1489,72 +1577,86 @@ function add_p24(){ h += readButton('p24'); body.innerHTML = h; - function draw24(s){ + function draw24(){ const svg = document.getElementById('p24-svg'); if(!svg) return; - const cx = 180, cy = 100; - let html = ''; - let info = ''; - if(s === 'rest'){ - // Тело на столе - html += ''; - for(let i = 0; i < 14; i++) html += ''; - html += ''; - // F_т вниз (фиолетовая) - html += ''; - html += ''; - html += 'F_т (на тело)'; - // P вниз от низа тела (индиго) - html += ''; - html += ''; - html += 'P (на стол)'; - info = 'Покой на горизонтальной опоре: $P = F_т = mg$, но приложены к разным телам.'; - } else if(s === 'fall'){ - html += ''; - // F_т вниз - html += ''; - html += ''; - html += 'F_т ↓'; - // P = 0 — пиктограмма - html += 'P = 0'; - html += 'невесомость'; - info = 'Свободное падение / орбита: сила тяжести есть ($F_т = mg \\ne 0$), но вес $P = 0$ — тело ни на что не давит.'; - } else if(s === 'up'){ - html += ''; - html += ''; // тяга вверх - html += ''; - // F_т - html += ''; - html += ''; - html += 'F_т = mg'; - // P больше — длинная стрелка - html += ''; - html += ''; - html += 'P > mg (перегрузка)'; - info = 'Лифт ускоряется вверх: вес больше $mg$ — это перегрузка. Космонавты на старте испытывают $P \\approx 3 mg$.'; - } else { - // down - html += ''; - html += ''; - html += ''; - html += ''; - html += 'F_т = mg'; - // P меньше — короткая стрелка - html += ''; - html += ''; - html += 'P < mg'; - info = 'Лифт ускоряется вниз: вес меньше $mg$. Если ускорение $= g$, то $P = 0$ — невесомость.'; + const m = +document.getElementById('p24-m-r').value; + const a = +document.getElementById('p24-a-r').value; // положительное = вверх + const g = 10; + // Вес на пружине: P = m(g + a). a > 0 (лифт ускоряется вверх) → P > mg. + // Если лифт в свободном падении (a = -g), P = 0. + const P = Math.max(0, m * (g + a)); + const W = 380, H = 260; + // Кабина лифта 160×220, центрирована + const cabX = 110, cabY = 20, cabW = 160, cabH = 220; + let s = ''; + // Шахта (стенки) + s += ''; + s += ''; + // Кабина + s += ''; + // Стрелка ускорения слева снаружи + if(Math.abs(a) > 0.3){ + const aY = cabY + cabH/2; + const arrLen = Math.min(50, Math.abs(a) * 4); + const dir = a > 0 ? -1 : 1; // вверх = -y, вниз = +y + s += ''; + s += ''; + s += 'a = ' + (a > 0 ? '+' : '') + a + ''; } - svg.innerHTML = html; - document.getElementById('p24-info').innerHTML = info; + // Динамометр (вертикальный): корпус, пружина, груз + const dynX = cabX + cabW/2, dynTop = cabY + 30; + s += ''; + // Корпус + s += ''; + // Пружина — длина пропорц. P + const maxStretch = 80; + const Pmax = m * (g + 10); // P при a = +10 + const stretch = Math.min(maxStretch, (P / Pmax) * maxStretch); + const sprBot = dynTop + 5 + stretch; + const coils = 6; + let path = 'M ' + dynX + ' ' + (dynTop + 5); + for(let i = 0; i < coils; i++){ + path += ' L ' + (dynX + (i%2 ? 9 : -9)) + ' ' + (dynTop + 5 + (i + 0.5) * stretch/coils); + } + path += ' L ' + dynX + ' ' + sprBot; + s += ''; + // Шкала справа + for(let i = 0; i <= 10; i++){ + const ty = dynTop + 5 + (i / 10) * maxStretch; + s += ''; + if(i % 2 === 0) s += '' + ((Pmax * i / 10).toFixed(0)) + ''; + } + // Указатель + s += ''; + // Груз — кружок снизу пружины + s += ''; + s += '' + m + ' кг'; + // Показание динамометра в нижней части кабины + s += 'P = ' + P.toFixed(1) + ' Н'; + // Подпись режима + let mode, modeCol; + if(Math.abs(P - m*g) < 0.01){ mode = 'ПОКОЙ или равномерно: P = mg'; modeCol = '#475569'; } + else if(P < 0.1){ mode = 'НЕВЕСОМОСТЬ: P = 0'; modeCol = '#10b981'; } + else if(P > m*g){ mode = 'ПЕРЕГРУЗКА: P > mg'; modeCol = '#dc2626'; } + else { mode = 'ПОНИЖЕННЫЙ ВЕС: P < mg'; modeCol = '#0284c7'; } + s += '' + mode + ''; + svg.innerHTML = s; + document.getElementById('p24-m').textContent = m; + document.getElementById('p24-a').textContent = a > 0 ? '+' + a : a; + const Ft = m * g; + document.getElementById('p24-info').innerHTML = + '$F_т = mg = ' + Ft.toFixed(1) + '$ Н — сила тяжести на тело (не меняется при ускорении).
' + + '$P = m(g + a) = ' + m + ' \\cdot (' + g + (a >= 0 ? ' + ' : ' − ') + Math.abs(a) + ') = ' + P.toFixed(1) + '$ Н — показание динамометра (вес).
' + + '' + mode + ''; renderMath(document.getElementById('p24-info')); } - body.querySelectorAll('.p24-sit').forEach(btn => btn.addEventListener('click', () => { - body.querySelectorAll('.p24-sit').forEach(b => { b.style.background = '#fff'; b.style.color = '#dc2626'; }); - btn.style.background = '#dc2626'; btn.style.color = '#fff'; - draw24(btn.dataset.s); + body.querySelectorAll('.p24-preset').forEach(btn => btn.addEventListener('click', () => { + document.getElementById('p24-a-r').value = btn.dataset.a; + draw24(); })); - draw24('rest'); + ['p24-m-r','p24-a-r'].forEach(id => document.getElementById(id).addEventListener('input', draw24)); + draw24(); wireDnd('p24-dnd', [ { id:'a1', cat:'ft' },{ id:'a2', cat:'ft' },{ id:'a3', cat:'p' },