Spaces:
Running
Running
| <html lang="ko"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <title>Anima Emergence — mutual information visualizer</title> | |
| <style> | |
| :root { | |
| --bg: #1a1d24; | |
| --card: #232730; | |
| --border: #2c3140; | |
| --border-bright: #3b4154; | |
| --text: #e6e9f0; | |
| --muted: #8b92a3; | |
| --blue: #7b9aff; | |
| --green: #a8e668; | |
| --emerald: #6dd870; | |
| } | |
| body { | |
| margin: 0; | |
| padding: 16px; | |
| background: var(--bg); | |
| color: var(--text); | |
| font-family: -apple-system, BlinkMacSystemFont, "Pretendard", "Segoe UI", sans-serif; | |
| } | |
| .card { | |
| background: var(--card); | |
| border: 1px solid var(--border); | |
| border-radius: 14px; | |
| padding: 24px; | |
| max-width: 1100px; | |
| margin: 0 auto; | |
| } | |
| .header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 20px; } | |
| .title { font-size: 17px; font-weight: 500; margin: 0; } | |
| .status { color: var(--muted); font-size: 13px; margin-top: 6px; } | |
| .metrics { display: flex; gap: 28px; font-family: "SF Mono", "Menlo", monospace; } | |
| .metric { text-align: center; } | |
| .metric-label { color: var(--muted); font-size: 11px; letter-spacing: 0.5px; } | |
| .metric-value { font-size: 18px; font-weight: 600; margin-top: 2px; } | |
| .canvas-area { background: var(--bg); border-radius: 10px; padding: 20px; margin: 16px 0 20px 0; } | |
| .stream-row { margin-bottom: 14px; } | |
| .stream-label { font-weight: 600; margin-bottom: 6px; font-size: 14px; } | |
| canvas { display: block; } | |
| .stream-canvas { width: 100%; height: 100px; background: rgba(35, 39, 48, 0.6); border-radius: 6px; } | |
| hr.divider { border: none; border-top: 1px solid var(--border); margin: 18px 0; } | |
| .bottom-row { display: flex; align-items: center; justify-content: center; gap: 32px; padding: 8px 0 0 0; } | |
| .scatter-wrap { text-align: center; } | |
| .scatter-canvas { width: 240px; height: 240px; } | |
| .scatter-axis { color: var(--muted); font-size: 12px; margin-top: 4px; } | |
| .badge { | |
| padding: 10px 22px; | |
| border: 2px solid var(--emerald); | |
| color: #c8f7cd; | |
| border-radius: 28px; | |
| font-weight: 700; | |
| font-size: 14px; | |
| letter-spacing: 0.6px; | |
| opacity: 0; | |
| visibility: hidden; | |
| transition: opacity 0.25s, box-shadow 0.25s, visibility 0s linear 0.25s; | |
| } | |
| .badge.active { | |
| opacity: 1; | |
| visibility: visible; | |
| box-shadow: 0 0 22px rgba(109, 216, 112, 0.55), inset 0 0 12px rgba(109, 216, 112, 0.2); | |
| transition: opacity 0.25s, box-shadow 0.25s, visibility 0s linear 0s; | |
| } | |
| .controls { display: flex; align-items: center; gap: 16px; padding-top: 18px; border-top: 1px solid var(--border); margin-top: 16px; } | |
| .controls .label { color: var(--muted); font-size: 13px; min-width: 130px; } | |
| input[type="range"] { flex: 1; -webkit-appearance: none; appearance: none; height: 6px; background: var(--border); border-radius: 3px; outline: none; } | |
| input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; width: 18px; height: 18px; background: var(--blue); border-radius: 50%; cursor: pointer; } | |
| .coup-val { background: var(--bg); border: 1px solid var(--border-bright); border-radius: 8px; padding: 6px 14px; font-family: "SF Mono", monospace; min-width: 48px; text-align: center; font-size: 14px; } | |
| .reset-btn { background: var(--border); color: var(--text); border: 1px solid var(--border-bright); border-radius: 24px; padding: 8px 28px; cursor: pointer; font-size: 13px; transition: background 0.15s; } | |
| .reset-btn:hover { background: var(--border-bright); } | |
| .footnote { color: var(--muted); font-size: 12px; text-align: center; margin-top: 18px; line-height: 1.6; } | |
| .footnote code { background: rgba(255,255,255,0.06); padding: 1px 6px; border-radius: 4px; } | |
| /* tabs */ | |
| .tabs { | |
| max-width: 1100px; | |
| margin: 0 auto 12px auto; | |
| display: flex; | |
| gap: 4px; | |
| border-bottom: 1px solid var(--border); | |
| } | |
| .tab { | |
| background: transparent; | |
| border: none; | |
| color: var(--muted); | |
| padding: 10px 18px; | |
| cursor: pointer; | |
| font-size: 14px; | |
| font-weight: 500; | |
| border-bottom: 2px solid transparent; | |
| margin-bottom: -1px; | |
| transition: color 0.15s, border-color 0.15s; | |
| font-family: inherit; | |
| } | |
| .tab:hover { color: var(--text); } | |
| .tab.active { | |
| color: var(--text); | |
| border-bottom-color: var(--blue); | |
| } | |
| .tab.disabled { | |
| opacity: 0.55; | |
| } | |
| .tab-panel { display: none; } | |
| .tab-panel.active { display: block; } | |
| /* A×G tab */ | |
| .phase-canvas { | |
| width: 100%; | |
| height: 280px; | |
| background: rgba(35, 39, 48, 0.55); | |
| border-radius: 6px; | |
| } | |
| .channel-grid { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 16px; | |
| margin-top: 14px; | |
| } | |
| .channel-row { margin-bottom: 14px; } | |
| .channel-label { font-weight: 600; font-size: 13px; margin-bottom: 6px; } | |
| .channel-canvas { | |
| width: 100%; | |
| height: 90px; | |
| background: rgba(35, 39, 48, 0.55); | |
| border-radius: 6px; | |
| } | |
| .ag-mini-grid { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr 1fr; | |
| gap: 14px; | |
| margin-top: 14px; | |
| } | |
| .ag-mini-canvas { | |
| width: 100%; | |
| height: 70px; | |
| background: rgba(35, 39, 48, 0.55); | |
| border-radius: 6px; | |
| } | |
| /* DTC tab */ | |
| .spin-chain-canvas { | |
| width: 100%; | |
| height: 38px; | |
| background: rgba(35, 39, 48, 0.55); | |
| border-radius: 6px; | |
| margin-bottom: 12px; | |
| } | |
| .dtc-badge { | |
| padding: 10px 22px; | |
| border: 2px solid var(--border-bright); | |
| color: var(--muted); | |
| border-radius: 28px; | |
| font-weight: 700; | |
| font-size: 14px; | |
| letter-spacing: 0.6px; | |
| transition: all 0.25s; | |
| } | |
| .dtc-badge.locked { | |
| border-color: var(--emerald); | |
| color: #c8f7cd; | |
| box-shadow: 0 0 22px rgba(109, 216, 112, 0.55), inset 0 0 12px rgba(109, 216, 112, 0.2); | |
| } | |
| .dtc-badge.building { | |
| border-color: #d8b86d; | |
| color: #f7e3c0; | |
| box-shadow: 0 0 16px rgba(216, 184, 109, 0.35); | |
| } | |
| .dtc-badge.chaos { | |
| border-color: var(--border-bright); | |
| color: var(--muted); | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <nav class="tabs"> | |
| <button class="tab active" data-tab="emergence">✨ Emergence</button> | |
| <button class="tab" data-tab="dtc">🔮 Time Crystal</button> | |
| <button class="tab" data-tab="ag">🧲 A×G Repulsion Field</button> | |
| <button class="tab" data-tab="pipeline">🌊 Engine</button> | |
| </nav> | |
| <div id="tab-emergence" class="tab-panel active"> | |
| <div class="card"> | |
| <div class="header"> | |
| <div> | |
| <h2 class="title">데이터 스트림 결합 및 창발성 시각화</h2> | |
| <div class="status" id="status">버퍼 채우는 중...</div> | |
| </div> | |
| <div class="metrics"> | |
| <div class="metric"><div class="metric-label">H(L)</div><div class="metric-value" id="m-hl">0.00</div></div> | |
| <div class="metric"><div class="metric-label">H(R)</div><div class="metric-value" id="m-hr">0.00</div></div> | |
| <div class="metric"><div class="metric-label">H(L,R)</div><div class="metric-value" id="m-hj">0.00</div></div> | |
| <div class="metric"><div class="metric-label">창발성 (MI)</div><div class="metric-value" id="m-mi" style="color:#c8f7cd">0.000</div></div> | |
| </div> | |
| </div> | |
| <div class="canvas-area"> | |
| <div class="stream-row"> | |
| <div class="stream-label" style="color:var(--blue)">Stream L</div> | |
| <canvas class="stream-canvas" id="cv-l"></canvas> | |
| </div> | |
| <div class="stream-row"> | |
| <div class="stream-label" style="color:var(--green)">Stream R</div> | |
| <canvas class="stream-canvas" id="cv-r"></canvas> | |
| </div> | |
| <hr class="divider"> | |
| <div class="bottom-row"> | |
| <div class="scatter-wrap"> | |
| <canvas class="scatter-canvas" id="cv-s" width="240" height="240"></canvas> | |
| <div class="scatter-axis">Stream L</div> | |
| </div> | |
| <div class="badge" id="badge">EMERGENT ✨</div> | |
| </div> | |
| </div> | |
| <div class="controls"> | |
| <span class="label">결합 강도 (Coupling)</span> | |
| <input type="range" id="coup" min="0" max="100" value="80"> | |
| <span class="coup-val" id="coup-val">0.80</span> | |
| <button class="reset-btn" id="reset">초기화</button> | |
| </div> | |
| <div class="footnote"> | |
| emergence = H(L) + H(R) − H(L,R) — 두 스트림이 독립이면 0, 결합하면 양수로 떠오릅니다.<br> | |
| 공유 사인파 + 개별 노이즈 모델 (no XOR) — 고결합 시 두 스트림이 동일 신호로 수렴해 산점도가 대각선 정렬. | |
| </div> | |
| </div> | |
| </div> | |
| <div id="tab-dtc" class="tab-panel"> | |
| <div class="card"> | |
| <div class="header"> | |
| <div> | |
| <h2 class="title">이산 시간 결정 (Discrete Time Crystal) 시뮬레이션</h2> | |
| <div class="status" id="dtc-status">초기화 중...</div> | |
| </div> | |
| <div class="metrics"> | |
| <div class="metric"><div class="metric-label">mag</div><div class="metric-value" id="dtc-mag">0.00</div></div> | |
| <div class="metric"><div class="metric-label">ac(1)</div><div class="metric-value" id="dtc-ac1">0.00</div></div> | |
| <div class="metric"><div class="metric-label">ac(2)</div><div class="metric-value" id="dtc-ac2">0.00</div></div> | |
| <div class="metric"><div class="metric-label">ac(4)</div><div class="metric-value" id="dtc-ac4">0.00</div></div> | |
| </div> | |
| </div> | |
| <div class="canvas-area"> | |
| <div class="stream-row"> | |
| <div class="stream-label" style="color:#c0c8e0">Spin Chain (N = 64)</div> | |
| <canvas class="spin-chain-canvas" id="dtc-spins"></canvas> | |
| </div> | |
| <div class="stream-row"> | |
| <div class="stream-label" style="color:var(--blue)">Magnetization ⟨s⟩ (zigzag = period-2 lock)</div> | |
| <canvas class="stream-canvas" id="dtc-mag-cv"></canvas> | |
| </div> | |
| <hr class="divider"> | |
| <div class="bottom-row"> | |
| <div class="dtc-badge chaos" id="dtc-badge">…</div> | |
| </div> | |
| </div> | |
| <div class="controls"> | |
| <span class="label">플립 오차 (EPS)</span> | |
| <input type="range" id="dtc-eps" min="0" max="50" value="5"> | |
| <span class="coup-val" id="dtc-eps-val">0.05</span> | |
| <button class="reset-btn" id="dtc-reset">초기화</button> | |
| </div> | |
| <div class="footnote"> | |
| 매 tick: Ising Metropolis sweep (β = 2.5) → π-flip (각 스핀이 1−ε 확률로 뒤집힘).<br> | |
| DTC lock 조건: ac(1) < −0.85 (외부 드라이브와 anti-phase) AND ac(2) > 0.80 (2틱 주기로 원상 복구).<br> | |
| EPS 0.4 근처로 올리면 Ising self-correction 임계 초과 → 결정 융해 → ac → 0 카오스. | |
| </div> | |
| </div> | |
| </div> | |
| <div id="tab-ag" class="tab-panel"> | |
| <div class="card"> | |
| <div class="header"> | |
| <div> | |
| <h2 class="title">A × G — 듀얼 엔진 repulsion field</h2> | |
| <div class="status" id="ag-status">두 엔진을 분리하면 사고가 발생합니다.</div> | |
| </div> | |
| <div class="metrics"> | |
| <div class="metric"><div class="metric-label">tension</div><div class="metric-value" id="ag-tension">0.000</div></div> | |
| <div class="metric"><div class="metric-label">topic#</div><div class="metric-value" id="ag-topic">—</div></div> | |
| <div class="metric"><div class="metric-label">authenticity</div><div class="metric-value" id="ag-auth">0.00</div></div> | |
| <div class="metric"><div class="metric-label">mood</div><div class="metric-value" id="ag-mood" style="color:#c8f7cd">—</div></div> | |
| </div> | |
| </div> | |
| <div class="canvas-area"> | |
| <div class="channel-row"> | |
| <div class="channel-label">Phase space — engine A (●파랑) ↔ engine G (●분홍) — 화살표 A→G = repulsion</div> | |
| <canvas class="phase-canvas" id="ag-phase"></canvas> | |
| </div> | |
| <hr class="divider"> | |
| <div class="channel-grid"> | |
| <div> | |
| <div class="channel-label" style="color:var(--blue)">concept = normalize(A − G) (16-dim direction)</div> | |
| <canvas class="channel-canvas" id="ag-concept"></canvas> | |
| </div> | |
| <div> | |
| <div class="channel-label" style="color:#ff7b9a">meaning = A · G (element-wise interaction)</div> | |
| <canvas class="channel-canvas" id="ag-meaning"></canvas> | |
| </div> | |
| </div> | |
| <div class="ag-mini-grid"> | |
| <div> | |
| <div class="channel-label" style="color:#a8e668">context (8) — circadian + tension trend</div> | |
| <canvas class="ag-mini-canvas" id="ag-context"></canvas> | |
| </div> | |
| <div> | |
| <div class="channel-label" style="color:#d8b86d">authenticity gauge — Dedekind consistency proxy</div> | |
| <canvas class="ag-mini-canvas" id="ag-auth-gauge"></canvas> | |
| </div> | |
| <div> | |
| <div class="channel-label" style="color:#c0c8e0">sender (4) — engine weight signature</div> | |
| <canvas class="ag-mini-canvas" id="ag-sender"></canvas> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="controls"> | |
| <span class="label">separation (A↔G)</span> | |
| <input type="range" id="ag-sep" min="0" max="100" value="60"> | |
| <span class="coup-val" id="ag-sep-val">0.60</span> | |
| <button class="reset-btn" id="ag-reset">초기화</button> | |
| </div> | |
| <div class="controls" style="border:none; padding-top:10px"> | |
| <span class="label">noise (cloud spread)</span> | |
| <input type="range" id="ag-noise" min="0" max="100" value="20"> | |
| <span class="coup-val" id="ag-noise-val">0.20</span> | |
| </div> | |
| <div class="footnote"> | |
| repulsion = A − G; tension = ‖repulsion‖² / N. concept = normalized direction (어떤 axis 가 활성).<br> | |
| meaning = A ⊙ G (두 엔진이 동시에 활성인 차원 — 의미의 중첩).<br> | |
| mood 5-class (`tension_link.py` 룰 그대로): curiosity > 0.5 → surprised / tension > 1.0 → excited / > 0.3 → thoughtful / > 0.05 → calm / else → quiet. | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // tab switcher — exposed activeTab for tick gating | |
| window.activeTab = "emergence"; | |
| document.querySelectorAll(".tab").forEach(btn => { | |
| btn.addEventListener("click", () => { | |
| const target = btn.dataset.tab; | |
| window.activeTab = target; | |
| document.querySelectorAll(".tab").forEach(b => b.classList.toggle("active", b === btn)); | |
| document.querySelectorAll(".tab-panel").forEach(p => { | |
| p.classList.toggle("active", p.id === "tab-" + target); | |
| }); | |
| // resize canvases (hidden canvases have 0×0 client rect) | |
| if (typeof window.applyEmergenceResize === "function") window.applyEmergenceResize(); | |
| if (typeof window.applyDtcResize === "function") window.applyDtcResize(); | |
| if (typeof window.applyAgResize === "function") window.applyAgResize(); | |
| if (typeof window.applyPipelineResize === "function") window.applyPipelineResize(); | |
| }); | |
| }); | |
| </script> | |
| <script> | |
| ; | |
| // ───────────────────────────────────────────────────────────────────────── | |
| // Config | |
| // ───────────────────────────────────────────────────────────────────────── | |
| const TICK_MS = 16; // ~60 fps | |
| const HIST_LEN = 250; // rolling buffer length (~4 s of history at 60 fps) | |
| const BINS = 12; // entropy histogram bins | |
| const VRANGE = 1.5; // value range [-VRANGE, +VRANGE] | |
| const MIN_FOR_METRICS = 50; | |
| const MI_EMERGENT = 0.30; | |
| const MI_PARTIAL = 0.05; | |
| // ───────────────────────────────────────────────────────────────────────── | |
| // State | |
| // ───────────────────────────────────────────────────────────────────────── | |
| const histL = []; | |
| const histR = []; | |
| let t = 0; | |
| // ───────────────────────────────────────────────────────────────────────── | |
| // DOM refs | |
| // ───────────────────────────────────────────────────────────────────────── | |
| const $ = id => document.getElementById(id); | |
| const cvL = $("cv-l"), cvR = $("cv-r"), cvS = $("cv-s"); | |
| const ctxL = cvL.getContext("2d"), ctxR = cvR.getContext("2d"), ctxS = cvS.getContext("2d"); | |
| const slider = $("coup"), coupVal = $("coup-val"), resetBtn = $("reset"); | |
| const elHL = $("m-hl"), elHR = $("m-hr"), elHJ = $("m-hj"), elMI = $("m-mi"); | |
| const elBadge = $("badge"), elStatus = $("status"); | |
| // ───────────────────────────────────────────────────────────────────────── | |
| // Engine — shared sine + individual noise (Gemini-style, line-aligned) | |
| // ───────────────────────────────────────────────────────────────────────── | |
| function generateSample(coupling, tick) { | |
| const common = Math.sin(tick * 0.1); | |
| const noiseL = (Math.random() - 0.5) * 2; | |
| const noiseR = (Math.random() - 0.5) * 2; | |
| return [ | |
| (1 - coupling) * noiseL + coupling * common, | |
| (1 - coupling) * noiseR + coupling * common, | |
| ]; | |
| } | |
| // ───────────────────────────────────────────────────────────────────────── | |
| // Information theory | |
| // ───────────────────────────────────────────────────────────────────────── | |
| function bin(v) { | |
| let i = ((v + VRANGE) / (2 * VRANGE) * BINS) | 0; | |
| if (i < 0) i = 0; | |
| else if (i >= BINS) i = BINS - 1; | |
| return i; | |
| } | |
| function entropy(arr) { | |
| if (arr.length === 0) return 0; | |
| const counts = new Int32Array(BINS); | |
| for (let i = 0; i < arr.length; i++) counts[bin(arr[i])]++; | |
| const inv = 1 / arr.length; | |
| let H = 0; | |
| for (let i = 0; i < BINS; i++) { | |
| if (counts[i] > 0) { | |
| const p = counts[i] * inv; | |
| H -= p * Math.log2(p); | |
| } | |
| } | |
| return H; | |
| } | |
| function jointEntropy(L, R) { | |
| const counts = new Int32Array(BINS * BINS); | |
| for (let i = 0; i < L.length; i++) { | |
| counts[bin(L[i]) * BINS + bin(R[i])]++; | |
| } | |
| const inv = 1 / L.length; | |
| let H = 0; | |
| for (let i = 0; i < counts.length; i++) { | |
| if (counts[i] > 0) { | |
| const p = counts[i] * inv; | |
| H -= p * Math.log2(p); | |
| } | |
| } | |
| return H; | |
| } | |
| // ───────────────────────────────────────────────────────────────────────── | |
| // Rendering | |
| // ───────────────────────────────────────────────────────────────────────── | |
| let dimsL, dimsR; | |
| function resizeCanvas(canvas) { | |
| const dpr = window.devicePixelRatio || 1; | |
| const rect = canvas.getBoundingClientRect(); | |
| canvas.width = rect.width * dpr; | |
| canvas.height = rect.height * dpr; | |
| canvas.getContext("2d").setTransform(dpr, 0, 0, dpr, 0, 0); | |
| return [rect.width, rect.height]; | |
| } | |
| function applyResize() { | |
| dimsL = resizeCanvas(cvL); | |
| dimsR = resizeCanvas(cvR); | |
| } | |
| window.applyEmergenceResize = applyResize; | |
| applyResize(); | |
| window.addEventListener("resize", applyResize); | |
| function valueToY(v, h) { | |
| // map [-VRANGE, +VRANGE] → [h*0.92, h*0.08] | |
| const norm = (v + VRANGE) / (2 * VRANGE); | |
| return h - (norm * h * 0.84 + h * 0.08); | |
| } | |
| function drawStream(ctx, arr, color, w, h) { | |
| ctx.clearRect(0, 0, w, h); | |
| ctx.fillStyle = "rgba(35, 39, 48, 0.55)"; | |
| ctx.fillRect(0, 0, w, h); | |
| if (arr.length < 2) return; | |
| ctx.beginPath(); | |
| ctx.strokeStyle = color; | |
| ctx.lineWidth = 1.6; | |
| ctx.lineJoin = "round"; | |
| const denom = HIST_LEN - 1; | |
| for (let i = 0; i < arr.length; i++) { | |
| const x = (i / denom) * w; | |
| const y = valueToY(arr[i], h); | |
| if (i === 0) ctx.moveTo(x, y); | |
| else ctx.lineTo(x, y); | |
| } | |
| ctx.stroke(); | |
| } | |
| function valueToScatter(v, axisLen, pad) { | |
| const norm = (v + VRANGE) / (2 * VRANGE); | |
| return norm * (axisLen - 2 * pad) + pad; | |
| } | |
| function drawScatter(L, R) { | |
| const w = 240, h = 240, pad = 12; | |
| ctxS.clearRect(0, 0, w, h); | |
| ctxS.strokeStyle = "rgba(139, 146, 163, 0.5)"; | |
| ctxS.lineWidth = 1; | |
| ctxS.setLineDash([4, 4]); | |
| ctxS.strokeRect(pad, pad, w - 2 * pad, h - 2 * pad); | |
| // diagonal guide | |
| ctxS.beginPath(); | |
| ctxS.moveTo(pad, h - pad); | |
| ctxS.lineTo(w - pad, pad); | |
| ctxS.stroke(); | |
| ctxS.setLineDash([]); | |
| for (let i = 0; i < L.length; i++) { | |
| const x = valueToScatter(L[i], w, pad); | |
| const y = h - valueToScatter(R[i], h, pad); | |
| ctxS.fillStyle = i % 2 === 0 ? "rgba(123, 154, 255, 0.55)" : "rgba(168, 230, 104, 0.55)"; | |
| ctxS.beginPath(); | |
| ctxS.arc(x, y, 2.4, 0, Math.PI * 2); | |
| ctxS.fill(); | |
| } | |
| } | |
| // ───────────────────────────────────────────────────────────────────────── | |
| // Tick — runs at ~60 fps via setInterval | |
| // ───────────────────────────────────────────────────────────────────────── | |
| function tick() { | |
| if (window.activeTab !== "emergence") return; | |
| const c = parseInt(slider.value, 10) / 100; | |
| const [l, r] = generateSample(c, t); | |
| histL.push(l); | |
| histR.push(r); | |
| if (histL.length > HIST_LEN) { | |
| histL.shift(); | |
| histR.shift(); | |
| } | |
| if (histL.length >= MIN_FOR_METRICS) { | |
| const hL = entropy(histL); | |
| const hR = entropy(histR); | |
| const hJ = jointEntropy(histL, histR); | |
| const mi = Math.max(0, hL + hR - hJ); | |
| elHL.textContent = hL.toFixed(2); | |
| elHR.textContent = hR.toFixed(2); | |
| elHJ.textContent = hJ.toFixed(2); | |
| elMI.textContent = mi.toFixed(3); | |
| if (mi > MI_EMERGENT) { | |
| elBadge.classList.add("active"); | |
| elStatus.textContent = "강한 결합: 창발성 ✨ — 두 스트림이 통합되어 새로운 정보가 발생합니다."; | |
| } else if (mi > MI_PARTIAL) { | |
| elBadge.classList.remove("active"); | |
| elStatus.textContent = "부분적 결합: 유의미한 상호정보량이 발생합니다."; | |
| } else { | |
| elBadge.classList.remove("active"); | |
| elStatus.textContent = "독립적인 노이즈 단계: 창발성이 낮습니다."; | |
| } | |
| } | |
| drawStream(ctxL, histL, "#7b9aff", dimsL[0], dimsL[1]); | |
| drawStream(ctxR, histR, "#a8e668", dimsR[0], dimsR[1]); | |
| drawScatter(histL, histR); | |
| t++; | |
| } | |
| // ───────────────────────────────────────────────────────────────────────── | |
| // Wiring | |
| // ───────────────────────────────────────────────────────────────────────── | |
| slider.addEventListener("input", () => { | |
| coupVal.textContent = (parseInt(slider.value, 10) / 100).toFixed(2); | |
| }); | |
| resetBtn.addEventListener("click", () => { | |
| histL.length = 0; | |
| histR.length = 0; | |
| t = 0; | |
| slider.value = 80; | |
| coupVal.textContent = "0.80"; | |
| elBadge.classList.remove("active"); | |
| }); | |
| setInterval(tick, TICK_MS); | |
| </script> | |
| <script> | |
| ; | |
| // ───────────────────────────────────────────────────────────────────────── | |
| // Discrete Time Crystal (DTC) — port of dtc_demo.py | |
| // ───────────────────────────────────────────────────────────────────────── | |
| const DTC_TICK_MS = 50; // ~20 fps (Ising sweep is heavier than emergence) | |
| const DTC_N = 64; // spin chain length | |
| const DTC_J = 1.0; // coupling | |
| const DTC_BETA = 2.5; // inverse temperature | |
| const DTC_HIST = 300; // magnetization history length | |
| // state | |
| const dtcSpin = new Int8Array(DTC_N); | |
| for (let i = 0; i < DTC_N; i++) dtcSpin[i] = Math.random() < 0.5 ? 1 : -1; | |
| const dtcMagHist = []; | |
| let dtcEps = 0.05; | |
| // DOM refs | |
| const dtcCvSpins = document.getElementById("dtc-spins"); | |
| const dtcCtxSpins = dtcCvSpins.getContext("2d"); | |
| const dtcCvMag = document.getElementById("dtc-mag-cv"); | |
| const dtcCtxMag = dtcCvMag.getContext("2d"); | |
| const dtcSlider = document.getElementById("dtc-eps"); | |
| const dtcSliderVal = document.getElementById("dtc-eps-val"); | |
| const dtcResetBtn = document.getElementById("dtc-reset"); | |
| const dtcMag = document.getElementById("dtc-mag"); | |
| const dtcAc1 = document.getElementById("dtc-ac1"); | |
| const dtcAc2 = document.getElementById("dtc-ac2"); | |
| const dtcAc4 = document.getElementById("dtc-ac4"); | |
| const dtcStatus = document.getElementById("dtc-status"); | |
| const dtcBadge = document.getElementById("dtc-badge"); | |
| // physics | |
| function dtcIsingSweep() { | |
| for (let i = 0; i < DTC_N; i++) { | |
| const left = dtcSpin[(i - 1 + DTC_N) % DTC_N]; | |
| const right = dtcSpin[(i + 1) % DTC_N]; | |
| const dE = 2 * DTC_J * dtcSpin[i] * (left + right); | |
| if (dE <= 0 || Math.random() < Math.exp(-DTC_BETA * dE)) { | |
| dtcSpin[i] = -dtcSpin[i]; | |
| } | |
| } | |
| } | |
| function dtcPiFlip(eps) { | |
| for (let i = 0; i < DTC_N; i++) { | |
| if (Math.random() > eps) dtcSpin[i] = -dtcSpin[i]; | |
| } | |
| } | |
| function dtcMagnetization() { | |
| let s = 0; | |
| for (let i = 0; i < DTC_N; i++) s += dtcSpin[i]; | |
| return s / DTC_N; | |
| } | |
| function dtcAc(x, lag) { | |
| const n = x.length; | |
| if (n < lag + 20) return 0; | |
| let mean = 0; | |
| for (let i = 0; i < n; i++) mean += x[i]; | |
| mean /= n; | |
| let aDotA = 0, bDotB = 0, aDotB = 0; | |
| for (let i = 0; i < n - lag; i++) { | |
| const a = x[i] - mean, b = x[i + lag] - mean; | |
| aDotA += a * a; bDotB += b * b; aDotB += a * b; | |
| } | |
| const d = Math.sqrt(aDotA * bDotB); | |
| return d > 1e-10 ? aDotB / d : 0; | |
| } | |
| // rendering | |
| let dtcDimsSpins, dtcDimsMag; | |
| function dtcResize() { | |
| dtcDimsSpins = resizeCanvas(dtcCvSpins); | |
| dtcDimsMag = resizeCanvas(dtcCvMag); | |
| } | |
| window.applyDtcResize = dtcResize; | |
| dtcResize(); | |
| window.addEventListener("resize", dtcResize); | |
| function drawSpinChain(w, h) { | |
| dtcCtxSpins.clearRect(0, 0, w, h); | |
| dtcCtxSpins.fillStyle = "rgba(35, 39, 48, 0.55)"; | |
| dtcCtxSpins.fillRect(0, 0, w, h); | |
| const cellW = w / DTC_N; | |
| for (let i = 0; i < DTC_N; i++) { | |
| dtcCtxSpins.fillStyle = dtcSpin[i] > 0 ? "#7b9aff" : "#ff7b9a"; | |
| dtcCtxSpins.fillRect(i * cellW + 1, 4, cellW - 2, h - 8); | |
| } | |
| } | |
| function drawMagSeries(w, h) { | |
| dtcCtxMag.clearRect(0, 0, w, h); | |
| dtcCtxMag.fillStyle = "rgba(35, 39, 48, 0.55)"; | |
| dtcCtxMag.fillRect(0, 0, w, h); | |
| // zero line | |
| dtcCtxMag.strokeStyle = "rgba(139, 146, 163, 0.25)"; | |
| dtcCtxMag.lineWidth = 1; | |
| dtcCtxMag.setLineDash([3, 3]); | |
| dtcCtxMag.beginPath(); | |
| dtcCtxMag.moveTo(0, h / 2); | |
| dtcCtxMag.lineTo(w, h / 2); | |
| dtcCtxMag.stroke(); | |
| dtcCtxMag.setLineDash([]); | |
| if (dtcMagHist.length < 2) return; | |
| dtcCtxMag.beginPath(); | |
| dtcCtxMag.strokeStyle = "#7b9aff"; | |
| dtcCtxMag.lineWidth = 1.6; | |
| dtcCtxMag.lineJoin = "round"; | |
| const denom = DTC_HIST - 1; | |
| for (let i = 0; i < dtcMagHist.length; i++) { | |
| const x = (i / denom) * w; | |
| // map [-1, 1] → [h*0.9, h*0.1] | |
| const y = h - ((dtcMagHist[i] + 1) / 2 * h * 0.8 + h * 0.1); | |
| if (i === 0) dtcCtxMag.moveTo(x, y); | |
| else dtcCtxMag.lineTo(x, y); | |
| } | |
| dtcCtxMag.stroke(); | |
| } | |
| // tick | |
| function dtcTick() { | |
| if (window.activeTab !== "dtc") return; | |
| dtcIsingSweep(); | |
| dtcPiFlip(dtcEps); | |
| const m = dtcMagnetization(); | |
| dtcMagHist.push(m); | |
| if (dtcMagHist.length > DTC_HIST) dtcMagHist.shift(); | |
| const a1 = dtcAc(dtcMagHist, 1); | |
| const a2 = dtcAc(dtcMagHist, 2); | |
| const a4 = dtcAc(dtcMagHist, 4); | |
| dtcMag.textContent = (m >= 0 ? "+" : "") + m.toFixed(3); | |
| dtcAc1.textContent = (a1 >= 0 ? "+" : "") + a1.toFixed(3); | |
| dtcAc2.textContent = (a2 >= 0 ? "+" : "") + a2.toFixed(3); | |
| dtcAc4.textContent = (a4 >= 0 ? "+" : "") + a4.toFixed(3); | |
| if (a1 < -0.85 && a2 > 0.80) { | |
| dtcBadge.textContent = "DTC LOCKED ✨"; | |
| dtcBadge.className = "dtc-badge locked"; | |
| dtcStatus.textContent = "시간 결정 형성 — period-2 lock. 외부 드라이브와 anti-phase 동기화."; | |
| } else if (a1 < -0.5) { | |
| dtcBadge.textContent = "BUILDING..."; | |
| dtcBadge.className = "dtc-badge building"; | |
| dtcStatus.textContent = "주기 배가 형성 중 — 자기상관이 anti-phase 로 정렬되는 중."; | |
| } else { | |
| dtcBadge.textContent = "CHAOS — crystal melted"; | |
| dtcBadge.className = "dtc-badge chaos"; | |
| dtcStatus.textContent = "결정 융해 — Ising self-correction 한계 초과, 무질서 평형으로 떨어짐."; | |
| } | |
| drawSpinChain(dtcDimsSpins[0], dtcDimsSpins[1]); | |
| drawMagSeries(dtcDimsMag[0], dtcDimsMag[1]); | |
| } | |
| // wiring | |
| dtcSlider.addEventListener("input", () => { | |
| dtcEps = parseInt(dtcSlider.value, 10) / 100; | |
| dtcSliderVal.textContent = dtcEps.toFixed(2); | |
| }); | |
| dtcResetBtn.addEventListener("click", () => { | |
| for (let i = 0; i < DTC_N; i++) dtcSpin[i] = Math.random() < 0.5 ? 1 : -1; | |
| dtcMagHist.length = 0; | |
| dtcSlider.value = 5; | |
| dtcEps = 0.05; | |
| dtcSliderVal.textContent = "0.05"; | |
| }); | |
| setInterval(dtcTick, DTC_TICK_MS); | |
| </script> | |
| <div id="tab-pipeline" class="tab-panel"> | |
| <div class="card"> | |
| <div class="header"> | |
| <div> | |
| <h2 class="title">Engine → Emergence — 듀얼 엔진이 emergence 를 생성하는 pipeline</h2> | |
| <div class="status" id="pl-status">슬라이더로 두 엔진의 분리 정도를 조절하세요.</div> | |
| </div> | |
| <div class="metrics"> | |
| <div class="metric"><div class="metric-label">tension</div><div class="metric-value" id="pl-tension">0.00</div></div> | |
| <div class="metric"><div class="metric-label">H(L)</div><div class="metric-value" id="pl-hl">0.00</div></div> | |
| <div class="metric"><div class="metric-label">H(R)</div><div class="metric-value" id="pl-hr">0.00</div></div> | |
| <div class="metric"><div class="metric-label">MI</div><div class="metric-value" id="pl-mi" style="color:#c8f7cd">0.000</div></div> | |
| </div> | |
| </div> | |
| <div class="canvas-area"> | |
| <div class="channel-row"> | |
| <div class="channel-label">Stage 1 — Phase space: A 와 G 가 separation 에 비례해 양극 분리</div> | |
| <canvas class="phase-canvas" id="pl-phase" style="height:180px"></canvas> | |
| </div> | |
| <div class="stream-row"> | |
| <div class="stream-label" style="color:var(--blue)">Stage 2 — Stream L (engine A 의 projection + noise)</div> | |
| <canvas class="stream-canvas" id="pl-stream-l"></canvas> | |
| </div> | |
| <div class="stream-row"> | |
| <div class="stream-label" style="color:var(--green)">Stage 2 — Stream R (engine G 의 projection + noise)</div> | |
| <canvas class="stream-canvas" id="pl-stream-r"></canvas> | |
| </div> | |
| <hr class="divider"> | |
| <div class="bottom-row"> | |
| <div class="scatter-wrap"> | |
| <canvas class="scatter-canvas" id="pl-scatter" width="240" height="240"></canvas> | |
| <div class="scatter-axis">Stream L</div> | |
| </div> | |
| <div class="badge" id="pl-badge">EMERGENT ✨</div> | |
| </div> | |
| </div> | |
| <div class="controls"> | |
| <span class="label">separation (A↔G)</span> | |
| <input type="range" id="pl-sep" min="0" max="100" value="60"> | |
| <span class="coup-val" id="pl-sep-val">0.60</span> | |
| <button class="reset-btn" id="pl-reset">초기화</button> | |
| </div> | |
| <div class="footnote"> | |
| <strong>Stage 1</strong>: A 와 G 가 separation 에 비례해 sin/cos 양극으로 분리 — tension = ‖A − G‖² 가 그 magnitude.<br> | |
| <strong>Stage 2</strong>: 매 tick A 의 projection (+ noise) 을 Stream L 에 push, G 의 projection 을 Stream R 에 push.<br> | |
| <strong>Stage 3</strong>: MI(L, R) = H(L) + H(R) − H(L,R). 분리 ↑ → 두 스트림 distinguishable ↑ → MI ↑ → EMERGENT.<br> | |
| 핵심: <em>두 엔진의 분리 행위가 emergence 를 생성한다</em> — anima 인용구의 직접 시각화. | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| ; | |
| // ───────────────────────────────────────────────────────────────────────── | |
| // 🧲 A × G — dual engine repulsion field | |
| // ───────────────────────────────────────────────────────────────────────── | |
| const AG_DIM = 16; // engine latent dim | |
| const AG_N_DOTS = 56; // points per cloud | |
| const AG_TICK_MS = 33; // ~30 fps | |
| const AG_AUTH_HIST = 30; // window for authenticity volatility proxy | |
| // state | |
| let agA = new Float32Array(AG_DIM); | |
| let agG = new Float32Array(AG_DIM); | |
| let agSep = 0.60; | |
| let agNoise = 0.20; | |
| let agTensionPrev = 0; | |
| const agAuthBuf = []; | |
| let agT = 0; | |
| function agInitVecs() { | |
| for (let i = 0; i < AG_DIM; i++) { | |
| agA[i] = (Math.random() - 0.5) * 0.4; | |
| agG[i] = (Math.random() - 0.5) * 0.4; | |
| } | |
| } | |
| agInitVecs(); | |
| // DOM refs | |
| const agPhase = document.getElementById("ag-phase"); | |
| const agCtxPhase = agPhase.getContext("2d"); | |
| const agConceptCv = document.getElementById("ag-concept"); | |
| const agCtxConcept = agConceptCv.getContext("2d"); | |
| const agMeaningCv = document.getElementById("ag-meaning"); | |
| const agCtxMeaning = agMeaningCv.getContext("2d"); | |
| const agContextCv = document.getElementById("ag-context"); | |
| const agCtxContext = agContextCv.getContext("2d"); | |
| const agAuthCv = document.getElementById("ag-auth-gauge"); | |
| const agCtxAuthCv = agAuthCv.getContext("2d"); | |
| const agSenderCv = document.getElementById("ag-sender"); | |
| const agCtxSender = agSenderCv.getContext("2d"); | |
| const agSepSlider = document.getElementById("ag-sep"); | |
| const agSepVal = document.getElementById("ag-sep-val"); | |
| const agNoiseSlider = document.getElementById("ag-noise"); | |
| const agNoiseVal = document.getElementById("ag-noise-val"); | |
| const agResetBtn = document.getElementById("ag-reset"); | |
| const elAgTension = document.getElementById("ag-tension"); | |
| const elAgTopic = document.getElementById("ag-topic"); | |
| const elAgAuth = document.getElementById("ag-auth"); | |
| const elAgMood = document.getElementById("ag-mood"); | |
| const elAgStatus = document.getElementById("ag-status"); | |
| // resize | |
| let agDimsPhase, agDimsConcept, agDimsMeaning, agDimsContext, agDimsAuth, agDimsSender; | |
| function agResize() { | |
| agDimsPhase = resizeCanvas(agPhase); | |
| agDimsConcept = resizeCanvas(agConceptCv); | |
| agDimsMeaning = resizeCanvas(agMeaningCv); | |
| agDimsContext = resizeCanvas(agContextCv); | |
| agDimsAuth = resizeCanvas(agAuthCv); | |
| agDimsSender = resizeCanvas(agSenderCv); | |
| } | |
| window.applyAgResize = agResize; | |
| agResize(); | |
| window.addEventListener("resize", agResize); | |
| // drawing helpers | |
| function agDrawBars(ctx, vals, w, h, posColor, negColor) { | |
| ctx.clearRect(0, 0, w, h); | |
| ctx.fillStyle = "rgba(35, 39, 48, 0.55)"; | |
| ctx.fillRect(0, 0, w, h); | |
| ctx.strokeStyle = "rgba(139, 146, 163, 0.25)"; | |
| ctx.lineWidth = 1; | |
| ctx.beginPath(); | |
| ctx.moveTo(0, h / 2); ctx.lineTo(w, h / 2); | |
| ctx.stroke(); | |
| let maxAbs = 0.01; | |
| for (let i = 0; i < vals.length; i++) maxAbs = Math.max(maxAbs, Math.abs(vals[i])); | |
| const barW = w / vals.length; | |
| for (let i = 0; i < vals.length; i++) { | |
| const v = vals[i]; | |
| const barH = (Math.abs(v) / maxAbs) * (h * 0.42); | |
| ctx.fillStyle = v >= 0 ? posColor : negColor; | |
| if (v >= 0) ctx.fillRect(i * barW + 1, h / 2 - barH, barW - 2, barH); | |
| else ctx.fillRect(i * barW + 1, h / 2, barW - 2, barH); | |
| } | |
| } | |
| function agDrawPhase(w, h) { | |
| agCtxPhase.clearRect(0, 0, w, h); | |
| agCtxPhase.fillStyle = "rgba(35, 39, 48, 0.55)"; | |
| agCtxPhase.fillRect(0, 0, w, h); | |
| const cx = w / 2, cy = h / 2; | |
| const scale = Math.min(w, h) * 0.32; | |
| // axis lines | |
| agCtxPhase.strokeStyle = "rgba(139, 146, 163, 0.2)"; | |
| agCtxPhase.lineWidth = 1; | |
| agCtxPhase.setLineDash([3, 3]); | |
| agCtxPhase.beginPath(); | |
| agCtxPhase.moveTo(cx, 0); agCtxPhase.lineTo(cx, h); | |
| agCtxPhase.moveTo(0, cy); agCtxPhase.lineTo(w, cy); | |
| agCtxPhase.stroke(); | |
| agCtxPhase.setLineDash([]); | |
| // engine A cloud | |
| for (let i = 0; i < AG_N_DOTS; i++) { | |
| const dx = (Math.random() - 0.5) * agNoise * 0.6; | |
| const dy = (Math.random() - 0.5) * agNoise * 0.6; | |
| const x = cx + (agA[0] + dx) * scale; | |
| const y = cy - (agA[1] + dy) * scale; | |
| agCtxPhase.fillStyle = "rgba(123, 154, 255, 0.45)"; | |
| agCtxPhase.beginPath(); | |
| agCtxPhase.arc(x, y, 2.2, 0, Math.PI * 2); | |
| agCtxPhase.fill(); | |
| } | |
| // engine G cloud | |
| for (let i = 0; i < AG_N_DOTS; i++) { | |
| const dx = (Math.random() - 0.5) * agNoise * 0.6; | |
| const dy = (Math.random() - 0.5) * agNoise * 0.6; | |
| const x = cx + (agG[0] + dx) * scale; | |
| const y = cy - (agG[1] + dy) * scale; | |
| agCtxPhase.fillStyle = "rgba(255, 123, 154, 0.45)"; | |
| agCtxPhase.beginPath(); | |
| agCtxPhase.arc(x, y, 2.2, 0, Math.PI * 2); | |
| agCtxPhase.fill(); | |
| } | |
| // arrow A → G (repulsion vector) | |
| const ax = cx + agA[0] * scale, ay = cy - agA[1] * scale; | |
| const gx = cx + agG[0] * scale, gy = cy - agG[1] * scale; | |
| const arrowMag = Math.hypot(gx - ax, gy - ay); | |
| if (arrowMag > 5) { | |
| agCtxPhase.strokeStyle = "rgba(168, 230, 104, 0.85)"; | |
| agCtxPhase.lineWidth = 2 + Math.min(4, arrowMag / 30); | |
| agCtxPhase.beginPath(); | |
| agCtxPhase.moveTo(ax, ay); agCtxPhase.lineTo(gx, gy); | |
| agCtxPhase.stroke(); | |
| const angle = Math.atan2(gy - ay, gx - ax); | |
| const ahLen = 9; | |
| agCtxPhase.beginPath(); | |
| agCtxPhase.moveTo(gx, gy); | |
| agCtxPhase.lineTo(gx - ahLen * Math.cos(angle - Math.PI / 6), gy - ahLen * Math.sin(angle - Math.PI / 6)); | |
| agCtxPhase.moveTo(gx, gy); | |
| agCtxPhase.lineTo(gx - ahLen * Math.cos(angle + Math.PI / 6), gy - ahLen * Math.sin(angle + Math.PI / 6)); | |
| agCtxPhase.stroke(); | |
| } | |
| // engine center markers + labels | |
| agCtxPhase.fillStyle = "#7b9aff"; | |
| agCtxPhase.beginPath(); agCtxPhase.arc(ax, ay, 4, 0, Math.PI * 2); agCtxPhase.fill(); | |
| agCtxPhase.fillStyle = "#ff7b9a"; | |
| agCtxPhase.beginPath(); agCtxPhase.arc(gx, gy, 4, 0, Math.PI * 2); agCtxPhase.fill(); | |
| agCtxPhase.fillStyle = "#7b9aff"; | |
| agCtxPhase.font = "bold 13px sans-serif"; | |
| agCtxPhase.fillText("A", ax + 8, ay - 6); | |
| agCtxPhase.fillStyle = "#ff7b9a"; | |
| agCtxPhase.fillText("G", gx + 8, gy - 6); | |
| } | |
| function agDrawAuthGauge(auth, w, h) { | |
| agCtxAuthCv.clearRect(0, 0, w, h); | |
| agCtxAuthCv.fillStyle = "rgba(35, 39, 48, 0.55)"; | |
| agCtxAuthCv.fillRect(0, 0, w, h); | |
| // bar | |
| const padX = 12, barH = 10, y = h / 2 - barH / 2; | |
| const barW = w - 2 * padX; | |
| agCtxAuthCv.fillStyle = "rgba(139, 146, 163, 0.25)"; | |
| agCtxAuthCv.fillRect(padX, y, barW, barH); | |
| agCtxAuthCv.fillStyle = "#d8b86d"; | |
| agCtxAuthCv.fillRect(padX, y, barW * Math.max(0, Math.min(1, auth)), barH); | |
| // tick at Dedekind ratio = 1.0 (perfect) | |
| agCtxAuthCv.fillStyle = "rgba(168, 230, 104, 0.7)"; | |
| agCtxAuthCv.fillRect(padX + barW - 1, y - 2, 2, barH + 4); | |
| agCtxAuthCv.fillStyle = "#e6e9f0"; | |
| agCtxAuthCv.font = "11px monospace"; | |
| agCtxAuthCv.fillText(auth.toFixed(3), padX, y - 6); | |
| agCtxAuthCv.fillText("Dedekind 1.0", padX + barW - 60, y + barH + 14); | |
| } | |
| // physics tick | |
| function agTick() { | |
| if (window.activeTab !== "ag") return; | |
| const phase = agT * 0.025; | |
| for (let i = 0; i < AG_DIM; i++) { | |
| agA[i] += (Math.random() - 0.5) * agNoise * 0.06; | |
| agG[i] += (Math.random() - 0.5) * agNoise * 0.06; | |
| if (i === 0) { | |
| const target = agSep * 1.3 * Math.sin(phase); | |
| agA[i] += (target - agA[i]) * 0.06; | |
| agG[i] += (-target - agG[i]) * 0.06; | |
| } else if (i === 1) { | |
| const target = agSep * 1.0 * Math.cos(phase); | |
| agA[i] += (target - agA[i]) * 0.06; | |
| agG[i] += (-target - agG[i]) * 0.06; | |
| } else if (i === 2 || i === 3) { | |
| // mid dims: weakly anti-correlated | |
| const target = agSep * 0.6 * Math.sin(phase * 1.7 + i); | |
| agA[i] += (target - agA[i]) * 0.04; | |
| agG[i] += (-target - agG[i]) * 0.04; | |
| } else { | |
| // higher dims: damped Brownian | |
| agA[i] *= 0.985; | |
| agG[i] *= 0.985; | |
| } | |
| } | |
| // channels | |
| const repulsion = new Float32Array(AG_DIM); | |
| let tSq = 0; | |
| for (let i = 0; i < AG_DIM; i++) { | |
| repulsion[i] = agA[i] - agG[i]; | |
| tSq += repulsion[i] * repulsion[i]; | |
| } | |
| const tension = tSq / AG_DIM; | |
| const norm = Math.sqrt(tSq) || 1; | |
| const concept = new Float32Array(AG_DIM); | |
| for (let i = 0; i < AG_DIM; i++) concept[i] = repulsion[i] / norm; | |
| const meaning = new Float32Array(AG_DIM); | |
| for (let i = 0; i < AG_DIM; i++) meaning[i] = agA[i] * agG[i]; | |
| // topic_hash = argmax |concept| | |
| let topic = 0, maxAbs = 0; | |
| for (let i = 0; i < AG_DIM; i++) { | |
| if (Math.abs(concept[i]) > maxAbs) { maxAbs = Math.abs(concept[i]); topic = i; } | |
| } | |
| // curiosity = |Δtension| | |
| const curiosity = Math.abs(tension - agTensionPrev); | |
| agTensionPrev = tension; | |
| // mood (tension_link.py rule, verbatim thresholds) | |
| let mood, moodColor; | |
| if (curiosity > 0.5) { mood = "surprised"; moodColor = "#ffaa66"; } | |
| else if (tension > 1.0) { mood = "excited"; moodColor = "#ff7b9a"; } | |
| else if (tension > 0.3) { mood = "thoughtful"; moodColor = "#c8f7cd"; } | |
| else if (tension > 0.05) { mood = "calm"; moodColor = "#a8e668"; } | |
| else { mood = "quiet"; moodColor = "#7b9aff"; } | |
| // authenticity = 1 − tension volatility (Dedekind consistency proxy) | |
| agAuthBuf.push(tension); | |
| if (agAuthBuf.length > AG_AUTH_HIST) agAuthBuf.shift(); | |
| let m = 0; | |
| for (const v of agAuthBuf) m += v; | |
| m /= agAuthBuf.length; | |
| let varAcc = 0; | |
| for (const v of agAuthBuf) varAcc += (v - m) * (v - m); | |
| varAcc /= agAuthBuf.length; | |
| const auth = Math.max(0, Math.min(1, 1 - Math.sqrt(varAcc) * 2)); | |
| // context (8) — circadian + tension trend (proxy) | |
| const tNow = Date.now() / 1000; | |
| const context = new Float32Array(8); | |
| context[0] = Math.sin(2 * Math.PI * (tNow % 86400) / 86400); | |
| context[1] = curiosity; | |
| context[2] = tension; | |
| context[3] = m; // mean recent tension | |
| context[4] = curiosity / Math.max(tension, 0.01); | |
| context[5] = Math.min(1, agAuthBuf.length / AG_AUTH_HIST); | |
| context[6] = 0; | |
| context[7] = 0; | |
| // sender (4) — engine signature | |
| const senderHash = (Math.abs(agA[0] * 1000) | 0) / 1000; | |
| const sender = new Float32Array(4); | |
| sender[0] = (Math.abs(agA[0]) * 7 % 1); | |
| sender[1] = (Math.abs(agG[0]) * 11 % 1); | |
| sender[2] = (Math.abs(agA[0] * agG[0]) * 13 % 1); | |
| sender[3] = (Math.abs(tension) * 17 % 1); | |
| // DOM | |
| elAgTension.textContent = tension.toFixed(3); | |
| elAgTopic.textContent = "#" + topic; | |
| elAgAuth.textContent = auth.toFixed(3); | |
| elAgMood.textContent = mood; | |
| elAgMood.style.color = moodColor; | |
| if (tension < 0.05) elAgStatus.textContent = "두 엔진이 거의 동일 — repulsion 0, 사고 정지 (quiet)."; | |
| else if (tension > 1.0) elAgStatus.textContent = "강한 반발 — 격렬한 사고 (" + mood + "). axis #" + topic + " 가 dominant."; | |
| else elAgStatus.textContent = "안정 사고 — A 와 G 가 적절히 분리 (" + mood + "). axis #" + topic + "."; | |
| // render | |
| agDrawPhase(agDimsPhase[0], agDimsPhase[1]); | |
| agDrawBars(agCtxConcept, concept, agDimsConcept[0], agDimsConcept[1], "rgba(123,154,255,0.85)", "rgba(255,123,154,0.85)"); | |
| agDrawBars(agCtxMeaning, meaning, agDimsMeaning[0], agDimsMeaning[1], "rgba(255,123,154,0.85)", "rgba(123,154,255,0.85)"); | |
| agDrawBars(agCtxContext, Array.from(context), agDimsContext[0], agDimsContext[1], "rgba(168,230,104,0.85)", "rgba(168,230,104,0.5)"); | |
| agDrawAuthGauge(auth, agDimsAuth[0], agDimsAuth[1]); | |
| agDrawBars(agCtxSender, Array.from(sender), agDimsSender[0], agDimsSender[1], "rgba(192,200,224,0.85)", "rgba(192,200,224,0.5)"); | |
| agT++; | |
| } | |
| // wiring | |
| agSepSlider.addEventListener("input", () => { | |
| agSep = parseInt(agSepSlider.value, 10) / 100; | |
| agSepVal.textContent = agSep.toFixed(2); | |
| }); | |
| agNoiseSlider.addEventListener("input", () => { | |
| agNoise = parseInt(agNoiseSlider.value, 10) / 100; | |
| agNoiseVal.textContent = agNoise.toFixed(2); | |
| }); | |
| agResetBtn.addEventListener("click", () => { | |
| agInitVecs(); | |
| agAuthBuf.length = 0; | |
| agSepSlider.value = 60; | |
| agNoiseSlider.value = 20; | |
| agSep = 0.60; agNoise = 0.20; | |
| agSepVal.textContent = "0.60"; | |
| agNoiseVal.textContent = "0.20"; | |
| }); | |
| setInterval(agTick, AG_TICK_MS); | |
| </script> | |
| <script> | |
| ; | |
| // ───────────────────────────────────────────────────────────────────────── | |
| // 🌊 Engine → Emergence — pipeline: A/G drives streams → MI | |
| // ───────────────────────────────────────────────────────────────────────── | |
| const PL_DIM = 8; | |
| const PL_HIST = 200; | |
| const PL_TICK_MS = 33; | |
| const PL_BINS = 12; | |
| const PL_VRANGE = 1.6; | |
| const PL_MIN_FOR_METRICS = 50; | |
| const PL_MI_EMERGENT = 0.30; | |
| const PL_MI_PARTIAL = 0.05; | |
| // state | |
| const plA = new Float32Array(PL_DIM); | |
| const plG = new Float32Array(PL_DIM); | |
| function plInit() { | |
| for (let i = 0; i < PL_DIM; i++) { | |
| plA[i] = (Math.random() - 0.5) * 0.3; | |
| plG[i] = (Math.random() - 0.5) * 0.3; | |
| } | |
| } | |
| plInit(); | |
| let plSep = 0.60; | |
| let plT = 0; | |
| const plHistL = []; | |
| const plHistR = []; | |
| // DOM | |
| const plPhase = document.getElementById("pl-phase"); | |
| const plCtxPhase = plPhase.getContext("2d"); | |
| const plStreamL = document.getElementById("pl-stream-l"); | |
| const plCtxL = plStreamL.getContext("2d"); | |
| const plStreamR = document.getElementById("pl-stream-r"); | |
| const plCtxR = plStreamR.getContext("2d"); | |
| const plScatter = document.getElementById("pl-scatter"); | |
| const plCtxS = plScatter.getContext("2d"); | |
| const plSepSlider = document.getElementById("pl-sep"); | |
| const plSepVal = document.getElementById("pl-sep-val"); | |
| const plResetBtn = document.getElementById("pl-reset"); | |
| const elPlTension = document.getElementById("pl-tension"); | |
| const elPlHL = document.getElementById("pl-hl"); | |
| const elPlHR = document.getElementById("pl-hr"); | |
| const elPlMI = document.getElementById("pl-mi"); | |
| const elPlBadge = document.getElementById("pl-badge"); | |
| const elPlStatus = document.getElementById("pl-status"); | |
| // resize | |
| let plDimsPhase, plDimsL, plDimsR; | |
| function plResize() { | |
| plDimsPhase = resizeCanvas(plPhase); | |
| plDimsL = resizeCanvas(plStreamL); | |
| plDimsR = resizeCanvas(plStreamR); | |
| } | |
| window.applyPipelineResize = plResize; | |
| plResize(); | |
| window.addEventListener("resize", plResize); | |
| // helpers | |
| function plBin(v) { | |
| let i = ((v + PL_VRANGE) / (2 * PL_VRANGE) * PL_BINS) | 0; | |
| if (i < 0) i = 0; else if (i >= PL_BINS) i = PL_BINS - 1; | |
| return i; | |
| } | |
| function plEntropy(arr) { | |
| if (!arr.length) return 0; | |
| const counts = new Int32Array(PL_BINS); | |
| for (let i = 0; i < arr.length; i++) counts[plBin(arr[i])]++; | |
| const inv = 1 / arr.length; | |
| let H = 0; | |
| for (let i = 0; i < PL_BINS; i++) if (counts[i] > 0) { const p = counts[i] * inv; H -= p * Math.log2(p); } | |
| return H; | |
| } | |
| function plJointEntropy(L, R) { | |
| const counts = new Int32Array(PL_BINS * PL_BINS); | |
| for (let i = 0; i < L.length; i++) counts[plBin(L[i]) * PL_BINS + plBin(R[i])]++; | |
| const inv = 1 / L.length; | |
| let H = 0; | |
| for (let i = 0; i < counts.length; i++) if (counts[i] > 0) { const p = counts[i] * inv; H -= p * Math.log2(p); } | |
| return H; | |
| } | |
| // drawing | |
| function plValueToY(v, h) { | |
| const norm = (v + PL_VRANGE) / (2 * PL_VRANGE); | |
| return h - (norm * h * 0.84 + h * 0.08); | |
| } | |
| function plDrawStream(ctx, arr, color, w, h) { | |
| ctx.clearRect(0, 0, w, h); | |
| ctx.fillStyle = "rgba(35, 39, 48, 0.55)"; | |
| ctx.fillRect(0, 0, w, h); | |
| if (arr.length < 2) return; | |
| ctx.beginPath(); | |
| ctx.strokeStyle = color; | |
| ctx.lineWidth = 1.4; | |
| ctx.lineJoin = "round"; | |
| const denom = PL_HIST - 1; | |
| for (let i = 0; i < arr.length; i++) { | |
| const x = (i / denom) * w; | |
| const y = plValueToY(arr[i], h); | |
| if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); | |
| } | |
| ctx.stroke(); | |
| } | |
| function plDrawPhase(w, h) { | |
| plCtxPhase.clearRect(0, 0, w, h); | |
| plCtxPhase.fillStyle = "rgba(35, 39, 48, 0.55)"; | |
| plCtxPhase.fillRect(0, 0, w, h); | |
| const cx = w / 2, cy = h / 2; | |
| const scale = Math.min(w, h) * 0.32; | |
| plCtxPhase.strokeStyle = "rgba(139, 146, 163, 0.18)"; | |
| plCtxPhase.setLineDash([3, 3]); | |
| plCtxPhase.beginPath(); | |
| plCtxPhase.moveTo(cx, 0); plCtxPhase.lineTo(cx, h); | |
| plCtxPhase.moveTo(0, cy); plCtxPhase.lineTo(w, cy); | |
| plCtxPhase.stroke(); | |
| plCtxPhase.setLineDash([]); | |
| const ax = cx + plA[0] * scale, ay = cy - plA[1] * scale; | |
| const gx = cx + plG[0] * scale, gy = cy - plG[1] * scale; | |
| // small clouds | |
| for (let i = 0; i < 24; i++) { | |
| const dx = (Math.random() - 0.5) * 0.5; | |
| const dy = (Math.random() - 0.5) * 0.5; | |
| plCtxPhase.fillStyle = "rgba(123, 154, 255, 0.4)"; | |
| plCtxPhase.beginPath(); | |
| plCtxPhase.arc(ax + dx * scale * 0.2, ay + dy * scale * 0.2, 2, 0, Math.PI * 2); | |
| plCtxPhase.fill(); | |
| plCtxPhase.fillStyle = "rgba(255, 123, 154, 0.4)"; | |
| plCtxPhase.beginPath(); | |
| plCtxPhase.arc(gx + dx * scale * 0.2, gy + dy * scale * 0.2, 2, 0, Math.PI * 2); | |
| plCtxPhase.fill(); | |
| } | |
| const arrowMag = Math.hypot(gx - ax, gy - ay); | |
| if (arrowMag > 5) { | |
| plCtxPhase.strokeStyle = "rgba(168, 230, 104, 0.85)"; | |
| plCtxPhase.lineWidth = 2 + Math.min(4, arrowMag / 30); | |
| plCtxPhase.beginPath(); | |
| plCtxPhase.moveTo(ax, ay); plCtxPhase.lineTo(gx, gy); | |
| plCtxPhase.stroke(); | |
| const angle = Math.atan2(gy - ay, gx - ax); | |
| plCtxPhase.beginPath(); | |
| plCtxPhase.moveTo(gx, gy); | |
| plCtxPhase.lineTo(gx - 9 * Math.cos(angle - Math.PI / 6), gy - 9 * Math.sin(angle - Math.PI / 6)); | |
| plCtxPhase.moveTo(gx, gy); | |
| plCtxPhase.lineTo(gx - 9 * Math.cos(angle + Math.PI / 6), gy - 9 * Math.sin(angle + Math.PI / 6)); | |
| plCtxPhase.stroke(); | |
| } | |
| plCtxPhase.fillStyle = "#7b9aff"; | |
| plCtxPhase.beginPath(); plCtxPhase.arc(ax, ay, 4, 0, Math.PI * 2); plCtxPhase.fill(); | |
| plCtxPhase.font = "bold 13px sans-serif"; plCtxPhase.fillText("A", ax + 8, ay - 6); | |
| plCtxPhase.fillStyle = "#ff7b9a"; | |
| plCtxPhase.beginPath(); plCtxPhase.arc(gx, gy, 4, 0, Math.PI * 2); plCtxPhase.fill(); | |
| plCtxPhase.fillText("G", gx + 8, gy - 6); | |
| } | |
| function plDrawScatter() { | |
| const w = 240, h = 240, pad = 12; | |
| plCtxS.clearRect(0, 0, w, h); | |
| plCtxS.strokeStyle = "rgba(139, 146, 163, 0.5)"; | |
| plCtxS.lineWidth = 1; | |
| plCtxS.setLineDash([4, 4]); | |
| plCtxS.strokeRect(pad, pad, w - 2 * pad, h - 2 * pad); | |
| plCtxS.beginPath(); | |
| plCtxS.moveTo(pad, h - pad); plCtxS.lineTo(w - pad, pad); | |
| plCtxS.stroke(); | |
| plCtxS.setLineDash([]); | |
| for (let i = 0; i < plHistL.length; i++) { | |
| const xn = (plHistL[i] + PL_VRANGE) / (2 * PL_VRANGE); | |
| const yn = (plHistR[i] + PL_VRANGE) / (2 * PL_VRANGE); | |
| const x = xn * (w - 2 * pad) + pad; | |
| const y = h - (yn * (h - 2 * pad) + pad); | |
| plCtxS.fillStyle = i % 2 === 0 ? "rgba(123, 154, 255, 0.55)" : "rgba(168, 230, 104, 0.55)"; | |
| plCtxS.beginPath(); | |
| plCtxS.arc(x, y, 2.4, 0, Math.PI * 2); | |
| plCtxS.fill(); | |
| } | |
| } | |
| // tick | |
| function plTick() { | |
| if (window.activeTab !== "pipeline") return; | |
| // engine drift | |
| const phase = plT * 0.025; | |
| for (let i = 0; i < PL_DIM; i++) { | |
| plA[i] += (Math.random() - 0.5) * 0.04; | |
| plG[i] += (Math.random() - 0.5) * 0.04; | |
| if (i === 0) { | |
| const t = plSep * 1.3 * Math.sin(phase); | |
| plA[i] += (t - plA[i]) * 0.06; | |
| plG[i] += (-t - plG[i]) * 0.06; | |
| } else if (i === 1) { | |
| const t = plSep * 1.0 * Math.cos(phase); | |
| plA[i] += (t - plA[i]) * 0.06; | |
| plG[i] += (-t - plG[i]) * 0.06; | |
| } else { | |
| plA[i] *= 0.985; plG[i] *= 0.985; | |
| } | |
| } | |
| // tension | |
| let tSq = 0; | |
| for (let i = 0; i < PL_DIM; i++) { | |
| const r = plA[i] - plG[i]; | |
| tSq += r * r; | |
| } | |
| const tension = tSq / PL_DIM; | |
| // sample one observation per tick from each engine's distribution | |
| const sampleL = plA[0] + (Math.random() - 0.5) * 0.4; | |
| const sampleR = plG[0] + (Math.random() - 0.5) * 0.4; | |
| plHistL.push(sampleL); | |
| plHistR.push(sampleR); | |
| if (plHistL.length > PL_HIST) { | |
| plHistL.shift(); plHistR.shift(); | |
| } | |
| // emergence | |
| let hL = 0, hR = 0, hJ = 0, mi = 0; | |
| if (plHistL.length >= PL_MIN_FOR_METRICS) { | |
| hL = plEntropy(plHistL); | |
| hR = plEntropy(plHistR); | |
| hJ = plJointEntropy(plHistL, plHistR); | |
| mi = Math.max(0, hL + hR - hJ); | |
| } | |
| elPlTension.textContent = tension.toFixed(2); | |
| elPlHL.textContent = hL.toFixed(2); | |
| elPlHR.textContent = hR.toFixed(2); | |
| elPlMI.textContent = mi.toFixed(3); | |
| if (mi > PL_MI_EMERGENT) { | |
| elPlBadge.classList.add("active"); | |
| elPlStatus.textContent = "✨ Emergence — A 와 G 의 분리가 두 스트림에 공통 정보를 생성합니다."; | |
| } else if (mi > PL_MI_PARTIAL) { | |
| elPlBadge.classList.remove("active"); | |
| elPlStatus.textContent = "부분적 emergence — 분리가 약함, MI 가 임계 아래."; | |
| } else { | |
| elPlBadge.classList.remove("active"); | |
| elPlStatus.textContent = "두 엔진이 거의 동일 — emergence 0, 사고 정지."; | |
| } | |
| plDrawPhase(plDimsPhase[0], plDimsPhase[1]); | |
| plDrawStream(plCtxL, plHistL, "#7b9aff", plDimsL[0], plDimsL[1]); | |
| plDrawStream(plCtxR, plHistR, "#a8e668", plDimsR[0], plDimsR[1]); | |
| plDrawScatter(); | |
| plT++; | |
| } | |
| // wiring | |
| plSepSlider.addEventListener("input", () => { | |
| plSep = parseInt(plSepSlider.value, 10) / 100; | |
| plSepVal.textContent = plSep.toFixed(2); | |
| }); | |
| plResetBtn.addEventListener("click", () => { | |
| plInit(); | |
| plHistL.length = 0; plHistR.length = 0; | |
| plSepSlider.value = 60; | |
| plSep = 0.60; | |
| plSepVal.textContent = "0.60"; | |
| elPlBadge.classList.remove("active"); | |
| }); | |
| setInterval(plTick, PL_TICK_MS); | |
| </script> | |
| </body> | |
| </html> | |