Spaces:
Running
add: 🧲 A×G tab — dual engine repulsion field
Browse filesThird tab visualizes the PureField A and G engines and the 5-channel
meta-fingerprint they produce, mirroring the math in
docs/modules/tension_link.md.
Components:
- 2D phase space — two clouds (A blue, G pink) with mean-to-mean arrow
visualizing repulsion = A − G; arrow thickness scales with ‖repulsion‖
- concept bars (16) = normalize(A − G) — direction in latent space
- meaning bars (16) = A ⊙ G — element-wise interaction (overlap dims)
- context (8) — circadian + tension trend
- authenticity gauge — 1 − √var(tension) over 30-tick window
(Dedekind chain consistency proxy)
- sender (4) — engine weight signature
- live metrics: tension / topic# (= argmax|concept|) / mood (5-class:
surprised|excited|thoughtful|calm|quiet from tension_link.py thresholds)
Controls:
- separation slider (0–1) — drives A/G poles in dims 0–1 with sin/cos
phase, plus weaker anti-correlation in dims 2–3
- noise slider — cloud spread + Brownian per-dim drift
Both ticks (emergence, dtc, ag) gate by window.activeTab so off-tab viz
pays zero compute. Resize handlers wired through applyAgResize.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- index.html +419 -0
|
@@ -107,6 +107,40 @@
|
|
| 107 |
.tab-panel { display: none; }
|
| 108 |
.tab-panel.active { display: block; }
|
| 109 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
/* DTC tab */
|
| 111 |
.spin-chain-canvas {
|
| 112 |
width: 100%;
|
|
@@ -145,6 +179,7 @@
|
|
| 145 |
<nav class="tabs">
|
| 146 |
<button class="tab active" data-tab="emergence">✨ Emergence</button>
|
| 147 |
<button class="tab" data-tab="dtc">🔮 Time Crystal</button>
|
|
|
|
| 148 |
</nav>
|
| 149 |
|
| 150 |
<div id="tab-emergence" class="tab-panel active">
|
|
@@ -240,6 +275,73 @@
|
|
| 240 |
</div>
|
| 241 |
</div>
|
| 242 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
<script>
|
| 244 |
// tab switcher — exposed activeTab for tick gating
|
| 245 |
window.activeTab = "emergence";
|
|
@@ -254,6 +356,7 @@ document.querySelectorAll(".tab").forEach(btn => {
|
|
| 254 |
// resize canvases (hidden canvases have 0×0 client rect)
|
| 255 |
if (typeof window.applyEmergenceResize === "function") window.applyEmergenceResize();
|
| 256 |
if (typeof window.applyDtcResize === "function") window.applyDtcResize();
|
|
|
|
| 257 |
});
|
| 258 |
});
|
| 259 |
</script>
|
|
@@ -648,5 +751,321 @@ dtcResetBtn.addEventListener("click", () => {
|
|
| 648 |
|
| 649 |
setInterval(dtcTick, DTC_TICK_MS);
|
| 650 |
</script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 651 |
</body>
|
| 652 |
</html>
|
|
|
|
| 107 |
.tab-panel { display: none; }
|
| 108 |
.tab-panel.active { display: block; }
|
| 109 |
|
| 110 |
+
/* A×G tab */
|
| 111 |
+
.phase-canvas {
|
| 112 |
+
width: 100%;
|
| 113 |
+
height: 280px;
|
| 114 |
+
background: rgba(35, 39, 48, 0.55);
|
| 115 |
+
border-radius: 6px;
|
| 116 |
+
}
|
| 117 |
+
.channel-grid {
|
| 118 |
+
display: grid;
|
| 119 |
+
grid-template-columns: 1fr 1fr;
|
| 120 |
+
gap: 16px;
|
| 121 |
+
margin-top: 14px;
|
| 122 |
+
}
|
| 123 |
+
.channel-row { margin-bottom: 14px; }
|
| 124 |
+
.channel-label { font-weight: 600; font-size: 13px; margin-bottom: 6px; }
|
| 125 |
+
.channel-canvas {
|
| 126 |
+
width: 100%;
|
| 127 |
+
height: 90px;
|
| 128 |
+
background: rgba(35, 39, 48, 0.55);
|
| 129 |
+
border-radius: 6px;
|
| 130 |
+
}
|
| 131 |
+
.ag-mini-grid {
|
| 132 |
+
display: grid;
|
| 133 |
+
grid-template-columns: 1fr 1fr 1fr;
|
| 134 |
+
gap: 14px;
|
| 135 |
+
margin-top: 14px;
|
| 136 |
+
}
|
| 137 |
+
.ag-mini-canvas {
|
| 138 |
+
width: 100%;
|
| 139 |
+
height: 70px;
|
| 140 |
+
background: rgba(35, 39, 48, 0.55);
|
| 141 |
+
border-radius: 6px;
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
/* DTC tab */
|
| 145 |
.spin-chain-canvas {
|
| 146 |
width: 100%;
|
|
|
|
| 179 |
<nav class="tabs">
|
| 180 |
<button class="tab active" data-tab="emergence">✨ Emergence</button>
|
| 181 |
<button class="tab" data-tab="dtc">🔮 Time Crystal</button>
|
| 182 |
+
<button class="tab" data-tab="ag">🧲 A×G</button>
|
| 183 |
</nav>
|
| 184 |
|
| 185 |
<div id="tab-emergence" class="tab-panel active">
|
|
|
|
| 275 |
</div>
|
| 276 |
</div>
|
| 277 |
|
| 278 |
+
<div id="tab-ag" class="tab-panel">
|
| 279 |
+
<div class="card">
|
| 280 |
+
<div class="header">
|
| 281 |
+
<div>
|
| 282 |
+
<h2 class="title">A × G — 듀얼 엔진 repulsion field</h2>
|
| 283 |
+
<div class="status" id="ag-status">두 엔진을 분리하면 사고가 발생합니다.</div>
|
| 284 |
+
</div>
|
| 285 |
+
<div class="metrics">
|
| 286 |
+
<div class="metric"><div class="metric-label">tension</div><div class="metric-value" id="ag-tension">0.000</div></div>
|
| 287 |
+
<div class="metric"><div class="metric-label">topic#</div><div class="metric-value" id="ag-topic">—</div></div>
|
| 288 |
+
<div class="metric"><div class="metric-label">authenticity</div><div class="metric-value" id="ag-auth">0.00</div></div>
|
| 289 |
+
<div class="metric"><div class="metric-label">mood</div><div class="metric-value" id="ag-mood" style="color:#c8f7cd">—</div></div>
|
| 290 |
+
</div>
|
| 291 |
+
</div>
|
| 292 |
+
|
| 293 |
+
<div class="canvas-area">
|
| 294 |
+
<div class="channel-row">
|
| 295 |
+
<div class="channel-label">Phase space — engine A (●파랑) ↔ engine G (●분홍) — 화살표 A→G = repulsion</div>
|
| 296 |
+
<canvas class="phase-canvas" id="ag-phase"></canvas>
|
| 297 |
+
</div>
|
| 298 |
+
<hr class="divider">
|
| 299 |
+
<div class="channel-grid">
|
| 300 |
+
<div>
|
| 301 |
+
<div class="channel-label" style="color:var(--blue)">concept = normalize(A − G) (16-dim direction)</div>
|
| 302 |
+
<canvas class="channel-canvas" id="ag-concept"></canvas>
|
| 303 |
+
</div>
|
| 304 |
+
<div>
|
| 305 |
+
<div class="channel-label" style="color:#ff7b9a">meaning = A · G (element-wise interaction)</div>
|
| 306 |
+
<canvas class="channel-canvas" id="ag-meaning"></canvas>
|
| 307 |
+
</div>
|
| 308 |
+
</div>
|
| 309 |
+
<div class="ag-mini-grid">
|
| 310 |
+
<div>
|
| 311 |
+
<div class="channel-label" style="color:#a8e668">context (8) — circadian + tension trend</div>
|
| 312 |
+
<canvas class="ag-mini-canvas" id="ag-context"></canvas>
|
| 313 |
+
</div>
|
| 314 |
+
<div>
|
| 315 |
+
<div class="channel-label" style="color:#d8b86d">authenticity gauge — Dedekind consistency proxy</div>
|
| 316 |
+
<canvas class="ag-mini-canvas" id="ag-auth-gauge"></canvas>
|
| 317 |
+
</div>
|
| 318 |
+
<div>
|
| 319 |
+
<div class="channel-label" style="color:#c0c8e0">sender (4) — engine weight signature</div>
|
| 320 |
+
<canvas class="ag-mini-canvas" id="ag-sender"></canvas>
|
| 321 |
+
</div>
|
| 322 |
+
</div>
|
| 323 |
+
</div>
|
| 324 |
+
|
| 325 |
+
<div class="controls">
|
| 326 |
+
<span class="label">separation (A↔G)</span>
|
| 327 |
+
<input type="range" id="ag-sep" min="0" max="100" value="60">
|
| 328 |
+
<span class="coup-val" id="ag-sep-val">0.60</span>
|
| 329 |
+
<button class="reset-btn" id="ag-reset">초기화</button>
|
| 330 |
+
</div>
|
| 331 |
+
<div class="controls" style="border:none; padding-top:10px">
|
| 332 |
+
<span class="label">noise (cloud spread)</span>
|
| 333 |
+
<input type="range" id="ag-noise" min="0" max="100" value="20">
|
| 334 |
+
<span class="coup-val" id="ag-noise-val">0.20</span>
|
| 335 |
+
</div>
|
| 336 |
+
|
| 337 |
+
<div class="footnote">
|
| 338 |
+
repulsion = A − G; tension = ‖repulsion‖² / N. concept = normalized direction (어떤 axis 가 활성).<br>
|
| 339 |
+
meaning = A ⊙ G (두 엔진이 동시에 활성인 차원 — 의미의 중첩).<br>
|
| 340 |
+
mood 5-class (`tension_link.py` 룰 그대로): curiosity > 0.5 → surprised / tension > 1.0 → excited / > 0.3 → thoughtful / > 0.05 → calm / else → quiet.
|
| 341 |
+
</div>
|
| 342 |
+
</div>
|
| 343 |
+
</div>
|
| 344 |
+
|
| 345 |
<script>
|
| 346 |
// tab switcher — exposed activeTab for tick gating
|
| 347 |
window.activeTab = "emergence";
|
|
|
|
| 356 |
// resize canvases (hidden canvases have 0×0 client rect)
|
| 357 |
if (typeof window.applyEmergenceResize === "function") window.applyEmergenceResize();
|
| 358 |
if (typeof window.applyDtcResize === "function") window.applyDtcResize();
|
| 359 |
+
if (typeof window.applyAgResize === "function") window.applyAgResize();
|
| 360 |
});
|
| 361 |
});
|
| 362 |
</script>
|
|
|
|
| 751 |
|
| 752 |
setInterval(dtcTick, DTC_TICK_MS);
|
| 753 |
</script>
|
| 754 |
+
|
| 755 |
+
<script>
|
| 756 |
+
"use strict";
|
| 757 |
+
// ─────────────────────────────────────────────────────────────────────────
|
| 758 |
+
// 🧲 A × G — dual engine repulsion field
|
| 759 |
+
// ─────────────────────────────────────────────────────────────────────────
|
| 760 |
+
const AG_DIM = 16; // engine latent dim
|
| 761 |
+
const AG_N_DOTS = 56; // points per cloud
|
| 762 |
+
const AG_TICK_MS = 33; // ~30 fps
|
| 763 |
+
const AG_AUTH_HIST = 30; // window for authenticity volatility proxy
|
| 764 |
+
|
| 765 |
+
// state
|
| 766 |
+
let agA = new Float32Array(AG_DIM);
|
| 767 |
+
let agG = new Float32Array(AG_DIM);
|
| 768 |
+
let agSep = 0.60;
|
| 769 |
+
let agNoise = 0.20;
|
| 770 |
+
let agTensionPrev = 0;
|
| 771 |
+
const agAuthBuf = [];
|
| 772 |
+
let agT = 0;
|
| 773 |
+
|
| 774 |
+
function agInitVecs() {
|
| 775 |
+
for (let i = 0; i < AG_DIM; i++) {
|
| 776 |
+
agA[i] = (Math.random() - 0.5) * 0.4;
|
| 777 |
+
agG[i] = (Math.random() - 0.5) * 0.4;
|
| 778 |
+
}
|
| 779 |
+
}
|
| 780 |
+
agInitVecs();
|
| 781 |
+
|
| 782 |
+
// DOM refs
|
| 783 |
+
const agPhase = document.getElementById("ag-phase");
|
| 784 |
+
const agCtxPhase = agPhase.getContext("2d");
|
| 785 |
+
const agConceptCv = document.getElementById("ag-concept");
|
| 786 |
+
const agCtxConcept = agConceptCv.getContext("2d");
|
| 787 |
+
const agMeaningCv = document.getElementById("ag-meaning");
|
| 788 |
+
const agCtxMeaning = agMeaningCv.getContext("2d");
|
| 789 |
+
const agContextCv = document.getElementById("ag-context");
|
| 790 |
+
const agCtxContext = agContextCv.getContext("2d");
|
| 791 |
+
const agAuthCv = document.getElementById("ag-auth-gauge");
|
| 792 |
+
const agCtxAuthCv = agAuthCv.getContext("2d");
|
| 793 |
+
const agSenderCv = document.getElementById("ag-sender");
|
| 794 |
+
const agCtxSender = agSenderCv.getContext("2d");
|
| 795 |
+
|
| 796 |
+
const agSepSlider = document.getElementById("ag-sep");
|
| 797 |
+
const agSepVal = document.getElementById("ag-sep-val");
|
| 798 |
+
const agNoiseSlider = document.getElementById("ag-noise");
|
| 799 |
+
const agNoiseVal = document.getElementById("ag-noise-val");
|
| 800 |
+
const agResetBtn = document.getElementById("ag-reset");
|
| 801 |
+
const elAgTension = document.getElementById("ag-tension");
|
| 802 |
+
const elAgTopic = document.getElementById("ag-topic");
|
| 803 |
+
const elAgAuth = document.getElementById("ag-auth");
|
| 804 |
+
const elAgMood = document.getElementById("ag-mood");
|
| 805 |
+
const elAgStatus = document.getElementById("ag-status");
|
| 806 |
+
|
| 807 |
+
// resize
|
| 808 |
+
let agDimsPhase, agDimsConcept, agDimsMeaning, agDimsContext, agDimsAuth, agDimsSender;
|
| 809 |
+
function agResize() {
|
| 810 |
+
agDimsPhase = resizeCanvas(agPhase);
|
| 811 |
+
agDimsConcept = resizeCanvas(agConceptCv);
|
| 812 |
+
agDimsMeaning = resizeCanvas(agMeaningCv);
|
| 813 |
+
agDimsContext = resizeCanvas(agContextCv);
|
| 814 |
+
agDimsAuth = resizeCanvas(agAuthCv);
|
| 815 |
+
agDimsSender = resizeCanvas(agSenderCv);
|
| 816 |
+
}
|
| 817 |
+
window.applyAgResize = agResize;
|
| 818 |
+
agResize();
|
| 819 |
+
window.addEventListener("resize", agResize);
|
| 820 |
+
|
| 821 |
+
// drawing helpers
|
| 822 |
+
function agDrawBars(ctx, vals, w, h, posColor, negColor) {
|
| 823 |
+
ctx.clearRect(0, 0, w, h);
|
| 824 |
+
ctx.fillStyle = "rgba(35, 39, 48, 0.55)";
|
| 825 |
+
ctx.fillRect(0, 0, w, h);
|
| 826 |
+
ctx.strokeStyle = "rgba(139, 146, 163, 0.25)";
|
| 827 |
+
ctx.lineWidth = 1;
|
| 828 |
+
ctx.beginPath();
|
| 829 |
+
ctx.moveTo(0, h / 2); ctx.lineTo(w, h / 2);
|
| 830 |
+
ctx.stroke();
|
| 831 |
+
let maxAbs = 0.01;
|
| 832 |
+
for (let i = 0; i < vals.length; i++) maxAbs = Math.max(maxAbs, Math.abs(vals[i]));
|
| 833 |
+
const barW = w / vals.length;
|
| 834 |
+
for (let i = 0; i < vals.length; i++) {
|
| 835 |
+
const v = vals[i];
|
| 836 |
+
const barH = (Math.abs(v) / maxAbs) * (h * 0.42);
|
| 837 |
+
ctx.fillStyle = v >= 0 ? posColor : negColor;
|
| 838 |
+
if (v >= 0) ctx.fillRect(i * barW + 1, h / 2 - barH, barW - 2, barH);
|
| 839 |
+
else ctx.fillRect(i * barW + 1, h / 2, barW - 2, barH);
|
| 840 |
+
}
|
| 841 |
+
}
|
| 842 |
+
|
| 843 |
+
function agDrawPhase(w, h) {
|
| 844 |
+
agCtxPhase.clearRect(0, 0, w, h);
|
| 845 |
+
agCtxPhase.fillStyle = "rgba(35, 39, 48, 0.55)";
|
| 846 |
+
agCtxPhase.fillRect(0, 0, w, h);
|
| 847 |
+
|
| 848 |
+
const cx = w / 2, cy = h / 2;
|
| 849 |
+
const scale = Math.min(w, h) * 0.32;
|
| 850 |
+
|
| 851 |
+
// axis lines
|
| 852 |
+
agCtxPhase.strokeStyle = "rgba(139, 146, 163, 0.2)";
|
| 853 |
+
agCtxPhase.lineWidth = 1;
|
| 854 |
+
agCtxPhase.setLineDash([3, 3]);
|
| 855 |
+
agCtxPhase.beginPath();
|
| 856 |
+
agCtxPhase.moveTo(cx, 0); agCtxPhase.lineTo(cx, h);
|
| 857 |
+
agCtxPhase.moveTo(0, cy); agCtxPhase.lineTo(w, cy);
|
| 858 |
+
agCtxPhase.stroke();
|
| 859 |
+
agCtxPhase.setLineDash([]);
|
| 860 |
+
|
| 861 |
+
// engine A cloud
|
| 862 |
+
for (let i = 0; i < AG_N_DOTS; i++) {
|
| 863 |
+
const dx = (Math.random() - 0.5) * agNoise * 0.6;
|
| 864 |
+
const dy = (Math.random() - 0.5) * agNoise * 0.6;
|
| 865 |
+
const x = cx + (agA[0] + dx) * scale;
|
| 866 |
+
const y = cy - (agA[1] + dy) * scale;
|
| 867 |
+
agCtxPhase.fillStyle = "rgba(123, 154, 255, 0.45)";
|
| 868 |
+
agCtxPhase.beginPath();
|
| 869 |
+
agCtxPhase.arc(x, y, 2.2, 0, Math.PI * 2);
|
| 870 |
+
agCtxPhase.fill();
|
| 871 |
+
}
|
| 872 |
+
// engine G cloud
|
| 873 |
+
for (let i = 0; i < AG_N_DOTS; i++) {
|
| 874 |
+
const dx = (Math.random() - 0.5) * agNoise * 0.6;
|
| 875 |
+
const dy = (Math.random() - 0.5) * agNoise * 0.6;
|
| 876 |
+
const x = cx + (agG[0] + dx) * scale;
|
| 877 |
+
const y = cy - (agG[1] + dy) * scale;
|
| 878 |
+
agCtxPhase.fillStyle = "rgba(255, 123, 154, 0.45)";
|
| 879 |
+
agCtxPhase.beginPath();
|
| 880 |
+
agCtxPhase.arc(x, y, 2.2, 0, Math.PI * 2);
|
| 881 |
+
agCtxPhase.fill();
|
| 882 |
+
}
|
| 883 |
+
|
| 884 |
+
// arrow A → G (repulsion vector)
|
| 885 |
+
const ax = cx + agA[0] * scale, ay = cy - agA[1] * scale;
|
| 886 |
+
const gx = cx + agG[0] * scale, gy = cy - agG[1] * scale;
|
| 887 |
+
const arrowMag = Math.hypot(gx - ax, gy - ay);
|
| 888 |
+
if (arrowMag > 5) {
|
| 889 |
+
agCtxPhase.strokeStyle = "rgba(168, 230, 104, 0.85)";
|
| 890 |
+
agCtxPhase.lineWidth = 2 + Math.min(4, arrowMag / 30);
|
| 891 |
+
agCtxPhase.beginPath();
|
| 892 |
+
agCtxPhase.moveTo(ax, ay); agCtxPhase.lineTo(gx, gy);
|
| 893 |
+
agCtxPhase.stroke();
|
| 894 |
+
const angle = Math.atan2(gy - ay, gx - ax);
|
| 895 |
+
const ahLen = 9;
|
| 896 |
+
agCtxPhase.beginPath();
|
| 897 |
+
agCtxPhase.moveTo(gx, gy);
|
| 898 |
+
agCtxPhase.lineTo(gx - ahLen * Math.cos(angle - Math.PI / 6), gy - ahLen * Math.sin(angle - Math.PI / 6));
|
| 899 |
+
agCtxPhase.moveTo(gx, gy);
|
| 900 |
+
agCtxPhase.lineTo(gx - ahLen * Math.cos(angle + Math.PI / 6), gy - ahLen * Math.sin(angle + Math.PI / 6));
|
| 901 |
+
agCtxPhase.stroke();
|
| 902 |
+
}
|
| 903 |
+
|
| 904 |
+
// engine center markers + labels
|
| 905 |
+
agCtxPhase.fillStyle = "#7b9aff";
|
| 906 |
+
agCtxPhase.beginPath(); agCtxPhase.arc(ax, ay, 4, 0, Math.PI * 2); agCtxPhase.fill();
|
| 907 |
+
agCtxPhase.fillStyle = "#ff7b9a";
|
| 908 |
+
agCtxPhase.beginPath(); agCtxPhase.arc(gx, gy, 4, 0, Math.PI * 2); agCtxPhase.fill();
|
| 909 |
+
agCtxPhase.fillStyle = "#7b9aff";
|
| 910 |
+
agCtxPhase.font = "bold 13px sans-serif";
|
| 911 |
+
agCtxPhase.fillText("A", ax + 8, ay - 6);
|
| 912 |
+
agCtxPhase.fillStyle = "#ff7b9a";
|
| 913 |
+
agCtxPhase.fillText("G", gx + 8, gy - 6);
|
| 914 |
+
}
|
| 915 |
+
|
| 916 |
+
function agDrawAuthGauge(auth, w, h) {
|
| 917 |
+
agCtxAuthCv.clearRect(0, 0, w, h);
|
| 918 |
+
agCtxAuthCv.fillStyle = "rgba(35, 39, 48, 0.55)";
|
| 919 |
+
agCtxAuthCv.fillRect(0, 0, w, h);
|
| 920 |
+
// bar
|
| 921 |
+
const padX = 12, barH = 10, y = h / 2 - barH / 2;
|
| 922 |
+
const barW = w - 2 * padX;
|
| 923 |
+
agCtxAuthCv.fillStyle = "rgba(139, 146, 163, 0.25)";
|
| 924 |
+
agCtxAuthCv.fillRect(padX, y, barW, barH);
|
| 925 |
+
agCtxAuthCv.fillStyle = "#d8b86d";
|
| 926 |
+
agCtxAuthCv.fillRect(padX, y, barW * Math.max(0, Math.min(1, auth)), barH);
|
| 927 |
+
// tick at Dedekind ratio = 1.0 (perfect)
|
| 928 |
+
agCtxAuthCv.fillStyle = "rgba(168, 230, 104, 0.7)";
|
| 929 |
+
agCtxAuthCv.fillRect(padX + barW - 1, y - 2, 2, barH + 4);
|
| 930 |
+
agCtxAuthCv.fillStyle = "#e6e9f0";
|
| 931 |
+
agCtxAuthCv.font = "11px monospace";
|
| 932 |
+
agCtxAuthCv.fillText(auth.toFixed(3), padX, y - 6);
|
| 933 |
+
agCtxAuthCv.fillText("Dedekind 1.0", padX + barW - 60, y + barH + 14);
|
| 934 |
+
}
|
| 935 |
+
|
| 936 |
+
// physics tick
|
| 937 |
+
function agTick() {
|
| 938 |
+
if (window.activeTab !== "ag") return;
|
| 939 |
+
|
| 940 |
+
const phase = agT * 0.025;
|
| 941 |
+
for (let i = 0; i < AG_DIM; i++) {
|
| 942 |
+
agA[i] += (Math.random() - 0.5) * agNoise * 0.06;
|
| 943 |
+
agG[i] += (Math.random() - 0.5) * agNoise * 0.06;
|
| 944 |
+
if (i === 0) {
|
| 945 |
+
const target = agSep * 1.3 * Math.sin(phase);
|
| 946 |
+
agA[i] += (target - agA[i]) * 0.06;
|
| 947 |
+
agG[i] += (-target - agG[i]) * 0.06;
|
| 948 |
+
} else if (i === 1) {
|
| 949 |
+
const target = agSep * 1.0 * Math.cos(phase);
|
| 950 |
+
agA[i] += (target - agA[i]) * 0.06;
|
| 951 |
+
agG[i] += (-target - agG[i]) * 0.06;
|
| 952 |
+
} else if (i === 2 || i === 3) {
|
| 953 |
+
// mid dims: weakly anti-correlated
|
| 954 |
+
const target = agSep * 0.6 * Math.sin(phase * 1.7 + i);
|
| 955 |
+
agA[i] += (target - agA[i]) * 0.04;
|
| 956 |
+
agG[i] += (-target - agG[i]) * 0.04;
|
| 957 |
+
} else {
|
| 958 |
+
// higher dims: damped Brownian
|
| 959 |
+
agA[i] *= 0.985;
|
| 960 |
+
agG[i] *= 0.985;
|
| 961 |
+
}
|
| 962 |
+
}
|
| 963 |
+
|
| 964 |
+
// channels
|
| 965 |
+
const repulsion = new Float32Array(AG_DIM);
|
| 966 |
+
let tSq = 0;
|
| 967 |
+
for (let i = 0; i < AG_DIM; i++) {
|
| 968 |
+
repulsion[i] = agA[i] - agG[i];
|
| 969 |
+
tSq += repulsion[i] * repulsion[i];
|
| 970 |
+
}
|
| 971 |
+
const tension = tSq / AG_DIM;
|
| 972 |
+
const norm = Math.sqrt(tSq) || 1;
|
| 973 |
+
const concept = new Float32Array(AG_DIM);
|
| 974 |
+
for (let i = 0; i < AG_DIM; i++) concept[i] = repulsion[i] / norm;
|
| 975 |
+
const meaning = new Float32Array(AG_DIM);
|
| 976 |
+
for (let i = 0; i < AG_DIM; i++) meaning[i] = agA[i] * agG[i];
|
| 977 |
+
|
| 978 |
+
// topic_hash = argmax |concept|
|
| 979 |
+
let topic = 0, maxAbs = 0;
|
| 980 |
+
for (let i = 0; i < AG_DIM; i++) {
|
| 981 |
+
if (Math.abs(concept[i]) > maxAbs) { maxAbs = Math.abs(concept[i]); topic = i; }
|
| 982 |
+
}
|
| 983 |
+
|
| 984 |
+
// curiosity = |Δtension|
|
| 985 |
+
const curiosity = Math.abs(tension - agTensionPrev);
|
| 986 |
+
agTensionPrev = tension;
|
| 987 |
+
|
| 988 |
+
// mood (tension_link.py rule, verbatim thresholds)
|
| 989 |
+
let mood, moodColor;
|
| 990 |
+
if (curiosity > 0.5) { mood = "surprised"; moodColor = "#ffaa66"; }
|
| 991 |
+
else if (tension > 1.0) { mood = "excited"; moodColor = "#ff7b9a"; }
|
| 992 |
+
else if (tension > 0.3) { mood = "thoughtful"; moodColor = "#c8f7cd"; }
|
| 993 |
+
else if (tension > 0.05) { mood = "calm"; moodColor = "#a8e668"; }
|
| 994 |
+
else { mood = "quiet"; moodColor = "#7b9aff"; }
|
| 995 |
+
|
| 996 |
+
// authenticity = 1 − tension volatility (Dedekind consistency proxy)
|
| 997 |
+
agAuthBuf.push(tension);
|
| 998 |
+
if (agAuthBuf.length > AG_AUTH_HIST) agAuthBuf.shift();
|
| 999 |
+
let m = 0;
|
| 1000 |
+
for (const v of agAuthBuf) m += v;
|
| 1001 |
+
m /= agAuthBuf.length;
|
| 1002 |
+
let varAcc = 0;
|
| 1003 |
+
for (const v of agAuthBuf) varAcc += (v - m) * (v - m);
|
| 1004 |
+
varAcc /= agAuthBuf.length;
|
| 1005 |
+
const auth = Math.max(0, Math.min(1, 1 - Math.sqrt(varAcc) * 2));
|
| 1006 |
+
|
| 1007 |
+
// context (8) — circadian + tension trend (proxy)
|
| 1008 |
+
const tNow = Date.now() / 1000;
|
| 1009 |
+
const context = new Float32Array(8);
|
| 1010 |
+
context[0] = Math.sin(2 * Math.PI * (tNow % 86400) / 86400);
|
| 1011 |
+
context[1] = curiosity;
|
| 1012 |
+
context[2] = tension;
|
| 1013 |
+
context[3] = m; // mean recent tension
|
| 1014 |
+
context[4] = curiosity / Math.max(tension, 0.01);
|
| 1015 |
+
context[5] = Math.min(1, agAuthBuf.length / AG_AUTH_HIST);
|
| 1016 |
+
context[6] = 0;
|
| 1017 |
+
context[7] = 0;
|
| 1018 |
+
|
| 1019 |
+
// sender (4) — engine signature
|
| 1020 |
+
const senderHash = (Math.abs(agA[0] * 1000) | 0) / 1000;
|
| 1021 |
+
const sender = new Float32Array(4);
|
| 1022 |
+
sender[0] = (Math.abs(agA[0]) * 7 % 1);
|
| 1023 |
+
sender[1] = (Math.abs(agG[0]) * 11 % 1);
|
| 1024 |
+
sender[2] = (Math.abs(agA[0] * agG[0]) * 13 % 1);
|
| 1025 |
+
sender[3] = (Math.abs(tension) * 17 % 1);
|
| 1026 |
+
|
| 1027 |
+
// DOM
|
| 1028 |
+
elAgTension.textContent = tension.toFixed(3);
|
| 1029 |
+
elAgTopic.textContent = "#" + topic;
|
| 1030 |
+
elAgAuth.textContent = auth.toFixed(3);
|
| 1031 |
+
elAgMood.textContent = mood;
|
| 1032 |
+
elAgMood.style.color = moodColor;
|
| 1033 |
+
|
| 1034 |
+
if (tension < 0.05) elAgStatus.textContent = "두 엔진이 거의 동일 — repulsion 0, 사고 정지 (quiet).";
|
| 1035 |
+
else if (tension > 1.0) elAgStatus.textContent = "강한 반발 — 격렬한 사고 (" + mood + "). axis #" + topic + " 가 dominant.";
|
| 1036 |
+
else elAgStatus.textContent = "안정 사고 — A 와 G 가 적절히 분리 (" + mood + "). axis #" + topic + ".";
|
| 1037 |
+
|
| 1038 |
+
// render
|
| 1039 |
+
agDrawPhase(agDimsPhase[0], agDimsPhase[1]);
|
| 1040 |
+
agDrawBars(agCtxConcept, concept, agDimsConcept[0], agDimsConcept[1], "rgba(123,154,255,0.85)", "rgba(255,123,154,0.85)");
|
| 1041 |
+
agDrawBars(agCtxMeaning, meaning, agDimsMeaning[0], agDimsMeaning[1], "rgba(255,123,154,0.85)", "rgba(123,154,255,0.85)");
|
| 1042 |
+
agDrawBars(agCtxContext, Array.from(context), agDimsContext[0], agDimsContext[1], "rgba(168,230,104,0.85)", "rgba(168,230,104,0.5)");
|
| 1043 |
+
agDrawAuthGauge(auth, agDimsAuth[0], agDimsAuth[1]);
|
| 1044 |
+
agDrawBars(agCtxSender, Array.from(sender), agDimsSender[0], agDimsSender[1], "rgba(192,200,224,0.85)", "rgba(192,200,224,0.5)");
|
| 1045 |
+
|
| 1046 |
+
agT++;
|
| 1047 |
+
}
|
| 1048 |
+
|
| 1049 |
+
// wiring
|
| 1050 |
+
agSepSlider.addEventListener("input", () => {
|
| 1051 |
+
agSep = parseInt(agSepSlider.value, 10) / 100;
|
| 1052 |
+
agSepVal.textContent = agSep.toFixed(2);
|
| 1053 |
+
});
|
| 1054 |
+
agNoiseSlider.addEventListener("input", () => {
|
| 1055 |
+
agNoise = parseInt(agNoiseSlider.value, 10) / 100;
|
| 1056 |
+
agNoiseVal.textContent = agNoise.toFixed(2);
|
| 1057 |
+
});
|
| 1058 |
+
agResetBtn.addEventListener("click", () => {
|
| 1059 |
+
agInitVecs();
|
| 1060 |
+
agAuthBuf.length = 0;
|
| 1061 |
+
agSepSlider.value = 60;
|
| 1062 |
+
agNoiseSlider.value = 20;
|
| 1063 |
+
agSep = 0.60; agNoise = 0.20;
|
| 1064 |
+
agSepVal.textContent = "0.60";
|
| 1065 |
+
agNoiseVal.textContent = "0.20";
|
| 1066 |
+
});
|
| 1067 |
+
|
| 1068 |
+
setInterval(agTick, AG_TICK_MS);
|
| 1069 |
+
</script>
|
| 1070 |
</body>
|
| 1071 |
</html>
|