feat(labs): wave 3 — 5 new sims + optics merger

Оптическая скамья (opticsbench) — merger thinlens + mirror + refraction
- 4 режима: «Свободная сборка» / «Линза» / «Зеркало» / «Преломление»
- Все 3 движка слиты в OpticsBenchSim (1583 строк)
- Backward compat: #thinlens / #mirrors / #refraction → #opticsbench
- Удалены: thinlens.js, mirror.js, refraction.js

Радиоактивный распад (radioactive) — новая сима
- Monte-Carlo распад: λ·dt вероятность на тик, частицы меняют цвет, эмитируются α/β/γ
- Real-time N(t) график с теоретической кривой N₀·exp(-λt)
- 7 изотопов: ¹⁴C, ¹³¹I, ¹³⁷Cs, ²²⁶Ra, ⁴⁰K, ²³⁸U-chain, ²³⁵U-chain
- Цепочки распадов (U-238: 14 шагов сокращены до 5 ключевых)
- Dating mode для C-14: t = ln(N₀/N)/λ
- HUD: периодов прошло, % распалось, активность в Бк

Тепловые двигатели (heatengine) — новая сима
- 4 цикла: Карно / Отто / Дизель / Брайтон
- PV-диаграмма с замкнутым циклом, заполненной площадью работы
- Аналитически точные изотермы (PV=nRT) и адиабаты (PV^γ=const)
- Анимированный поршень с резервуарами (красный T_h / синий T_c)
- Частицы газа, скорость ∝ √T
- Hover-tooltips с формулами для каждого сегмента

Логические схемы (logic) — новая сима для информатики
- Drag-drop конструктор: 12 типов компонентов (INPUT/CLOCK/OUTPUT/AND/OR/NOT/XOR/NAND/NOR/XNOR/BUF/wire)
- Топологическая сортировка для propagation, цветовая подсветка HIGH/LOW
- Авто-генерация булевого выражения (∧ ∨ ¬ ⊕)
- Авто-таблица истинности (до 2^6 = 64 строк)
- 6 пресетов: полусумматор, полный сумматор, RS-триггер, D-триггер, декодер 2-в-4, мультиплексор 2-в-1

Стехиометрия (stoichiometry) — новая сима
- 10 реакций: Zn+HCl, H₂+O₂, CH₄+O₂, N₂+H₂ (Габер), Al+CuSO₄, Mg+O₂, CaCO₃→, HCl+NaOH, KMnO₄→, C₂H₅OH+O₂
- Sliders с переключением m/n/V (для газов V=n·22.4 при н.у.)
- Анимация частиц при реакции, подсветка лимитирующего реагента
- Пошаговый расчёт m→n→n_product→m_product с KaTeX
- HUD: лимит, избытки, теоретический выход

Каталог: 33 → 35 сим (5 новых − 3 удалённых merger)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-05-23 13:25:16 +03:00
parent 8f30a8cef6
commit 8b3159b529
13 changed files with 5347 additions and 2232 deletions
+114 -13
View File
@@ -429,6 +429,35 @@
<circle cx="124" cy="121" r="3" fill="#4CC9F0"/><circle cx="159" cy="121" r="3" fill="#9BB8CC"/>
<circle cx="194" cy="121" r="3" fill="#FFD166"/>`);
const P_STOICHIOMETRY = _svg(`
<rect width="270" height="140" fill="#0D0D1A"/>
${_grid()}
<rect x="18" y="28" width="52" height="68" rx="5" fill="none" stroke="rgba(155,184,204,0.5)" stroke-width="1.5"/>
<rect x="20" y="58" width="48" height="36" rx="3" fill="rgba(155,184,204,0.12)"/>
<circle cx="32" cy="74" r="4" fill="rgba(155,184,204,0.7)"/>
<circle cx="45" cy="70" r="4" fill="rgba(155,184,204,0.7)"/>
<circle cx="58" cy="76" r="4" fill="rgba(155,184,204,0.7)"/>
<text x="44" y="46" font-size="9" fill="rgba(155,184,204,0.9)" font-family="Manrope,sans-serif" text-anchor="middle">Zn</text>
<rect x="78" y="28" width="60" height="68" rx="5" fill="none" stroke="rgba(120,210,120,0.5)" stroke-width="1.5"/>
<rect x="80" y="58" width="56" height="36" rx="3" fill="rgba(120,210,120,0.12)"/>
<circle cx="92" cy="72" r="3.5" fill="rgba(120,210,120,0.7)"/>
<circle cx="103" cy="76" r="3.5" fill="rgba(120,210,120,0.7)"/>
<circle cx="114" cy="70" r="3.5" fill="rgba(120,210,120,0.7)"/>
<circle cx="125" cy="74" r="3.5" fill="rgba(120,210,120,0.7)"/>
<text x="108" y="46" font-size="9" fill="rgba(120,210,120,0.9)" font-family="Manrope,sans-serif" text-anchor="middle">2HCl</text>
<text x="150" y="68" font-size="14" fill="rgba(255,255,255,0.5)" font-family="Manrope,sans-serif" text-anchor="middle">&#8594;</text>
<rect x="162" y="28" width="52" height="68" rx="5" fill="none" stroke="rgba(76,201,240,0.5)" stroke-width="1.5"/>
<rect x="164" y="58" width="48" height="36" rx="3" fill="rgba(76,201,240,0.12)"/>
<circle cx="176" cy="74" r="4" fill="rgba(76,201,240,0.7)"/>
<circle cx="189" cy="70" r="4" fill="rgba(76,201,240,0.7)"/>
<circle cx="202" cy="76" r="4" fill="rgba(76,201,240,0.7)"/>
<text x="184" y="46" font-size="9" fill="rgba(76,201,240,0.9)" font-family="Manrope,sans-serif" text-anchor="middle">ZnCl&#8322;</text>
<rect x="222" y="28" width="36" height="68" rx="5" fill="none" stroke="rgba(255,209,102,0.5)" stroke-width="1.5"/>
<circle cx="235" cy="68" r="3" fill="rgba(255,209,102,0.7)"/>
<circle cx="248" cy="74" r="3" fill="rgba(255,209,102,0.7)"/>
<text x="240" y="46" font-size="9" fill="rgba(255,209,102,0.9)" font-family="Manrope,sans-serif" text-anchor="middle">H&#8322;</text>
<text x="135" y="118" font-size="8" fill="rgba(239,71,111,0.7)" font-family="Manrope,sans-serif" text-anchor="middle">&#9679; лимит</text>`);
const P_CRYSTAL = _svg(`
<rect width="270" height="140" fill="#0D0D1A"/>
${[
@@ -520,6 +549,43 @@
stroke="#F15BB5" stroke-width="2.5" fill="none" opacity="0.9"/>
<text x="135" y="132" font-size="8" fill="rgba(255,255,255,0.35)" text-anchor="middle" font-family="Manrope,sans-serif">v = \u03bbf \u00b7 y = A sin(\u03c9t \u2212 kx) \u00b7 \u0441\u0442\u043e\u044f\u0447\u0438\u0435 \u0432\u043e\u043b\u043d\u044b</text>`);
/* Radioactive decay preview */
const P_RADIOACTIVE = _svg(`${_grid()}
<circle cx="55" cy="45" r="5" fill="#9B5DE5" opacity="0.9"/>
<circle cx="90" cy="65" r="5" fill="#9B5DE5" opacity="0.9"/>
<circle cx="38" cy="80" r="5" fill="#9B5DE5" opacity="0.7"/>
<circle cx="75" cy="95" r="5" fill="#EF476F" opacity="0.9"/>
<circle cx="110" cy="50" r="5" fill="#EF476F" opacity="0.85"/>
<circle cx="130" cy="85" r="5" fill="#4CAF50" opacity="0.85"/>
<circle cx="155" cy="55" r="5" fill="#9B5DE5" opacity="0.8"/>
<circle cx="170" cy="90" r="5" fill="#4CAF50" opacity="0.75"/>
<circle cx="200" cy="45" r="5" fill="#4CAF50" opacity="0.9"/>
<circle cx="215" cy="80" r="5" fill="#4CAF50" opacity="0.85"/>
<circle cx="240" cy="60" r="5" fill="#9B5DE5" opacity="0.7"/>
<path d="M 20,115 Q 67,42 135,52 Q 200,62 270,110"
fill="none" stroke="#9B5DE5" stroke-width="2" opacity="0.55" stroke-dasharray="5,3"/>
<path d="M 20,115 Q 100,110 175,100 Q 230,92 270,85"
fill="none" stroke="#4CAF50" stroke-width="1.5" opacity="0.5"/>
<text x="135" y="132" font-size="8" fill="rgba(255,255,255,0.35)" text-anchor="middle" font-family="Manrope,sans-serif">N(t) = N₀·e⁻λt · T½ · цепочки распада</text>`);
/* Heat Engines preview */
const P_HEATENGINE = _svg(`${_grid('rgba(255,255,255,0.04)')}
<line x1="30" y1="10" x2="30" y2="125" stroke="rgba(255,255,255,0.3)" stroke-width="1.5"/>
<line x1="30" y1="125" x2="265" y2="125" stroke="rgba(255,255,255,0.3)" stroke-width="1.5"/>
<path d="M 55,18 Q 100,30 140,75 Q 160,100 190,115" fill="none" stroke="#EF476F" stroke-width="2.2" opacity="0.85"/>
<path d="M 190,115 Q 205,118 215,110 Q 230,90 225,60" fill="none" stroke="#FFD166" stroke-width="2" opacity="0.8"/>
<path d="M 225,60 Q 200,40 160,32 Q 110,22 55,18" fill="none" stroke="#06D6E0" stroke-width="2.2" opacity="0.85"/>
<path d="M 55,18 Q 48,16 44,22 Q 38,38 45,60 Q 50,80 55,18" fill="rgba(155,93,229,0.12)" stroke="#9B5DE5" stroke-width="1" opacity="0.5"/>
<circle cx="55" cy="18" r="4" fill="#EF476F"/>
<circle cx="190" cy="115" r="4" fill="#FFD166"/>
<circle cx="225" cy="60" r="4" fill="#06D6E0"/>
<text x="44" y="14" font-size="9" fill="#EF476F" font-family="Manrope,sans-serif">A</text>
<text x="195" y="126" font-size="9" fill="#FFD166" font-family="Manrope,sans-serif">B</text>
<text x="230" y="58" font-size="9" fill="#06D6E0" font-family="Manrope,sans-serif">C</text>
<text x="255" y="128" font-size="9" fill="rgba(255,255,255,0.5)" font-family="Manrope,sans-serif">V</text>
<text x="18" y="12" font-size="9" fill="rgba(255,255,255,0.5)" font-family="Manrope,sans-serif">P</text>
<text x="135" y="115" font-size="8" fill="rgba(155,93,229,0.7)" font-family="Manrope,sans-serif" text-anchor="middle">η = 1 Tc/Th</text>`);
/* Geometry (planimetry) preview */
const P_GEOMETRY = _svg(`${_grid('rgba(255,255,255,0.04)')}
<circle cx="135" cy="70" r="50" fill="rgba(155,93,229,0.07)" stroke="#9B5DE5" stroke-width="1.5"/>
@@ -535,6 +601,29 @@
<text x="188" y="111" font-size="9" fill="rgba(255,255,255,0.5)" font-family="Manrope,sans-serif">B</text>
<text x="131" y="16" font-size="9" fill="rgba(255,255,255,0.5)" font-family="Manrope,sans-serif">C</text>`);
/* Logic Circuits preview */
const P_LOGIC = _svg(`${_grid('rgba(255,255,255,0.04)')}
<rect x="20" y="38" width="60" height="30" fill="rgba(155,93,229,0.12)" stroke="#9B5DE5" stroke-width="1.5" rx="4"/>
<text x="50" y="57" font-size="9" fill="#9B5DE5" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700">AND</text>
<rect x="130" y="20" width="60" height="30" fill="rgba(6,214,224,0.12)" stroke="#06D6E0" stroke-width="1.5" rx="4"/>
<text x="160" y="39" font-size="9" fill="#06D6E0" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700">XOR</text>
<rect x="130" y="60" width="60" height="30" fill="rgba(241,91,181,0.12)" stroke="#F15BB5" stroke-width="1.5" rx="4"/>
<text x="160" y="79" font-size="9" fill="#F15BB5" text-anchor="middle" font-family="Manrope,sans-serif" font-weight="700">AND</text>
<circle cx="18" cy="45" r="4" fill="#4ADE80"/>
<circle cx="18" cy="58" r="4" fill="rgba(255,255,255,0.35)"/>
<line x1="22" y1="45" x2="80" y2="40" stroke="#4ADE80" stroke-width="1.5"/>
<line x1="22" y1="58" x2="80" y2="55" stroke="rgba(255,255,255,0.3)" stroke-width="1.5"/>
<line x1="80" y1="53" x2="112" y2="35" stroke="#4ADE80" stroke-width="1.5"/>
<line x1="80" y1="53" x2="112" y2="75" stroke="#4ADE80" stroke-width="1.5"/>
<line x1="190" y1="35" x2="230" y2="35" stroke="#4ADE80" stroke-width="1.5"/>
<line x1="190" y1="75" x2="230" y2="75" stroke="rgba(255,255,255,0.3)" stroke-width="1.5"/>
<circle cx="234" cy="35" r="5" fill="#4ADE80" opacity="0.9"/>
<circle cx="234" cy="75" r="5" fill="rgba(255,255,255,0.25)"/>
<text x="240" y="39" font-size="8" fill="#4ADE80" font-family="Manrope,sans-serif" font-weight="700">S</text>
<text x="240" y="79" font-size="8" fill="rgba(255,255,255,0.5)" font-family="Manrope,sans-serif" font-weight="700">C</text>
<text x="135" y="130" font-size="8" fill="rgba(255,255,255,0.35)" text-anchor="middle" font-family="Manrope,sans-serif">S = A⊕B · C = A∧B · Таблица истинности`);
const SIMS = [
/* ── Математика ── */
{ id: 'graph', cat: 'math',
@@ -602,18 +691,10 @@
title: 'Динамика',
desc: 'Законы Ньютона, песочница сил, наклонная плоскость — всё в одном интерактивном модуле.',
preview: P_SANDBOX },
{ id: 'thinlens', cat: 'phys',
title: 'Тонкая линза',
desc: 'Двигай объект относительно линзы — формула линзы, мнимое и действительное изображение.',
{ id: 'opticsbench', cat: 'phys',
title: 'Оптическая скамья',
desc: 'Линза, зеркала и преломление в одной симуляции: формула линзы, зеркальное отражение, закон Снеллиуса, ПВО, дисперсия.',
preview: P_LENS },
{ id: 'refraction', cat: 'phys',
title: 'Преломление света',
desc: 'Луч на границе двух сред: закон Снеллиуса, угол Брюстера, полное внутреннее отражение.',
preview: P_REFRACTION },
{ id: 'mirrors', cat: 'phys',
title: 'Зеркала',
desc: 'Плоское, вогнутое и выпуклое зеркало: построение изображения тремя главными лучами.',
preview: P_MIRROR },
{ id: 'isoprocess', cat: 'phys',
title: 'Изопроцессы',
desc: 'PV-диаграмма для четырёх изопроцессов идеального газа. Расчёт работы, теплоты и внутренней энергии.',
@@ -622,6 +703,18 @@
title: 'Волны и звук',
desc: 'Поперечные и продольные волны, суперпозиция, стоячие волны. Частота, амплитуда, фаза, гармоники.',
preview: P_WAVES },
{ id: 'radioactive', cat: 'phys',
title: 'Радиоактивный распад',
desc: 'Период полураспада, цепочки распадов, активность. Визуализация ядер + кривая N(t). Радиоуглеродное датирование.',
preview: P_RADIOACTIVE },
{ id: 'heatengine', cat: 'phys',
title: 'Тепловые двигатели',
desc: 'Циклы Карно, Отто, Дизеля, Брайтона. PV-диаграмма, поршень, КПД.',
preview: P_HEATENGINE },
{ id: 'logic', cat: 'phys',
title: 'Логические схемы',
desc: 'Конструктор цифровых схем: И/ИЛИ/НЕ/XOR, триггеры, сумматоры. Авто-таблица истинности.',
preview: P_LOGIC },
/* ── Химия / Молекулярная физика ── */
{ id: 'molphys', cat: 'chem',
title: 'Молекулярная физика',
@@ -657,6 +750,10 @@
title: 'Химическая песочница',
desc: 'Смешивай реагенты, наблюдай реакции: осадки, газы, изменение цвета. Свободное экспериментирование.',
preview: P_CHEMSANDBOX },
{ id: 'stoichiometry', cat: 'chem',
title: 'Стехиометрия',
desc: 'Расчёты по уравнениям: масса, моль, объём. Лимитирующий реагент, выход. 10 реакций.',
preview: P_STOICHIOMETRY },
{ id: 'crystal', cat: 'chem',
title: 'Кристаллическая решётка',
desc: 'NaCl, алмаз, металл — интерактивная 3D-решётка, типы связей, вращение структуры.',
@@ -832,8 +929,12 @@
const _SIM_HASH_MAP = {};
SIMS.forEach(function(s) { if (s.id) { _SIM_HASH_MAP[s.id] = s.id; } });
// backward-compat aliases: old URLs redirect to unified emfield sim
_SIM_HASH_MAP['magnetic'] = 'magnetic';
_SIM_HASH_MAP['coulomb'] = 'coulomb';
_SIM_HASH_MAP['magnetic'] = 'magnetic';
_SIM_HASH_MAP['coulomb'] = 'coulomb';
// backward-compat aliases: old optics sims redirect to opticsbench
_SIM_HASH_MAP['thinlens'] = 'opticsbench';
_SIM_HASH_MAP['mirrors'] = 'opticsbench';
_SIM_HASH_MAP['refraction'] = 'opticsbench';
var _routerNavigating = false;