feat(phys9 flagships): F5 Атвуд + F7 Лифт (Wave B пилоты)

F5. Машина Атвуда (§22):
- Canvas 640×420: блок с двумя массами на нити
- Slider'ы: m₁, m₂, трение в блоке
- Запуск: бо́льшая масса опускается, меньшая поднимается
- Физика: a = ((m₁-m₂)g - μ)/(m₁+m₂)
- Анимация: блок вращается, грузы движутся, размер пропорц. m
- Показ векторов сил тяжести (m₁g, m₂g) и натяжений (T) в покое
- Stats: a, T, v, t

F7. Лифт с динамометром (§24):
- Canvas 640×460: шахта с 5 этажами + большой циферблат справа
- Слева кабина с динамометром и грузом m
- Slider'ы: m груза, a разгона
- 5 режимов кнопок:
  - Разгон ⬆ (hold) → a = +a_in
  - Разгон ⬇ (hold) → a = -a_in
  - Стоп → a = 0
  - Свободное падение → a = -g (трос показывается пунктиром)
  - Сброс
- 2 динамометра: мини в кабине + большой круглый (шкала 0..2.5g)
- Stats: P, P/(mg), v лифта, h высота
- Контекстный feedback: невесомость / норма / перегрузка / P<0

Подключение в ch2: F5 на p22 (закон Ньютона II), F7 на p24 (вес).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-05-30 10:13:08 +03:00
parent d701d824ba
commit d190fd2de9
3 changed files with 419 additions and 1 deletions
@@ -0,0 +1,182 @@
// F5. Машина Атвуда (§22 в ch2) — связанные массы через блок.
(function(){
'use strict';
const B = () => window.PHYS9_FLAG_BASE;
const C = () => window.PHYS9_COLORS || {};
function init(secId){
if (!B()) return false;
const body = ''
+ '<div class="flag-sliders">'
+ '<label>$m_1$ (левая), кг: <b id="F5-m1v">3</b><input type="range" id="F5-m1" min="0.1" max="10" step="0.1" value="3"></label>'
+ '<label>$m_2$ (правая), кг: <b id="F5-m2v">2</b><input type="range" id="F5-m2" min="0.1" max="10" step="0.1" value="2"></label>'
+ '<label>Трение в блоке: <b id="F5-frv">0</b><input type="range" id="F5-fr" min="0" max="3" step="0.05" value="0"></label>'
+ '</div>'
+ '<canvas id="F5-cv" class="flag-canvas" width="640" height="420" style="height:420px"></canvas>'
+ '<div class="flag-controls">'
+ '<button class="flag-btn primary" id="F5-go">Запустить</button>'
+ '<button class="flag-btn" id="F5-reset">Сброс</button>'
+ '</div>'
+ '<div class="flag-stats">'
+ '<div class="flag-stat"><span class="lbl">$a = (m_1-m_2)g/(m_1+m_2)$</span><span class="val" id="F5-a">1.96 м/с²</span></div>'
+ '<div class="flag-stat"><span class="lbl">Натяжение T</span><span class="val" id="F5-T">23.5 Н</span></div>'
+ '<div class="flag-stat"><span class="lbl">$v$ текущая</span><span class="val" id="F5-v">0 м/с</span></div>'
+ '<div class="flag-stat"><span class="lbl">$t$ прошло</span><span class="val" id="F5-t">0 с</span></div>'
+ '</div>';
const card = B().makeCard(secId,
'F5. Машина Атвуда',
'Две массы через блок на нити. Бо́льшая опускается, меньшая поднимается. Физика: $a = (m_1-m_2)g/(m_1+m_2)$.',
body);
if (!card) return false;
const cv = document.getElementById('F5-cv');
const ctx = cv.getContext('2d');
const W = cv.width, H = cv.height;
const blockX = W/2, blockY = 60, blockR = 28;
let st = { y1: 220, y2: 220, v: 0, t: 0, running: false };
/* y — расстояние от блока вниз, +v = m1 опускается */
function readSliders(){
const m1 = +document.getElementById('F5-m1').value;
const m2 = +document.getElementById('F5-m2').value;
const fr = +document.getElementById('F5-fr').value;
document.getElementById('F5-m1v').textContent = m1.toFixed(2);
document.getElementById('F5-m2v').textContent = m2.toFixed(2);
document.getElementById('F5-frv').textContent = fr.toFixed(2);
const g = 9.8;
const a = ((m1 - m2)*g - fr) / (m1 + m2);
const T = m2 * (g + a);
document.getElementById('F5-a').textContent = a.toFixed(2) + ' м/с²';
document.getElementById('F5-T').textContent = T.toFixed(1) + ' Н';
}
function reset(){
st = { y1: 220, y2: 220, v: 0, t: 0, running: false };
document.getElementById('F5-go').textContent = 'Запустить';
document.getElementById('F5-v').textContent = '0 м/с';
document.getElementById('F5-t').textContent = '0 с';
draw();
}
function tick(dt){
if (!st.running) { draw(); return; }
const m1 = +document.getElementById('F5-m1').value;
const m2 = +document.getElementById('F5-m2').value;
const fr = +document.getElementById('F5-fr').value;
const g = 9.8;
const a = ((m1 - m2)*g - fr*Math.sign(st.v || (m1-m2))) / (m1 + m2);
st.v += a * dt;
/* движение: m1 опускается со скоростью v, m2 поднимается */
const dy = st.v * dt * 30; /* масштаб 30 px/м */
st.y1 += dy;
st.y2 -= dy;
/* ограничения */
if (st.y1 > H - 80){ st.y1 = H - 80; st.v = 0; st.running = false; document.getElementById('F5-go').textContent='Запустить'; }
if (st.y2 < 100){ st.y2 = 100; st.v = 0; st.running = false; document.getElementById('F5-go').textContent='Запустить'; }
if (st.y1 < 100){ st.y1 = 100; st.v = 0; st.running = false; document.getElementById('F5-go').textContent='Запустить'; }
if (st.y2 > H - 80){ st.y2 = H - 80; st.v = 0; st.running = false; document.getElementById('F5-go').textContent='Запустить'; }
st.t += dt;
document.getElementById('F5-v').textContent = st.v.toFixed(2) + ' м/с';
document.getElementById('F5-t').textContent = st.t.toFixed(1) + ' с';
draw();
}
function draw(){
const col = C();
ctx.fillStyle = col.bg || '#fafafa';
ctx.fillRect(0, 0, W, H);
/* потолок */
ctx.fillStyle = col.surface || '#a16207';
ctx.fillRect(0, 0, W, 30);
for (let x = 0; x < W; x += 12){
ctx.strokeStyle = '#7c4a08';
ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x+6, 6); ctx.stroke();
}
/* кронштейн к блоку */
ctx.strokeStyle = col.axis || '#1e293b';
ctx.lineWidth = 4;
ctx.beginPath(); ctx.moveTo(blockX, 30); ctx.lineTo(blockX, blockY - blockR); ctx.stroke();
/* блок */
ctx.fillStyle = col.bodyLight || '#cbd5e1';
ctx.beginPath(); ctx.arc(blockX, blockY, blockR, 0, Math.PI*2); ctx.fill();
ctx.strokeStyle = col.axis || '#1e293b';
ctx.lineWidth = 2.5;
ctx.stroke();
/* радиальные линии (вращение) */
const rot = st.v * 0.1;
for (let i = 0; i < 4; i++){
const a = rot + i*Math.PI/2;
ctx.beginPath();
ctx.moveTo(blockX + (blockR-5)*Math.cos(a), blockY + (blockR-5)*Math.sin(a));
ctx.lineTo(blockX + 5*Math.cos(a), blockY + 5*Math.sin(a));
ctx.stroke();
}
/* нити */
const cordL = blockX - blockR;
const cordR = blockX + blockR;
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(cordL, blockY); ctx.lineTo(cordL, st.y1);
ctx.moveTo(cordR, blockY); ctx.lineTo(cordR, st.y2);
ctx.stroke();
/* грузы */
const m1 = +document.getElementById('F5-m1').value;
const m2 = +document.getElementById('F5-m2').value;
const r1 = Math.min(40, 12 + m1*3);
const r2 = Math.min(40, 12 + m2*3);
ctx.fillStyle = col.forceGravity || '#2563eb';
ctx.fillRect(cordL - r1, st.y1, r1*2, r1);
ctx.strokeStyle = col.axis || '#1e293b';
ctx.lineWidth = 1.5;
ctx.strokeRect(cordL - r1, st.y1, r1*2, r1);
ctx.fillStyle = col.fail || '#dc2626';
ctx.fillRect(cordR - r2, st.y2, r2*2, r2);
ctx.strokeRect(cordR - r2, st.y2, r2*2, r2);
ctx.fillStyle = '#fff';
ctx.font = 'bold 12px Inter,sans-serif';
ctx.textAlign = 'center';
ctx.fillText('m₁ = '+m1.toFixed(1)+' кг', cordL, st.y1 + r1/2 + 4);
ctx.fillText('m₂ = '+m2.toFixed(1)+' кг', cordR, st.y2 + r2/2 + 4);
ctx.textAlign = 'left';
/* векторы силы тяжести */
if (st.t < 0.5 || !st.running){
B().arrow(ctx, cordL, st.y1 + r1, cordL, st.y1 + r1 + 40, col.forceGravity || '#2563eb', 2);
ctx.fillStyle = col.forceGravity || '#2563eb';
ctx.font = 'bold 12px Inter,sans-serif';
ctx.fillText('m₁g', cordL + 8, st.y1 + r1 + 30);
B().arrow(ctx, cordR, st.y2 + r2, cordR, st.y2 + r2 + 40, col.forceGravity || '#2563eb', 2);
ctx.fillText('m₂g', cordR + 8, st.y2 + r2 + 30);
/* T снизу */
B().arrow(ctx, cordL, st.y1, cordL, st.y1 - 30, col.forceTension || '#16a34a', 2);
ctx.fillStyle = col.forceTension || '#16a34a';
ctx.fillText('T', cordL + 8, st.y1 - 20);
B().arrow(ctx, cordR, st.y2, cordR, st.y2 - 30, col.forceTension || '#16a34a', 2);
ctx.fillText('T', cordR + 8, st.y2 - 20);
}
}
document.getElementById('F5-go').addEventListener('click', ()=>{
if (st.y1 >= H - 80 || st.y2 >= H - 80 || st.y1 < 110 || st.y2 < 110) reset();
st.running = !st.running;
document.getElementById('F5-go').textContent = st.running ? 'Пауза' : 'Запустить';
});
document.getElementById('F5-reset').addEventListener('click', reset);
['F5-m1','F5-m2','F5-fr'].forEach(id => document.getElementById(id).addEventListener('input', ()=>{
readSliders();
if (!st.running) reset();
}));
readSliders();
draw();
B().startLoop('F5', cv, tick);
return true;
}
if (window.PHYS9_FLAG_BASE) window.PHYS9_FLAG_BASE.register('F5', { init: init, cleanup: function(){} });
else document.addEventListener('DOMContentLoaded', ()=>{
if (window.PHYS9_FLAG_BASE) window.PHYS9_FLAG_BASE.register('F5', { init: init, cleanup: function(){} });
});
})();
@@ -0,0 +1,236 @@
// F7. Лифт с динамометром (§24 в ch2) — переживание перегрузки и невесомости.
(function(){
'use strict';
const B = () => window.PHYS9_FLAG_BASE;
const C = () => window.PHYS9_COLORS || {};
function init(secId){
if (!B()) return false;
const body = ''
+ '<div class="flag-sliders">'
+ '<label>Масса груза $m$, кг: <b id="F7-mv">1.0</b><input type="range" id="F7-m" min="0.1" max="10" step="0.1" value="1"></label>'
+ '<label>$a$ разгона, м/с²: <b id="F7-av">3</b><input type="range" id="F7-a" min="0.5" max="9.8" step="0.5" value="3"></label>'
+ '</div>'
+ '<canvas id="F7-cv" class="flag-canvas" width="640" height="460" style="height:460px"></canvas>'
+ '<div class="flag-controls">'
+ '<button class="flag-btn primary" id="F7-up">Разгон ⬆</button>'
+ '<button class="flag-btn primary" id="F7-dn">Разгон ⬇</button>'
+ '<button class="flag-btn" id="F7-stop">Стоп (a=0)</button>'
+ '<button class="flag-btn danger" id="F7-fall">Свободное падение!</button>'
+ '<button class="flag-btn" id="F7-reset">Сброс</button>'
+ '</div>'
+ '<div class="flag-stats">'
+ '<div class="flag-stat"><span class="lbl">Вес динамометра</span><span class="val" id="F7-P">9.8 Н</span></div>'
+ '<div class="flag-stat"><span class="lbl">Отношение P/mg</span><span class="val" id="F7-pmg">1.0 g</span></div>'
+ '<div class="flag-stat"><span class="lbl">$v$ лифта</span><span class="val" id="F7-v">0 м/с</span></div>'
+ '<div class="flag-stat"><span class="lbl">$h$ высота</span><span class="val" id="F7-h">0 м</span></div>'
+ '</div>'
+ '<div class="flag-feedback" id="F7-fb"></div>';
const card = B().makeCard(secId,
'F7. Лифт с динамометром',
'Управляй лифтом — наблюдай, как меняется показание динамометра. $P = m(g + a)$. При $a = -g$ — невесомость.',
body);
if (!card) return false;
const cv = document.getElementById('F7-cv');
const ctx = cv.getContext('2d');
const W = cv.width, H = cv.height;
let st = { y: 0, v: 0, a: 0, t: 0, mode: 'idle' };
function readSliders(){
document.getElementById('F7-mv').textContent = (+document.getElementById('F7-m').value).toFixed(2);
document.getElementById('F7-av').textContent = (+document.getElementById('F7-a').value).toFixed(1);
}
function reset(){
st = { y: 0, v: 0, a: 0, t: 0, mode: 'idle' };
document.getElementById('F7-fb').className = 'flag-feedback';
draw();
}
function tick(dt){
const m = +document.getElementById('F7-m').value;
const a_in = +document.getElementById('F7-a').value;
const g = 9.8;
/* Управление режимом */
if (st.mode === 'up') st.a = a_in;
else if (st.mode === 'dn') st.a = -a_in;
else if (st.mode === 'stop') st.a = 0;
else if (st.mode === 'fall') st.a = -g;
else st.a = 0;
st.v += st.a * dt;
st.y += st.v * dt;
/* ограничения */
if (st.y < 0){ st.y = 0; if (st.v < 0) st.v = 0; }
if (st.y > 50){ st.y = 50; if (st.v > 0) st.v = 0; }
st.t += dt;
/* Вес */
const P = m * (g + st.a);
const ratio = P/(m*g);
document.getElementById('F7-P').textContent = P.toFixed(2) + ' Н';
document.getElementById('F7-pmg').textContent = ratio.toFixed(2) + ' g';
document.getElementById('F7-v').textContent = st.v.toFixed(2) + ' м/с';
document.getElementById('F7-h').textContent = st.y.toFixed(1) + ' м';
const fb = document.getElementById('F7-fb');
if (Math.abs(P) < m*g*0.05){
fb.className = 'flag-feedback warn show';
fb.innerHTML = '&#9888; НЕВЕСОМОСТЬ! Динамометр показывает почти 0.';
} else if (P < 0){
fb.className = 'flag-feedback fail show';
fb.innerHTML = 'P < 0: предмет «отрывается» от пола (или сильно прижимается к потолку).';
} else if (ratio > 1.5){
fb.className = 'flag-feedback warn show';
fb.innerHTML = 'ПЕРЕГРУЗКА '+ratio.toFixed(1)+'g — космонавты тренируются на таких.';
} else if (Math.abs(ratio - 1) < 0.05){
fb.className = 'flag-feedback ok show';
fb.innerHTML = 'Нормальный вес — $a = 0$ или равномерное движение.';
} else {
fb.className = 'flag-feedback';
}
draw();
}
function draw(){
const col = C();
ctx.fillStyle = col.bg || '#fafafa';
ctx.fillRect(0, 0, W, H);
/* Шахта */
const shaftX = 80, shaftY = 30, shaftW = 280, shaftH = H - 60;
ctx.fillStyle = col.bgSubtle || '#f8fafc';
ctx.fillRect(shaftX, shaftY, shaftW, shaftH);
ctx.strokeStyle = col.axis || '#1e293b';
ctx.lineWidth = 2;
ctx.strokeRect(shaftX, shaftY, shaftW, shaftH);
/* Этажи */
ctx.strokeStyle = col.grid || '#e5e7eb';
ctx.lineWidth = 1;
for (let i = 0; i <= 5; i++){
const py = shaftY + (i/5) * shaftH;
ctx.beginPath(); ctx.moveTo(shaftX, py); ctx.lineTo(shaftX + shaftW, py); ctx.stroke();
ctx.fillStyle = col.textMuted || '#64748b';
ctx.font = '11px Inter,sans-serif';
ctx.fillText((5-i)+' эт', shaftX + 6, py + 12);
}
/* Лифт (кабина) */
const yNorm = st.y / 50; /* 0..1 */
const liftY = shaftY + shaftH - 100 - yNorm * (shaftH - 120);
ctx.fillStyle = col.bodyLight || '#cbd5e1';
ctx.fillRect(shaftX + 50, liftY, shaftW - 100, 90);
ctx.strokeStyle = col.bodyAccent || '#1e293b';
ctx.lineWidth = 2;
ctx.strokeRect(shaftX + 50, liftY, shaftW - 100, 90);
/* Трос */
ctx.strokeStyle = st.mode === 'fall' ? col.fail : col.axis || '#1e293b';
ctx.lineWidth = st.mode === 'fall' ? 1 : 2.5;
if (st.mode === 'fall'){
ctx.setLineDash([4, 4]);
}
ctx.beginPath();
ctx.moveTo(shaftX + shaftW/2, shaftY);
ctx.lineTo(shaftX + shaftW/2, liftY);
ctx.stroke();
ctx.setLineDash([]);
/* Динамометр внутри лифта */
const dynaX = shaftX + shaftW/2, dynaY = liftY + 20;
ctx.strokeStyle = col.axis || '#1e293b';
ctx.lineWidth = 1.5;
ctx.fillStyle = col.bg || '#fafafa';
ctx.fillRect(dynaX - 12, dynaY, 24, 22);
ctx.strokeRect(dynaX - 12, dynaY, 24, 22);
/* стрелка */
const m = +document.getElementById('F7-m').value;
const g = 9.8;
const P = m*(g + st.a);
const Pmax = m*g*2.5;
const norm = Math.max(0, Math.min(1, P/Pmax));
const angle = -Math.PI*0.75 + norm * Math.PI*1.5;
ctx.strokeStyle = col.fail || '#dc2626';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(dynaX, dynaY + 11);
ctx.lineTo(dynaX + 9*Math.cos(angle), dynaY + 11 + 9*Math.sin(angle));
ctx.stroke();
/* Груз ниже динамометра */
ctx.fillStyle = col.forceGravity || '#2563eb';
const gSize = Math.min(30, 10 + m*3);
ctx.fillRect(dynaX - gSize/2, dynaY + 30, gSize, gSize);
ctx.strokeStyle = col.bodyAccent || '#1e293b';
ctx.strokeRect(dynaX - gSize/2, dynaY + 30, gSize, gSize);
ctx.fillStyle = '#fff';
ctx.font = 'bold 10px Inter,sans-serif';
ctx.textAlign = 'center';
ctx.fillText(m.toFixed(1)+' кг', dynaX, dynaY + 30 + gSize/2 + 3);
ctx.textAlign = 'left';
/* подпись режима */
ctx.fillStyle = col.text || '#0f172a';
ctx.font = 'bold 13px Inter,sans-serif';
const modeText = { idle: '— пауза —', up: '↑ РАЗГОН ВВЕРХ', dn: '↓ РАЗГОН ВНИЗ', stop: '— равномерно/стоит', fall: 'СВОБОДНОЕ ПАДЕНИЕ' }[st.mode];
ctx.fillText(modeText, shaftX + shaftW + 20, 60);
/* большой циферблат справа */
const dialX = shaftX + shaftW + 130, dialY = 200, dialR = 70;
ctx.fillStyle = col.bg || '#fafafa';
ctx.beginPath(); ctx.arc(dialX, dialY, dialR, 0, Math.PI*2); ctx.fill();
ctx.strokeStyle = col.axis || '#1e293b';
ctx.lineWidth = 3;
ctx.stroke();
/* шкала 0..2.5 g */
ctx.lineWidth = 2;
for (let i = 0; i <= 10; i++){
const a = Math.PI*0.75 + (i/10) * Math.PI*1.5;
const r1 = dialR - 12, r2 = dialR;
ctx.strokeStyle = col.axis || '#1e293b';
ctx.beginPath();
ctx.moveTo(dialX + r1*Math.cos(a), dialY + r1*Math.sin(a));
ctx.lineTo(dialX + r2*Math.cos(a), dialY + r2*Math.sin(a));
ctx.stroke();
ctx.fillStyle = col.textMuted || '#64748b';
ctx.font = '10px Inter,sans-serif';
const lbl = (i*0.25).toFixed(1);
const lblR = dialR - 25;
ctx.fillText(lbl, dialX + lblR*Math.cos(a) - 8, dialY + lblR*Math.sin(a) + 4);
}
/* стрелка на большом */
const normBig = Math.max(0, Math.min(1, P/(m*g*2.5)));
const aBig = Math.PI*0.75 + normBig * Math.PI*1.5;
ctx.strokeStyle = col.fail || '#dc2626';
ctx.lineWidth = 4;
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(dialX, dialY);
ctx.lineTo(dialX + (dialR-15)*Math.cos(aBig), dialY + (dialR-15)*Math.sin(aBig));
ctx.stroke();
ctx.fillStyle = col.fail || '#dc2626';
ctx.beginPath(); ctx.arc(dialX, dialY, 5, 0, Math.PI*2); ctx.fill();
/* подпись */
ctx.fillStyle = col.text || '#0f172a';
ctx.textAlign = 'center';
ctx.font = 'bold 14px Inter,sans-serif';
ctx.fillText(P.toFixed(1) + ' Н', dialX, dialY + dialR + 22);
ctx.font = '11px Inter,sans-serif';
ctx.fillText('(' + (P/(m*g)).toFixed(2) + ' g)', dialX, dialY + dialR + 38);
ctx.textAlign = 'left';
}
document.getElementById('F7-up').addEventListener('mousedown', ()=>{ st.mode = 'up'; });
document.getElementById('F7-up').addEventListener('mouseup', ()=>{ st.mode = 'stop'; });
document.getElementById('F7-dn').addEventListener('mousedown', ()=>{ st.mode = 'dn'; });
document.getElementById('F7-dn').addEventListener('mouseup', ()=>{ st.mode = 'stop'; });
document.getElementById('F7-stop').addEventListener('click', ()=>{ st.mode = 'stop'; });
document.getElementById('F7-fall').addEventListener('click', ()=>{ st.mode = 'fall'; });
document.getElementById('F7-reset').addEventListener('click', reset);
['F7-m','F7-a'].forEach(id => document.getElementById(id).addEventListener('input', readSliders));
readSliders();
draw();
B().startLoop('F7', cv, tick);
return true;
}
if (window.PHYS9_FLAG_BASE) window.PHYS9_FLAG_BASE.register('F7', { init: init, cleanup: function(){} });
else document.addEventListener('DOMContentLoaded', ()=>{
if (window.PHYS9_FLAG_BASE) window.PHYS9_FLAG_BASE.register('F7', { init: init, cleanup: function(){} });
});
})();
+1 -1
View File
@@ -790,7 +790,7 @@ function _injectTasks(id){
var body = document.getElementById(id + '-body');
if(!body || body.querySelector('.legacy-tasks')) return;
body.insertAdjacentHTML('beforeend', _makeTaskBlock(id));
setTimeout(function(){ try { if(window.renderTask) window.renderTask(id); if(window.renderNav) window.renderNav(id); } catch(e){} try { if(window.PHYS9_FINALS_INIT && /^final\d+$/.test(id)) window.PHYS9_FINALS_INIT(id); } catch(e){ console.warn("phys9 final init:", e.message); } try { if(window.PHYS9_CH2_WIDGETS && window.PHYS9_CH2_WIDGETS[id]) window.PHYS9_CH2_WIDGETS[id](); } catch(e){ console.warn('phys9 widget init:', e.message); } try { if(window.PHYS9_FLAG_BASE){ if(id==='p17') window.PHYS9_FLAG_BASE.mount('F4','p17'); } } catch(e){ console.warn('phys9 flag init:', e.message); } }, 60);
setTimeout(function(){ try { if(window.renderTask) window.renderTask(id); if(window.renderNav) window.renderNav(id); } catch(e){} try { if(window.PHYS9_FINALS_INIT && /^final\d+$/.test(id)) window.PHYS9_FINALS_INIT(id); } catch(e){ console.warn("phys9 final init:", e.message); } try { if(window.PHYS9_CH2_WIDGETS && window.PHYS9_CH2_WIDGETS[id]) window.PHYS9_CH2_WIDGETS[id](); } catch(e){ console.warn('phys9 widget init:', e.message); } try { if(window.PHYS9_FLAG_BASE){ if(id==='p17') window.PHYS9_FLAG_BASE.mount('F4','p17'); else if(id==='p22') window.PHYS9_FLAG_BASE.mount('F5','p22'); else if(id==='p24') window.PHYS9_FLAG_BASE.mount('F7','p24'); } } catch(e){ console.warn('phys9 flag init:', e.message); } }, 60);
}
var _origEnsureBuilt = ensureBuilt;
ensureBuilt = function(id){ _origEnsureBuilt(id); _injectTasks(id); };