dancinlife Claude Opus 4.7 (1M context) commited on
Commit
eb8957b
·
1 Parent(s): 593d4d1

add: 🧲 A×G tab — dual engine repulsion field

Browse files

Third 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>

Files changed (1) hide show
  1. index.html +419 -0
index.html CHANGED
@@ -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 &gt; 0.5 → surprised / tension &gt; 1.0 → excited / &gt; 0.3 → thoughtful / &gt; 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>