'use strict'; /* ════════════════════════════════════════════════════════════════════════ _sim_demo — рукописная демо-спека «Бросок тела» (projectile) для проверки рантайма Фазы 0. Регистрируется как id 'customdemo' ТОЛЬКО за флагом — не светится ученикам в каталоге (карточка не добавляется в SIMS). Включение для проверки (любой из вариантов): - URL: /lab?simdemo=1 (или ?sim=customdemo прямой deep-link) - глоб: window.LAB_SHOW_SPEC_DEMO = true (до загрузки labs-скриптов) - localStorage.setItem('lab-spec-demo','1') Проверка: открыть /lab?simdemo=1 -> карточка появится; либо открыть /lab?sim=customdemo напрямую. Слайдеры угла/скорости меняют траекторию, play/pause/reset работают. Это ВРЕМЕННЫЙ раздел (удалить после Фазы 4). ════════════════════════════════════════════════════════════════════════ */ (function (global) { function demoEnabled() { try { var qs = (global.location && global.location.search) || ''; if (/[?&]simdemo=1\b/.test(qs)) return true; if (/[?&]sim=customdemo\b/.test(qs)) return true; if (global.LAB_SHOW_SPEC_DEMO === true) return true; if (global.localStorage && global.localStorage.getItem('lab-spec-demo') === '1') return true; } catch (e) { /* noop */ } return false; } // Спека v1+ (Фаза 1): бросок тела. g фиксирован 10 -> y = y0 + v*sin(θ)*t - 5*t^2. // Старт (x0,y0) — перетаскиваемая ручка (drag). plot — статическая параболическая // траектория y(x); readout — дальность и макс. высота. Вектор v0 — origin+dx/dy. var PROJECTILE_DEMO = { id: 'customdemo', cat: 'phys', meta: { title: 'Демо: бросок тела', desc: 'Спек-симуляция (Фаза 1): слайдеры, drag-старт, график, readout.' }, viewport: { xmin: 0, xmax: 60, ymin: 0, ymax: 30, grid: true, axes: true, bg: '#0D0D1A' }, time: { autoplay: false, loop: true, duration: 8, speed: 1 }, params: [ { name: 'theta', label: 'Угол θ', min: 0, max: 90, step: 1, value: 45, unit: '°' }, { name: 'v', label: 'Скорость v', min: 0, max: 30, step: 0.5, value: 20, unit: 'м/с' }, { name: 'x0', label: 'Старт X', min: 0, max: 20, step: 0.5, value: 2, unit: 'м' }, { name: 'y0', label: 'Старт Y', min: 0, max: 25, step: 0.5, value: 0, unit: 'м' } ], objects: [ // снаряд: x = x0 + v*cos(θ)*t, y = y0 + v*sin(θ)*t - 5 t^2 (но не ниже 0) { id: 'ball', type: 'point', x: 'x0 + v*cos(theta*pi/180)*t', y: 'max(0, y0 + v*sin(theta*pi/180)*t - 5*t^2)', r: 7, color: '#06D6E0', trail: true, trailColor: '#9B5DE5' }, // график траектории y(x): парабола броска, var=x на [x0, x0+дальность]. // y(x) = y0 + tan(θ)(x-x0) - g(x-x0)^2/(2 v^2 cos^2θ), g=10. { type: 'plot', color: '#FFD166', width: 1.6, var: 'x', range: ['x0', 'x0 + v*v*sin(2*theta*pi/180)/10 + 0.001'], samples: 200, expr: 'max(0, y0 + tan(theta*pi/180)*(x-x0) - 10*(x-x0)^2/(2*v*v*cos(theta*pi/180)^2))' }, // перетаскиваемая ручка старта (drag по обеим осям -> x0/y0) { id: 'start', type: 'point', x: 'x0', y: 'y0', r: 8, color: '#EF476F', drag: { axis: 'xy', param: 'x0', paramY: 'y0', min: 0, max: 25 } }, // вектор начальной скорости из старта (origin + dx/dy) { type: 'vector', origin: ['x0', 'y0'], dx: 'cos(theta*pi/180)*v*0.4', dy: 'sin(theta*pi/180)*v*0.4', color: '#FFD166', width: 3 }, // земля { type: 'segment', x1: 0, y1: 0, x2: 60, y2: 0, color: 'rgba(255,255,255,0.35)', width: 2 }, // подпись над снарядом { type: 'label', latex: true, x: 'ball.x', y: 'ball.y + 2.5', text: 'v_0', color: '#06D6E0', size: 15 }, // readout: дальность полёта R = v^2 sin(2θ)/g (для y0=0) и макс. высота H { type: 'readout', label: 'R', unit: 'м', precision: 1, color: '#FFD166', expr: 'x0 + v*v*sin(2*theta*pi/180)/10' }, { type: 'readout', label: 'H', unit: 'м', precision: 1, color: '#06D6E0', expr: 'y0 + (v*sin(theta*pi/180))^2/20' } ] }; function tryRegister() { if (!demoEnabled()) return; if (typeof global.registerSpecSim !== 'function') { if (global.console) console.warn('[sim-demo] registerSpecSim недоступен'); return; } global.registerSpecSim(PROJECTILE_DEMO); // Если каталог уже отрисован, добавить карточку демо вручную (минимально- // инвазивно: только когда флаг включён; не трогаем SIMS/каталожный рендер). addDemoCardIfNeeded(); } function addDemoCardIfNeeded() { var grid = document.getElementById('sim-grid'); if (!grid) return; if (document.getElementById('sim-card-customdemo')) return; var m = global.LabRegistry && global.LabRegistry.get('customdemo'); if (!m) return; var preview = global.LabRegistry.resolvePreview(m); var card = document.createElement('div'); card.id = 'sim-card-customdemo'; card.className = 'sim-card'; card.setAttribute('onclick', "openSim('customdemo')"); card.innerHTML = preview + '