// Common scope notebook の表側。 // // ウィンドウは二形ある。 // full notebook と scope を並べる // solo ?pane=<名前> で scope 一枚だけ。別のディスプレイへ出すため // // どちらのウィンドウも自分の kernel を持つ。同期するのは平文と初期値だけで、 // 計算はウィンドウごとに回す。lane の作業領域は交わらない(20 式 1.2)ので、 // 同じ平文を何枚実行しても state が混ざらない。 const CDN = 'https://cdn.jsdelivr.net/pyodide/v314.0.7/full/'; const MDCDN = 'https://cdn.jsdelivr.net/npm/common-scope-web@0/'; // vendor/web が無いときの逃げ道 const PANES = { chamber: ['層と机', 'S(k,q,r) = Atom r = 層境界音 ka の数、q = 机の位置(16 §2)\nfr(Λ_{r+1}) = fr(Λ_r) の未消費部 ‖ 層 r の出力(19 §4.2)', s => drawChamber(s)], dag: ['箱の DAG', 'commonc の OPK / OPA / OPB / OPD をそのまま(19 §3.2)\nδ(Cst(x,d)) = d, δ(u⊕v) = max, δ(u⋆v) = δu + δv(19 式 4.2)', s => drawDag(s)], tsui: ['ツイ数平面', null, s => drawTsui(s)], lane: ['lane64', 'Σ_lane64 = ℤ^(W×L) × ℤ^(W×S) × ℕ × {0,1}^W × ℰ* × Σ_out*(20 式 1.1)\nblock の入口 ℬ = {0} ∪ {i+d_i} ∪ {i+1}(式 7.4)、層 Λ_r = {v | h(v) = r}(式 7.2)', s => drawLane(s)], joint: ['接合秩', 'F[α][β] = f(α,β) ρ = rank_𝔽₂ F\nf = ⊕_{r<ρ} p_r(x_A) q_r(x_B) (Common-Lang/1 定理 4.1)', () => drawJoint(lastJoint)], text: ['音節と箱', null, s => drawText(s)], }; const solo = new URLSearchParams(location.search).get('pane'); let py = null, nb = null, nbFile = null, dirty = false; let lastScope = null, lastJoint = null, cur = -1; // ===== 起動 ===== function log(msg, cls) { const li = document.createElement('li'); li.textContent = msg; if (cls) li.className = cls; document.getElementById('bootlog').appendChild(li); } function loadScript(src) { return new Promise((ok, no) => { const s = document.createElement('script'); s.src = src; s.onload = ok; s.onerror = () => no(new Error(src)); document.head.appendChild(s); }); } async function boot() { const rt = await (await fetch('/api/runtime')).json(); if (rt.missing.length) { log('配信できないファイル: ' + rt.missing.join(', '), 'bad'); document.getElementById('bootmsg').textContent = 'setup.py を実行して runtime/ を揃えてください。'; return; } try { await startRender(rt.web || MDCDN); log('Markdown と KaTeX を読み込みました', 'ok'); } catch (e) { log('Markdown の library を読めません。説明は素のまま出ます', 'bad'); } const index = rt.pyodide || CDN; log(rt.pyodide ? 'Pyodide はこのフォルダのコピーを使います' : 'Pyodide を CDN から取ります', 'ok'); await loadScript(index + 'pyodide.js'); const pyodide = await loadPyodide({ indexURL: index }); log('CPython ' + pyodide.version + ' がブラウザの中で動きました', 'ok'); pyodide.FS.mkdir('/common'); for (const f of rt.files) { const buf = new Uint8Array(await (await fetch('/runtime/' + f)).arrayBuffer()); pyodide.FS.writeFile('/common/' + f, buf); } log('lane64 の実行台と commonc の image を読み込みました(' + rt.files.length + ' 個)', 'ok'); pyodide.runPython(await (await fetch('boot.py')).text()); pyodide.runPython('ready()'); py = { cell: s => JSON.parse(pyodide.globals.get('cell')(s)), joint: s => JSON.parse(pyodide.globals.get('joint')(s)), }; const k = document.getElementById('kernel'); k.textContent = 'lane64 稼働中'; k.className = 'tag ok'; document.getElementById('boot').classList.add('done'); } // ===== ウィンドウ間の同期 ===== function publish(patch) { fetch('/state', { method: 'POST', body: JSON.stringify(patch) }).catch(() => { }); } // ===== 本文 ===== // // 説明セルは Markdown で書く。数式は $…$ と $$…$$。画像と動画は assets/ へ置いて // ![](assets/x.png) や