wu-xing-pair-breaker.html raw
1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>否 The Star — Wu Xing Pair-Breaker</title>
7 <style>
8 @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;600&display=swap');
9 *{margin:0;padding:0;box-sizing:border-box}
10 body{background:#0a0a0a;color:#c0c0c0;font-family:'JetBrains Mono',monospace;font-size:12px;overflow:hidden;width:100vw;height:100vh}
11 #c{display:block}
12 #panel{position:fixed;top:0;right:0;width:280px;height:100vh;background:rgba(8,8,8,0.94);border-left:1px solid #1a1a1a;padding:14px;overflow-y:auto;z-index:10;backdrop-filter:blur(12px)}
13 h1{font-size:11px;font-weight:600;color:#e8d5a3;letter-spacing:.14em;text-transform:uppercase;border-bottom:1px solid #2a2218;padding-bottom:7px;margin-bottom:10px}
14 .gt{font-size:9px;font-weight:600;color:#555;text-transform:uppercase;letter-spacing:.14em;margin:11px 0 5px}
15 .p{display:flex;align-items:center;justify-content:space-between;margin-bottom:4px}
16 .p label{color:#777;font-size:10px}
17 .pr{display:flex;align-items:center;gap:5px}
18 input[type=range]{-webkit-appearance:none;width:85px;height:2px;background:#333;outline:none}
19 input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:10px;height:10px;background:#e8d5a3;border-radius:50%;cursor:pointer}
20 .val{color:#e8d5a3;font-size:10px;min-width:30px;text-align:right;font-variant-numeric:tabular-nums}
21 .btn{background:#1a1714;border:1px solid #2a2218;color:#e8d5a3;font-family:inherit;font-size:10px;padding:5px 10px;cursor:pointer;letter-spacing:.08em;text-transform:uppercase;margin-top:3px;width:100%;transition:all .15s}
22 .btn:hover{background:#2a2218;border-color:#e8d5a3}
23 .btn.active{background:#e8d5a3;color:#0a0a0a}
24 .sep{height:1px;background:#1a1a1a;margin:8px 0}
25 .legend{display:flex;flex-wrap:wrap;gap:5px;margin-top:5px}
26 .leg{display:flex;align-items:center;gap:3px;font-size:9px;color:#777}
27 .leg .dot{width:7px;height:7px;border-radius:50%}
28 .nb{display:flex;align-items:center;gap:3px;margin-bottom:3px;font-size:9px}
29 .nb .icon{width:14px;height:14px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:7px;color:#000;font-weight:700}
30 .nb .nm{width:36px;color:#888}
31 .nb .bg{flex:1;height:10px;background:#111;border-radius:2px;overflow:hidden;position:relative}
32 .nb .fl{height:100%;transition:width 0.04s linear;border-radius:2px}
33 .nb .th{position:absolute;top:0;height:100%;width:2px;background:#e8d5a3;opacity:0.6}
34 .nb .vl{width:22px;text-align:right;color:#666;font-size:8px;font-variant-numeric:tabular-nums}
35 #info{position:fixed;bottom:14px;left:14px;z-index:10;font-size:9px;color:#444}
36 #tl{position:fixed;top:18px;left:22px;z-index:10}
37 #tl h2{font-size:14px;font-weight:300;color:#e8d5a3;letter-spacing:.2em;text-transform:uppercase;margin-bottom:3px}
38 #tl .sub{font-size:9px;color:#555;letter-spacing:.12em;text-transform:uppercase}
39 #rotor-label{font-size:9px;color:#e8d5a3;margin-top:4px;letter-spacing:.06em;text-align:center}
40 #fire-seq{font-size:8px;color:#555;margin-top:4px;line-height:1.6;max-height:55px;overflow-y:auto}
41 #fire-seq b{color:#e8d5a3}
42 .res-row{display:flex;align-items:center;gap:4px;font-size:8px;margin-bottom:3px}
43 .res-row .r-label{color:#888;width:70px}
44 .res-row .r-bar{flex:1;height:4px;border-radius:2px;background:#111;overflow:hidden}
45 .res-row .r-fill{height:100%;border-radius:2px;transition:width .08s}
46 .steady{font-size:9px;color:#e8d5a3;text-align:center;margin:4px 0;letter-spacing:.06em}
47 .cb{display:flex;align-items:center;gap:3px;margin-bottom:2px;font-size:8px;height:12px}
48 .cb .cl{width:50px;color:#555;text-align:right}
49 .cb .bg{flex:1;height:6px;background:#111;border-radius:1px;overflow:hidden}
50 .cb .fl{height:100%;border-radius:1px}
51 .cb .vl{width:22px;text-align:right;color:#666;font-variant-numeric:tabular-nums}
52 </style>
53 </head>
54 <body>
55 <canvas id="c"></canvas>
56 <div id="tl"><h2>否 The Star</h2><div class="sub">Wu Xing Pair-Breaker · Kè Cycle Rotor · 5 nodes · 19μ</div></div>
57
58 <div id="panel">
59 <h1>否 Pi Device — Solve</h1>
60
61 <div class="gt">Geometry</div>
62 <div class="p"><label>Radius</label><div class="pr"><input type=range id="sR" min=2 max=8 step=.1 value=4><span class=val id="vR">4.0</span></div></div>
63 <div class="p"><label>Vert. Spread</label><div class="pr"><input type=range id="sV" min=.5 max=4 step=.1 value=1.8><span class=val id="vV">1.8</span></div></div>
64 <div class="p"><label>Bar Thick</label><div class="pr"><input type=range id="sT" min=.05 max=.3 step=.01 value=.12><span class=val id="vT">0.12</span></div></div>
65 <div class="p"><label>Coil Turns/m</label><div class="pr"><input type=range id="sD" min=8 max=40 step=1 value=20><span class=val id="vD">20</span></div></div>
66 <div class="p"><label>Core μᵣ</label><div class="pr"><input type=range id="sMu" min=100 max=5000 step=100 value=1000><span class=val id="vMu">1000</span></div></div>
67
68 <div class="sep"></div>
69 <div class="gt">Ignition</div>
70 <div class="p"><label>Seed Node</label><div class="pr"><input type=range id="sSeed" min=0 max=4 step=1 value=0><span class=val id="vSeed">木</span></div></div>
71 <div class="p"><label>Coupling k</label><div class="pr"><input type=range id="sK" min=.1 max=.95 step=.05 value=.6><span class=val id="vK">0.60</span></div></div>
72 <div class="p"><label>Damping</label><div class="pr"><input type=range id="sDamp" min=.001 max=.06 step=.001 value=.008><span class=val id="vDamp">0.008</span></div></div>
73 <div class="p"><label>Pair Energy</label><div class="pr"><input type=range id="sPair" min=0 max=1 step=.01 value=.35><span class=val id="vPair">0.35</span></div></div>
74 <div class="p"><label>Load (extract)</label><div class="pr"><input type=range id="sLoad" min=0 max=.5 step=.01 value=0><span class=val id="vLoad">0.00</span></div></div>
75 <div class="p"><label>Glow</label><div class="pr"><input type=range id="sGlow" min=0 max=2 step=.1 value=1.2><span class=val id="vGlow">1.2</span></div></div>
76 <button class="btn" id="btnMag">🧲 Wave Magnet</button>
77 <button class="btn" id="btnReset">Reset</button>
78
79 <div class="sep"></div>
80 <div class="gt">Dimmer (木 potentiometer)</div>
81 <div class="p"><label>木 R<sub>pot</sub></label><div class="pr"><input type=range id="sDim" min=0 max=1 step=.01 value=0><span class=val id="vDim">0.00</span></div></div>
82 <div style="font-size:8px;color:#444;margin-top:2px">0 = full bright · 1 = off (short to ground)</div>
83
84 <div class="sep"></div>
85 <div class="gt">Steady State</div>
86 <div class="steady">ΣC = 19 · μᵣ = <span id="muDisp">1000</span> · I₀ = <span id="steadyI">19000</span></div>
87
88 <div class="sep"></div>
89 <div class="gt">Node Capacitors (Fibonacci)</div>
90 <div id="node-caps"></div>
91
92 <div class="sep"></div>
93 <div class="gt">Three Resonances</div>
94 <div id="resonances">
95 <div class="res-row"><div class="r-label" style="color:#c4a040">木1+火2=土3</div><div class="r-bar"><div class="r-fill" id="res0" style="background:#c4a040;width:0"></div></div></div>
96 <div class="res-row"><div class="r-label" style="color:#a0a0a8">火2+土3=金5</div><div class="r-bar"><div class="r-fill" id="res1" style="background:#a0a0a8;width:0"></div></div></div>
97 <div class="res-row"><div class="r-label" style="color:#4060c4">土3+金5=水8</div><div class="r-bar"><div class="r-fill" id="res2" style="background:#4060c4;width:0"></div></div></div>
98 </div>
99
100 <div class="sep"></div>
101 <div class="gt">Central Vortex</div>
102 <div class="cb"><div class="cl" style="color:#a060e0">克 flux</div><div class="bg"><div class="fl" id="fluxBar" style="background:linear-gradient(90deg,#4422aa,#8844ff);width:0"></div></div><div class="vl" id="fluxV">0</div></div>
103 <div class="cb"><div class="cl" style="color:#44cc66">pair → ΣC</div><div class="bg"><div class="fl" id="pairBar" style="background:linear-gradient(90deg,#226633,#44cc66);width:0"></div></div><div class="vl" id="pairV">0</div></div>
104
105 <div class="sep"></div>
106 <div class="gt">Hysteresis (Core Memory)</div>
107 <div class="cb"><div class="cl" style="color:#cc8844">core B̄</div><div class="bg"><div class="fl" id="satBar" style="background:linear-gradient(90deg,#663311,#cc8844);width:0"></div></div><div class="vl" id="satV">0</div></div>
108 <div id="rem-bars"></div>
109 <div style="font-size:8px;color:#444;margin-top:3px;text-align:center" id="hystState">dormant</div>
110
111 <div class="sep"></div>
112 <div class="gt">Kè Rotor</div>
113 <div style="text-align:center"><canvas id="rc" width="130" height="130"></canvas></div>
114 <div id="rotor-label">awaiting ignition</div>
115
116 <div class="sep"></div>
117 <div class="gt">Fire Sequence (克 dominant)</div>
118 <div id="fire-seq"></div>
119
120 <div class="sep"></div>
121 <div class="gt">Display</div>
122 <div class="p"><label>Fractal Depth</label><div class="pr"><input type=range id="sFrac" min=0 max=5 step=1 value=3><span class=val id="vFrac">3</span></div></div>
123 <button class="btn" id="btnVac">Vacuum Chamber</button>
124 <button class="btn active" id="btnFlux">Flux Lines</button>
125 <button class="btn" id="btnWire">Wireframe</button>
126
127 <div class="sep"></div>
128 <div class="legend">
129 <div class="leg"><div class="dot" style="background:#4a9e4a"></div>木 C=1</div>
130 <div class="leg"><div class="dot" style="background:#c44040"></div>火 C=2</div>
131 <div class="leg"><div class="dot" style="background:#c4a040"></div>土 C=3</div>
132 <div class="leg"><div class="dot" style="background:#a0a0a8"></div>金 C=5</div>
133 <div class="leg"><div class="dot" style="background:#4060c4"></div>水 C=8</div>
134 </div>
135 <div style="font-size:8px;color:#333;margin-top:6px;text-align:center">克 Wood→Earth→Water→Fire→Metal→Wood</div>
136 </div>
137
138 <div id="info">Drag to rotate · Scroll to zoom · 否 The Star · Kè Rotor</div>
139
140 <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
141 <script>
142 const PHI=(1+Math.sqrt(5))/2;
143 const P=[
144 {n:'Wood',c:'木',col:0x4a9e4a,css:'#4a9e4a',a:-Math.PI/2, cap:1},
145 {n:'Fire',c:'火',col:0xc44040,css:'#c44040',a:-Math.PI/2+2*Math.PI/5, cap:2},
146 {n:'Earth',c:'土',col:0xc4a040,css:'#c4a040',a:-Math.PI/2+4*Math.PI/5, cap:3},
147 {n:'Metal',c:'金',col:0xa0a0a8,css:'#a0a0a8',a:-Math.PI/2+6*Math.PI/5, cap:5},
148 {n:'Water',c:'水',col:0x4060c4,css:'#4060c4',a:-Math.PI/2+8*Math.PI/5, cap:8}
149 ];
150 const SUM_C=19; // 1+2+3+5+8 — prime
151
152 // Kè cycle order: 0→2→4→1→3→0 (Wood→Earth→Water→Fire→Metal)
153 const KE_ORDER=[0,2,4,1,3];
154 // Edges: 0-4 shēng (i→i+1%5), 5-9 kè (i→i+2%5)
155 function eNodes(e){return e<5?[e,(e+1)%5]:[e-5,(e-5+2)%5]}
156
157 const G={R:4,V:1.8,T:.12,D:20,mu:1000,seed:0,k:.6,damp:.008,pair:.35,load:0,dim:0,glow:1.2,frac:3,vac:false,wire:false,showFlux:true};
158 const THRESHOLD=0.7;
159
160 const sim={
161 charge:[0,0,0,0,0],
162 fireIntensity:[0,0,0,0,0],
163 edgeGlow:new Float64Array(10),
164 flux:0, running:false,
165 fireHistory:[],
166 rotorAngle:0, rotorSpeed:0, totalFirings:0,
167 resonance:[0,0,0],
168 totalEnergy:0,
169 pairFeedback:0,
170 refractory:[0,0,0,0,0],
171 barMag:new Float64Array(10),
172 remnant:new Float64Array(5),
173 coreSaturation:0
174 };
175
176 // 3D
177 const canvas=document.getElementById('c');
178 const scene=new THREE.Scene();
179 scene.fog=new THREE.FogExp2(0x0a0a0a,0.02);
180 const camera=new THREE.PerspectiveCamera(50,innerWidth/innerHeight,0.1,200);
181 const renderer=new THREE.WebGLRenderer({canvas,antialias:true});
182 renderer.setSize(innerWidth,innerHeight);
183 renderer.setPixelRatio(Math.min(devicePixelRatio,2));
184 renderer.toneMapping=THREE.ACESFilmicToneMapping;
185 renderer.toneMappingExposure=1.2;
186
187 scene.add(new THREE.AmbientLight(0x1a1410,0.6));
188 const dL=new THREE.DirectionalLight(0xffe8c0,0.8);dL.position.set(5,8,4);scene.add(dL);
189 const ctrL=new THREE.PointLight(0x8844ff,0,20);scene.add(ctrL);
190 // Kè-colored point light for the star pattern
191 const keL=new THREE.PointLight(0xff4444,0,15);keL.position.set(0,1,0);scene.add(keL);
192 scene.add(new THREE.GridHelper(20,40,0x111111,0x0d0d0d)).position.y=-3;
193
194 const devG=new THREE.Group();scene.add(devG);
195
196 const coreM=new THREE.MeshStandardMaterial({color:0x2a2a2a,metalness:.85,roughness:.35});
197 const coreMW=new THREE.MeshStandardMaterial({color:0x2a2a2a,metalness:.85,roughness:.35,wireframe:true});
198 const copM=new THREE.MeshStandardMaterial({color:0xb87333,metalness:.9,roughness:.25});
199 const copMW=new THREE.MeshStandardMaterial({color:0xb87333,metalness:.9,roughness:.25,wireframe:true});
200 const nodeMs=P.map(p=>new THREE.MeshBasicMaterial({color:p.col,transparent:true,opacity:.5}));
201 const edgeGMs=[];
202 for(let e=0;e<10;e++){edgeGMs.push(new THREE.MeshBasicMaterial({color:P[eNodes(e)[0]].col,transparent:true,opacity:0}))}
203 const cntM=new THREE.MeshBasicMaterial({color:0x8844ff,transparent:true,opacity:0,side:2});
204 const cntM2=new THREE.MeshBasicMaterial({color:0xff4466,transparent:true,opacity:0,side:2});
205
206 function nPos(i,R,y){return new THREE.Vector3(Math.cos(P[i].a)*R,y||0,Math.sin(P[i].a)*R)}
207
208 // === NONLINEAR CORE PHYSICS ===
209 // Base coupling topology (kè-dominant)
210 // Continuous ferromagnetic core = tight coupling.
211 // kè path shares a bar segment — very high coupling.
212 // shēng path goes through adjacent bar — still good.
213 function computeBaseCoupling(){
214 const C=[];
215 for(let i=0;i<5;i++){
216 C[i]=[];
217 for(let j=0;j<5;j++){
218 if(i===j){C[i][j]=0;continue;}
219 const isKe=(i+2)%5===j;
220 const isSheng=(i+1)%5===j;
221 const isKeRecv=(i+3)%5===j;
222 if(isKe) C[i][j]=G.k*0.9; // primary kè: tight
223 else if(isSheng) C[i][j]=G.k*0.45; // shēng: regenerative
224 else if(isKeRecv) C[i][j]=G.k*0.2; // secondary kè
225 else C[i][j]=G.k*0.1; // residual through core
226 }
227 }
228 return C;
229 }
230 let baseCoupling=computeBaseCoupling();
231
232 // Per-bar magnetization state (0..1, fraction of saturation)
233 sim.barMag=new Float64Array(10);
234 // Core remnant field per node (hysteresis — what's left after discharge)
235 sim.remnant=new Float64Array(5);
236
237 // B-H saturation curve: soft knee, doesn't kill coupling until very high saturation
238 // A continuous core distributes flux — individual bars don't saturate easily
239 function muEff(magnetization){
240 // Gentle cubic: stays above 0.5 until magnetization > 0.8
241 const x=Math.min(1,magnetization);
242 return 1.0 - x*x*x*0.7;
243 }
244
245 // Effective coupling: saturation is GLOBAL (continuous core), not per-bar
246 function effectiveCoupling(i,j){
247 const base=baseCoupling[i][j];
248 if(base===0)return 0;
249 // Global core saturation — flux distributes across whole core
250 let totalMag=0;for(let e=0;e<10;e++)totalMag+=sim.barMag[e];
251 const globalSat=totalMag/10;
252 return base*muEff(globalSat);
253 }
254
255 function simStep(dt){
256 if(!sim.running)return;
257 const nc=[...sim.charge];
258
259 // === FIRING (with nonlinear coupling) ===
260 for(let i=0;i<5;i++){
261 const cap=P[i].cap;
262 const norm=sim.charge[i]/cap;
263
264 if(sim.refractory[i]>0){
265 sim.refractory[i]-=dt;
266 continue;
267 }
268
269 if(norm>=THRESHOLD){
270 const energy=sim.charge[i]; // full discharge
271 nc[i]=0;
272 sim.refractory[i]=0.008*cap; // short refractory — fast recovery
273 sim.fireIntensity[i]=1.0;
274 sim.totalFirings++;
275 sim.fireHistory.push({node:i,t:performance.now()});
276 if(sim.fireHistory.length>60)sim.fireHistory.shift();
277
278 sim.edgeGlow[i+5]=1.0;
279 sim.edgeGlow[i]=0.4;
280
281 // Magnetize outgoing bars (gradual — continuous core distributes)
282 sim.barMag[i]=Math.min(1,sim.barMag[i]+energy*0.06); // shēng bar
283 sim.barMag[i+5]=Math.min(1,sim.barMag[i+5]+energy*0.1); // kè bar
284
285 // Distribute via STATE-DEPENDENT coupling
286 for(let j=0;j<5;j++){
287 if(i===j)continue;
288 nc[j]+=energy*effectiveCoupling(i,j);
289 }
290
291 // Central flux — nonlinear: constructive interference
292 sim.flux+=energy*0.5;
293
294 // Rotor impulse
295 sim.rotorSpeed+=energy*0.12;
296
297 // Remnant field at firing node (hysteresis memory)
298 sim.remnant[i]=Math.min(0.4,sim.remnant[i]+energy*0.06);
299
300 // Resonance tracking
301 if(i===2)sim.resonance[0]=1.0;
302 if(i===3)sim.resonance[1]=1.0;
303 if(i===4)sim.resonance[2]=1.0;
304 }
305 }
306
307 // === PAIR-BOND ENERGY FEEDBACK ===
308 // Above flux threshold, the quasiperiodic field disrupts dark pairs.
309 // Released energy couples back through the core.
310 // Linear base + quadratic bonus as coherence builds.
311 const fluxThreshold=0.12;
312 if(sim.flux>fluxThreshold){
313 const excess=sim.flux-fluxThreshold;
314 const pairEnergy=(excess*G.pair*3 + excess*excess*G.pair*8)*dt;
315 sim.pairFeedback=pairEnergy;
316 for(let i=0;i<5;i++){
317 // Pair energy couples proportional to cap + remnant bias (hysteresis directs it)
318 const weight=(P[i].cap/SUM_C)*(1+sim.remnant[i]*3);
319 nc[i]+=pairEnergy*weight;
320 }
321 // Breaking pairs costs some flux, but less than what's returned
322 sim.flux-=pairEnergy*0.2;
323 } else {
324 sim.pairFeedback=0;
325 }
326
327 // === LOAD EXTRACTION ===
328 if(G.load>0){
329 for(let i=0;i<5;i++){
330 nc[i]*=(1-G.load*dt*3);
331 }
332 sim.rotorSpeed*=(1-G.load*dt*2);
333 }
334
335 // === DIMMER: variable resistor to ground on 木 (node 0) ===
336 // At dim=0, no drain (full bright). At dim=1, all charge bleeds instantly (off).
337 // The potentiometer drains charge from Wood continuously.
338 // This weakens the cascade because Wood initiates each kè revolution.
339 if(G.dim>0){
340 const drainRate=G.dim*G.dim*40; // quadratic: gentle at low, savage at high
341 nc[0]*=(1/(1+drainRate*dt));
342 // At high resistance, Wood's threshold is effectively unreachable,
343 // which starves the rest of the Fibonacci sequence
344 }
345
346 // === DECAY (nonlinear — saturated bars decay slower due to remnant magnetization) ===
347 for(let i=0;i<5;i++){
348 nc[i]*=(1-G.damp*dt*10);
349 // Remnant field slowly charges caps (residual flux in core)
350 nc[i]+=sim.remnant[i]*dt*0.5;
351 nc[i]=Math.max(0,Math.min(P[i].cap,nc[i]));
352 sim.fireIntensity[i]*=(1-dt*12);
353 sim.edgeGlow[i]*=(1-dt*14);
354 sim.edgeGlow[i+5]*=(1-dt*10);
355 }
356 // Bar demagnetization — slower when more saturated (hysteresis)
357 for(let e=0;e<10;e++){
358 const demagRate=1.5*(1-sim.barMag[e]*0.7); // very slow when saturated
359 sim.barMag[e]*=(1-dt*demagRate);
360 }
361 // Remnant field decays very slowly (this is the hysteresis diode effect)
362 for(let i=0;i<5;i++){
363 sim.remnant[i]*=(1-dt*0.15);
364 }
365 sim.flux=Math.max(0,sim.flux*(1-dt*1.5));
366 sim.rotorSpeed*=(1-dt*0.12);
367 sim.rotorAngle+=sim.rotorSpeed*dt;
368 for(let r=0;r<3;r++)sim.resonance[r]*=(1-dt*4);
369
370 sim.charge=nc;
371 sim.totalEnergy=nc.reduce((a,b)=>a+b,0)+sim.flux;
372
373 // Track core saturation for display
374 let totalMag=0;for(let e=0;e<10;e++)totalMag+=sim.barMag[e];
375 sim.coreSaturation=totalMag/10;
376
377 if(sim.totalEnergy<0.0005&&sim.rotorSpeed<0.005&&sim.coreSaturation<0.01)sim.running=false;
378 }
379
380 let glowBars=[],nodeMeshes=[],capMeshes=[],glassNodes=[],centralS,centralI;
381 // Flux visualization storage
382 let barEndpoints=[]; // {from, to, dir, len} per edge
383 let fluxParticles=[]; // [edge][particle] = {mesh, u}
384 let fieldLineLoops=[]; // [edge][loop] = mesh
385 let vortexLines=[]; // central vortex spiral meshes
386 const PARTICLES_PER_BAR=7;
387 const LOOPS_PER_BAR=3;
388
389 function mkCyl(f,t,th,mat){
390 const d=new THREE.Vector3().subVectors(t,f),l=d.length(),
391 m=new THREE.Vector3().addVectors(f,t).multiplyScalar(.5),
392 g=new THREE.CylinderGeometry(th,th,l,12),mesh=new THREE.Mesh(g,mat);
393 mesh.position.copy(m);mesh.quaternion.setFromUnitVectors(new THREE.Vector3(0,1,0),d.normalize());
394 return mesh;
395 }
396
397 function mkCoils(f,t,th,dens){
398 const d=new THREE.Vector3().subVectors(t,f),l=d.length(),dn=d.clone().normalize(),
399 cR=th*2.5,turns=Math.round(dens*l/G.R),steps=Math.max(16,turns*16),pts=[];
400 for(let i=0;i<=steps;i++){
401 const u=i/steps,a=u*turns*Math.PI*2;
402 pts.push(new THREE.Vector3(Math.cos(a)*cR,(u-.5)*l*.85,Math.sin(a)*cR));
403 }
404 const curve=new THREE.CatmullRomCurve3(pts),
405 g=new THREE.TubeGeometry(curve,Math.min(steps*2,512),th*.35,6,false),
406 mesh=new THREE.Mesh(g,G.wire?copMW:copM),
407 m=new THREE.Vector3().addVectors(f,t).multiplyScalar(.5);
408 mesh.position.copy(m);mesh.quaternion.setFromUnitVectors(new THREE.Vector3(0,1,0),dn);
409 return mesh;
410 }
411
412 function buildDevice(){
413 while(devG.children.length)devG.remove(devG.children[0]);
414 glowBars=[];nodeMeshes=[];capMeshes=[];glassNodes=[];
415 baseCoupling=computeBaseCoupling();
416
417 const R=G.R,V=G.V,T=G.T;
418 const bG=new THREE.Group(),cG=new THREE.Group(),nG=new THREE.Group(),
419 casG=new THREE.Group(),cenG=new THREE.Group();
420
421 // === JUNCTION GEOMETRY ===
422 // Each node has two junction points where bars physically meet:
423 // Upper: shēng incoming arrives + kè outgoing departs
424 // Lower: shēng outgoing departs + kè incoming arrives
425 const jH=V*.4;
426 function jPos(ni,upper){return new THREE.Vector3(Math.cos(P[ni].a)*R,upper?jH:-jH,Math.sin(P[ni].a)*R)}
427
428 const capUnitMat=new THREE.MeshStandardMaterial({color:0x1a1a2a,metalness:.5,roughness:.5});
429 const capUnitSize=T*1.0;
430
431 function fibPolyVerts(n){
432 const s=capUnitSize*2.5;
433 if(n===1)return[new THREE.Vector3(0,0,0)];
434 if(n===2)return[new THREE.Vector3(-s*.5,0,0),new THREE.Vector3(s*.5,0,0)];
435 if(n===3){const v=[];for(let k=0;k<3;k++){const a=k*Math.PI*2/3-Math.PI/2;v.push(new THREE.Vector3(Math.cos(a)*s*.6,0,Math.sin(a)*s*.6))}return v}
436 if(n===5){const v=[];for(let k=0;k<5;k++){const a=k*Math.PI*2/5-Math.PI/2;v.push(new THREE.Vector3(Math.cos(a)*s*.8,0,Math.sin(a)*s*.8))}return v}
437 if(n===8){const h=s*.55,v=[];for(let x=-1;x<=1;x+=2)for(let y=-1;y<=1;y+=2)for(let z=-1;z<=1;z+=2)v.push(new THREE.Vector3(x*h,y*h,z*h));return v}
438 return[new THREE.Vector3(0,0,0)];
439 }
440 function fibPolyEdges(n,verts){
441 const pts=[];
442 if(n===2){pts.push(verts[0],verts[1])}
443 else if(n===3){for(let k=0;k<3;k++){pts.push(verts[k],verts[(k+1)%3])}}
444 else if(n===5){for(let k=0;k<5;k++){pts.push(verts[k],verts[(k+1)%5])}}
445 else if(n===8){for(let a=0;a<8;a++)for(let b=a+1;b<8;b++){const d=verts[a].clone().sub(verts[b]);if((d.x!==0?1:0)+(d.y!==0?1:0)+(d.z!==0?1:0)===1)pts.push(verts[a],verts[b])}}
446 return pts;
447 }
448
449 const nodeR=T*2.8;
450 for(let i=0;i<5;i++){
451 const posUp=jPos(i,true),posLo=jPos(i,false);
452 const posMid=new THREE.Vector3().addVectors(posUp,posLo).multiplyScalar(.5);
453
454 // Upper junction sphere (core metal)
455 const sUp=new THREE.Mesh(new THREE.SphereGeometry(nodeR,16,16),G.wire?coreMW:coreM);
456 sUp.position.copy(posUp);nG.add(sUp);
457 // Lower junction sphere (core metal)
458 const sLo=new THREE.Mesh(new THREE.SphereGeometry(nodeR,16,16),G.wire?coreMW:coreM);
459 sLo.position.copy(posLo);nG.add(sLo);
460 nodeMeshes.push(sUp);
461
462 // Vertical core bar connecting upper to lower
463 const vLen=jH*2-nodeR*2;
464 if(vLen>0){
465 const vb=mkCyl(new THREE.Vector3(posUp.x,jH-nodeR,posUp.z),new THREE.Vector3(posLo.x,-jH+nodeR,posLo.z),T*.8,G.wire?coreMW:coreM);
466 nG.add(vb);
467 }
468
469 // Glass sheaths
470 const glassR=nodeR*1.5;
471 const gMatUp=new THREE.MeshPhysicalMaterial({color:P[i].col,transparent:true,opacity:.06,roughness:.05,metalness:0,transmission:.88,side:2});
472 const gMatLo=new THREE.MeshPhysicalMaterial({color:P[i].col,transparent:true,opacity:.06,roughness:.05,metalness:0,transmission:.88,side:2});
473 const glUp=new THREE.Mesh(new THREE.SphereGeometry(glassR,16,16),gMatUp);
474 glUp.position.copy(posUp);nG.add(glUp);
475 const glLo=new THREE.Mesh(new THREE.SphereGeometry(glassR,16,16),gMatLo);
476 glLo.position.copy(posLo);nG.add(glLo);
477 glassNodes.push({matUp:gMatUp,matLo:gMatLo});
478
479 // Fibonacci cap polyhedron — between junction balls
480 const outD=posMid.clone().normalize();
481 const polyPos=posMid.clone().add(outD.multiplyScalar(T*3));
482 const capGroup=new THREE.Group();
483 capGroup.position.copy(polyPos);
484 const verts=fibPolyVerts(P[i].cap);
485 for(let v=0;v<verts.length;v++){
486 const cu=new THREE.Mesh(new THREE.CylinderGeometry(capUnitSize*.7,capUnitSize*.7,capUnitSize*1.4,8),capUnitMat.clone());
487 cu.position.copy(verts[v]);capGroup.add(cu);
488 const dot=new THREE.Mesh(new THREE.SphereGeometry(capUnitSize*.25,4,4),new THREE.MeshBasicMaterial({color:0xcc3333}));
489 dot.position.copy(verts[v]);dot.position.y+=capUnitSize*.8;
490 capGroup.add(dot);
491 }
492 const edgePts=fibPolyEdges(P[i].cap,verts);
493 if(edgePts.length>0)capGroup.add(new THREE.LineSegments(new THREE.BufferGeometry().setFromPoints(edgePts),new THREE.LineBasicMaterial({color:P[i].col,transparent:true,opacity:.4})));
494 nG.add(capGroup);
495 capMeshes.push(capGroup);
496 }
497
498 // Bars — all terminate at junction spheres
499 // Shēng (i→i+1): lower(i) → upper(i+1)
500 // Kè (i→i+2): upper(i) → lower(i+2)
501 for(let e=0;e<10;e++){
502 const[a,b]=eNodes(e);
503 let f,t;
504 if(e<5){f=jPos(a,false);t=jPos(b,true)}
505 else{f=jPos(a,true);t=jPos(b,false)}
506 const isKe=e>=5;
507 const thick=isKe?T*.85:T;
508 bG.add(mkCyl(f,t,thick,G.wire?coreMW:coreM));
509 cG.add(mkCoils(f,t,thick,G.D));
510 const gm=edgeGMs[e];gm.opacity=0;
511 const gThick=isKe?thick*2.5:thick*2;
512 const d=new THREE.Vector3().subVectors(t,f),l=d.length(),
513 mid=new THREE.Vector3().addVectors(f,t).multiplyScalar(.5),
514 gg=new THREE.CylinderGeometry(gThick,gThick,l,8),
515 gb=new THREE.Mesh(gg,gm);
516 gb.position.copy(mid);gb.quaternion.setFromUnitVectors(new THREE.Vector3(0,1,0),d.normalize());
517 glowBars.push(gb);bG.add(gb);
518 barEndpoints[e]={from:f.clone(),to:t.clone(),dir:d.clone().normalize(),len:l};
519 }
520
521 // === FLUX PARTICLES along each bar ===
522 const fluxG=new THREE.Group();
523 fluxParticles=[];
524 for(let e=0;e<10;e++){
525 const bp=barEndpoints[e];
526 const isKe=e>=5;
527 const col=P[eNodes(e)[0]].col;
528 const pSize=isKe?G.T*.6:G.T*.45;
529 fluxParticles[e]=[];
530 for(let p=0;p<PARTICLES_PER_BAR;p++){
531 const mat=new THREE.MeshBasicMaterial({color:col,transparent:true,opacity:0});
532 const mesh=new THREE.Mesh(new THREE.SphereGeometry(pSize,4,4),mat);
533 const u=p/PARTICLES_PER_BAR;
534 mesh.position.lerpVectors(bp.from,bp.to,u);
535 fluxG.add(mesh);
536 fluxParticles[e].push({mesh,mat,u});
537 }
538 }
539
540 // === FIELD LINE LOOPS around each bar (B-field visualization) ===
541 fieldLineLoops=[];
542 for(let e=0;e<10;e++){
543 const bp=barEndpoints[e];
544 const isKe=e>=5;
545 const col=new THREE.Color(P[eNodes(e)[0]].col);
546 fieldLineLoops[e]=[];
547 for(let li=0;li<LOOPS_PER_BAR;li++){
548 const u=(li+1)/(LOOPS_PER_BAR+1); // position along bar
549 const center=new THREE.Vector3().lerpVectors(bp.from,bp.to,u);
550 // Build elliptical loop in plane perpendicular to bar direction
551 const loopPts=[];
552 const loopR=isKe?G.R*.18:G.R*.15;
553 const loopH=loopR*.6; // elliptical: taller along bar
554 // Need a perpendicular basis
555 const up=bp.dir;
556 let perp1=new THREE.Vector3(1,0,0);
557 if(Math.abs(up.dot(perp1))>.9)perp1.set(0,1,0);
558 perp1.crossVectors(up,perp1).normalize();
559 const perp2=new THREE.Vector3().crossVectors(up,perp1).normalize();
560 const segments=24;
561 for(let s=0;s<=segments;s++){
562 const a=s/segments*Math.PI*2;
563 const r=loopR*(1+.15*Math.sin(a*2)); // slight figure-8 distortion
564 const pt=center.clone()
565 .add(perp1.clone().multiplyScalar(Math.cos(a)*r))
566 .add(perp2.clone().multiplyScalar(Math.sin(a)*r))
567 .add(up.clone().multiplyScalar(Math.sin(a)*loopH));
568 loopPts.push(pt);
569 }
570 const loopGeo=new THREE.BufferGeometry().setFromPoints(loopPts);
571 const loopMat=new THREE.LineBasicMaterial({color:col,transparent:true,opacity:0});
572 const loopMesh=new THREE.Line(loopGeo,loopMat);
573 fluxG.add(loopMesh);
574 fieldLineLoops[e].push({mesh:loopMesh,mat:loopMat});
575 }
576 }
577
578 // === CENTRAL VORTEX FIELD LINES ===
579 vortexLines=[];
580 // Two counter-rotating spirals: kè (inward, star pattern) and shēng (inward, pentagon)
581 for(let v=0;v<2;v++){
582 const isKe=v===0;
583 const col=isKe?0xff4466:0x6644ff;
584 const spiralPts=[];
585 const turns=2.5;
586 const rMax=G.R*.5;
587 const rMin=G.R*.05;
588 const hMax=isKe?G.V*.4:-G.V*.4;
589 const dir=isKe?1:-1;
590 const steps=80;
591 for(let s=0;s<=steps;s++){
592 const u=s/steps;
593 const a=u*turns*Math.PI*2*dir;
594 const r=rMax*(1-u)+rMin*u;
595 const y=hMax*u*(1-u)*4; // parabolic height profile
596 spiralPts.push(new THREE.Vector3(Math.cos(a)*r,y,Math.sin(a)*r));
597 }
598 const curve=new THREE.CatmullRomCurve3(spiralPts);
599 const tubeGeo=new THREE.TubeGeometry(curve,steps,G.T*.2,4,false);
600 const tubeMat=new THREE.MeshBasicMaterial({color:col,transparent:true,opacity:0});
601 const tubeMesh=new THREE.Mesh(tubeGeo,tubeMat);
602 fluxG.add(tubeMesh);
603 vortexLines.push({mesh:tubeMesh,mat:tubeMat,isKe,baseAngle:0});
604 // Second arm offset by π
605 const spiralPts2=spiralPts.map(p=>{
606 const p2=p.clone();p2.x=-p2.x;p2.z=-p2.z;return p2;
607 });
608 const curve2=new THREE.CatmullRomCurve3(spiralPts2);
609 const tubeGeo2=new THREE.TubeGeometry(curve2,steps,G.T*.2,4,false);
610 const tubeMat2=new THREE.MeshBasicMaterial({color:col,transparent:true,opacity:0});
611 const tubeMesh2=new THREE.Mesh(tubeGeo2,tubeMat2);
612 fluxG.add(tubeMesh2);
613 vortexLines.push({mesh:tubeMesh2,mat:tubeMat2,isKe,baseAngle:Math.PI});
614 }
615
616 // Central — star-shaped for kè dominance
617 centralS=new THREE.Mesh(new THREE.SphereGeometry(R*.2,32,32),cntM);
618 cenG.add(centralS);
619 // Icosahedron rotates with kè rotor
620 centralI=new THREE.Mesh(new THREE.IcosahedronGeometry(R*.3,1),cntM2);
621 cenG.add(centralI);
622
623 // Pentagram wireframe at center (the star pattern)
624 const starPts=[];
625 for(let i=0;i<5;i++){
626 const j=(i+2)%5;
627 const a1=P[i].a,a2=P[j].a,sr=R*.45;
628 starPts.push(new THREE.Vector3(Math.cos(a1)*sr,0,Math.sin(a1)*sr));
629 starPts.push(new THREE.Vector3(Math.cos(a2)*sr,0,Math.sin(a2)*sr));
630 }
631 const starGeo=new THREE.BufferGeometry().setFromPoints(starPts);
632 cenG.add(new THREE.LineSegments(starGeo,new THREE.LineBasicMaterial({color:0x442222,transparent:true,opacity:.3})));
633
634 // Fractal cascade
635 if(G.frac>0){
636 for(let lv=0;lv<G.frac;lv++){
637 const sc=1/Math.pow(PHI,2*lv),r=R*.65*sc,rot=lv*Math.PI/5,yy=(lv%2?.04:-.04)*lv;
638 const lm=new THREE.LineBasicMaterial({
639 color:new THREE.Color().setHSL(.08+lv*.06,.7,.55),
640 transparent:true,opacity:.4-lv*.06
641 });
642 const pp=[];
643 for(let i=0;i<=5;i++){const aa=rot+(i%5)*2*Math.PI/5-Math.PI/2;pp.push(new THREE.Vector3(Math.cos(aa)*r,yy,Math.sin(aa)*r))}
644 casG.add(new THREE.Line(new THREE.BufferGeometry().setFromPoints(pp),lm));
645 for(let i=0;i<5;i++){
646 const j=(i+2)%5,a1=rot+i*2*Math.PI/5-Math.PI/2,a2=rot+j*2*Math.PI/5-Math.PI/2;
647 casG.add(new THREE.Line(new THREE.BufferGeometry().setFromPoints([
648 new THREE.Vector3(Math.cos(a1)*r,yy,Math.sin(a1)*r),
649 new THREE.Vector3(Math.cos(a2)*r,yy,Math.sin(a2)*r)
650 ]),lm.clone()));
651 }
652 }
653 }
654
655 if(G.vac){
656 cenG.add(new THREE.Mesh(new THREE.SphereGeometry(R*1.5,48,48),
657 new THREE.MeshPhysicalMaterial({color:0x222233,transparent:true,opacity:.07,roughness:.1,side:2,transmission:.9})));
658 const vr=new THREE.Mesh(new THREE.TorusGeometry(R*1.5,T*.8,8,64),G.wire?coreMW:coreM);
659 vr.rotation.x=Math.PI/2;cenG.add(vr);
660 }
661
662 devG.add(bG);devG.add(cG);devG.add(nG);devG.add(fluxG);devG.add(casG);devG.add(cenG);
663 }
664
665 buildDevice();
666
667 // UI: cap bars
668 function buildCapUI(){
669 const el=document.getElementById('node-caps');let h='';
670 for(let i=0;i<5;i++){
671 h+=`<div class="nb"><div class="icon" style="background:${P[i].css}">${P[i].c}</div>
672 <div class="nm">C=${P[i].cap}</div>
673 <div class="bg"><div class="fl" id="cf${i}" style="background:${P[i].css};width:0"></div>
674 <div class="th" style="left:${THRESHOLD*100}%"></div></div>
675 <div class="vl" id="cv${i}">0</div></div>`;
676 }
677 el.innerHTML=h;
678 }
679 buildCapUI();
680
681 // Build remnant field bars
682 function buildRemUI(){
683 const el=document.getElementById('rem-bars');let h='';
684 for(let i=0;i<5;i++){
685 h+=`<div class="cb"><div class="cl" style="color:${P[i].css};font-size:7px">${P[i].c} Bᵣ</div>
686 <div class="bg"><div class="fl" id="rem${i}" style="background:${P[i].css};width:0;opacity:.6"></div></div>
687 <div class="vl" id="remV${i}" style="font-size:7px">0</div></div>`;
688 }
689 el.innerHTML=h;
690 }
691 buildRemUI();
692
693 // Orbit
694 let drag=false,pm={x:0,y:0},sph={t:Math.PI/4,p:Math.PI/3,r:14};
695 function updCam(){
696 camera.position.set(sph.r*Math.sin(sph.p)*Math.cos(sph.t),sph.r*Math.cos(sph.p),sph.r*Math.sin(sph.p)*Math.sin(sph.t));
697 camera.lookAt(0,0,0);
698 }
699 updCam();
700 canvas.onmousedown=e=>{drag=true;pm={x:e.clientX,y:e.clientY}};
701 canvas.onmousemove=e=>{if(!drag)return;sph.t-=(e.clientX-pm.x)*.005;sph.p=Math.max(.2,Math.min(Math.PI-.2,sph.p+(e.clientY-pm.y)*.005));pm={x:e.clientX,y:e.clientY};updCam()};
702 canvas.onmouseup=canvas.onmouseleave=()=>drag=false;
703 canvas.onwheel=e=>{sph.r=Math.max(4,Math.min(30,sph.r+e.deltaY*.01));updCam()};
704 canvas.ontouchstart=e=>{if(e.touches.length===1){drag=true;pm={x:e.touches[0].clientX,y:e.touches[0].clientY}}};
705 canvas.ontouchmove=e=>{if(!drag||e.touches.length!==1)return;sph.t-=(e.touches[0].clientX-pm.x)*.005;sph.p=Math.max(.2,Math.min(Math.PI-.2,sph.p+(e.touches[0].clientY-pm.y)*.005));pm={x:e.touches[0].clientX,y:e.touches[0].clientY};updCam()};
706 canvas.ontouchend=()=>drag=false;
707
708 // Rotor canvas
709 const rc=document.getElementById('rc'),rx=rc.getContext('2d');
710 function drawRotor(){
711 rx.clearRect(0,0,130,130);
712 const cx=65,cy=65,r=48;
713 // Pentagram (kè path) — this is the rotor pattern
714 rx.strokeStyle='#331111';rx.lineWidth=1;rx.beginPath();
715 for(let i=0;i<5;i++){
716 const ki=KE_ORDER[i],kj=KE_ORDER[(i+1)%5];
717 const a1=P[ki].a-Math.PI/2,a2=P[kj].a-Math.PI/2;
718 rx.moveTo(cx+Math.cos(a1)*r,cy+Math.sin(a1)*r);
719 rx.lineTo(cx+Math.cos(a2)*r,cy+Math.sin(a2)*r);
720 }
721 rx.stroke();
722 // Pentagon (shēng) faint
723 rx.strokeStyle='#181818';rx.beginPath();
724 for(let i=0;i<=5;i++){const a=P[i%5].a-Math.PI/2;rx.lineTo(cx+Math.cos(a)*r,cy+Math.sin(a)*r)}
725 rx.stroke();
726 // Nodes
727 for(let i=0;i<5;i++){
728 const a=P[i].a-Math.PI/2,x=cx+Math.cos(a)*r,y=cy+Math.sin(a)*r;
729 const fi=sim.fireIntensity[i];
730 rx.fillStyle=P[i].css;rx.globalAlpha=.25+fi*.75;
731 rx.beginPath();rx.arc(x,y,3+fi*7+P[i].cap,0,Math.PI*2);rx.fill();
732 rx.globalAlpha=1;
733 // Cap value
734 rx.fillStyle='#888';rx.font='8px JetBrains Mono';rx.textAlign='center';
735 rx.fillText(P[i].cap,x,y+14+P[i].cap);
736 }
737 // Rotor arrow — follows kè cycle
738 if(sim.rotorSpeed>0.01){
739 const ra=sim.rotorAngle;
740 rx.strokeStyle='#e8d5a3';rx.lineWidth=2;
741 rx.globalAlpha=Math.min(1,sim.rotorSpeed);
742 rx.beginPath();rx.moveTo(cx,cy);
743 rx.lineTo(cx+Math.cos(ra)*35,cy+Math.sin(ra)*35);rx.stroke();
744 const hx=cx+Math.cos(ra)*35,hy=cy+Math.sin(ra)*35;
745 rx.beginPath();rx.moveTo(hx,hy);
746 rx.lineTo(hx+Math.cos(ra+2.7)*8,hy+Math.sin(ra+2.7)*8);
747 rx.lineTo(hx+Math.cos(ra-2.7)*8,hy+Math.sin(ra-2.7)*8);
748 rx.closePath();rx.fillStyle='#e8d5a3';rx.fill();
749 rx.globalAlpha=1;
750 }
751 // Center: flux indicator
752 if(sim.flux>0.05){
753 rx.fillStyle='#8844ff';rx.globalAlpha=sim.flux*.6;
754 rx.beginPath();rx.arc(cx,cy,5+sim.flux*15,0,Math.PI*2);rx.fill();
755 rx.globalAlpha=1;
756 }
757 }
758
759 // Sliders
760 const SL=[
761 ['sR','R','vR',v=>v.toFixed(1),true],['sV','V','vV',v=>v.toFixed(1),true],
762 ['sT','T','vT',v=>v.toFixed(2),true],['sD','D','vD',v=>''+v,true],
763 ['sMu','mu','vMu',v=>''+v,true],['sSeed','seed','vSeed',v=>P[v].c,false],
764 ['sK','k','vK',v=>v.toFixed(2),false],['sDamp','damp','vDamp',v=>v.toFixed(3),false],
765 ['sPair','pair','vPair',v=>v.toFixed(2),false],['sLoad','load','vLoad',v=>v.toFixed(2),false],
766 ['sDim','dim','vDim',v=>v.toFixed(2),false],
767 ['sGlow','glow','vGlow',v=>v.toFixed(1),false],['sFrac','frac','vFrac',v=>''+v,true],
768 ];
769 SL.forEach(([id,key,vid,fmt,reb])=>{
770 document.getElementById(id).oninput=function(){
771 G[key]=parseFloat(this.value);
772 document.getElementById(vid).textContent=fmt(G[key]);
773 if(key==='mu'){
774 document.getElementById('muDisp').textContent=G.mu;
775 document.getElementById('steadyI').textContent=SUM_C*G.mu;
776 }
777 if(reb){buildDevice();baseCoupling=computeBaseCoupling()}
778 if(key==='k')baseCoupling=computeBaseCoupling();
779 };
780 });
781
782 document.getElementById('btnMag').onclick=()=>{
783 const seed=G.seed;
784 sim.charge=[0,0,0,0,0];
785 // A magnet waved past the device sweeps the entire core.
786 // Every node gets induced charge — strongest at the seed (closest pass),
787 // falling off with angular distance around the pentagon.
788 // The pulse is strong: rare earth magnet, close pass, high dB/dt.
789 const basePulse=3.5; // enough to bootstrap the cascade
790 for(let i=0;i<5;i++){
791 // Angular distance from seed node (0 to 2 steps away on pentagon)
792 const steps=Math.min(Math.abs(i-seed),5-Math.abs(i-seed));
793 // Falloff: ~1.0 at seed, ~0.5 at adjacent, ~0.25 at opposite
794 const proximity=1.0/Math.pow(2,steps);
795 sim.charge[i]=basePulse*proximity;
796 }
797 sim.running=true;sim.fireHistory=[];sim.totalFirings=0;
798 sim.rotorAngle=P[seed].a;sim.rotorSpeed=0;
799 sim.resonance=[0,0,0];sim.pairFeedback=0;sim.refractory=[0,0,0,0,0];
800 document.getElementById('rotor-label').textContent='magnet sweep from '+P[seed].c+' — cascade starting';
801 document.getElementById('fire-seq').innerHTML='';
802 };
803 document.getElementById('btnReset').onclick=()=>{
804 sim.charge=[0,0,0,0,0];sim.fireIntensity=[0,0,0,0,0];
805 sim.edgeGlow=new Float64Array(10);sim.flux=0;sim.running=false;
806 sim.rotorSpeed=0;sim.fireHistory=[];sim.totalFirings=0;sim.resonance=[0,0,0];sim.pairFeedback=0;sim.refractory=[0,0,0,0,0];
807 sim.barMag=new Float64Array(10);sim.remnant=new Float64Array(5);sim.coreSaturation=0;
808 document.getElementById('rotor-label').textContent='awaiting ignition';
809 document.getElementById('fire-seq').innerHTML='';
810 };
811 document.getElementById('btnVac').onclick=function(){G.vac=!G.vac;this.classList.toggle('active');buildDevice()};
812 document.getElementById('btnFlux').onclick=function(){G.showFlux=!G.showFlux;this.classList.toggle('active')};
813 document.getElementById('btnWire').onclick=function(){G.wire=!G.wire;this.classList.toggle('active');buildDevice()};
814
815 let lastT=0;
816 function animate(t){
817 requestAnimationFrame(animate);
818 const dt=Math.min(.05,(t-lastT)/1000);lastT=t;
819 for(let s=0;s<10;s++)simStep(dt/10);
820
821 const glow=G.glow;
822 for(let i=0;i<5;i++){
823 // Glass sheath glow on fire
824 if(glassNodes[i]){
825 const fi=sim.fireIntensity[i];
826 glassNodes[i].matUp.opacity=.06+fi*.45*glow;
827 glassNodes[i].matLo.opacity=.06+fi*.45*glow;
828 }
829 if(capMeshes[i]){
830 const norm=sim.charge[i]/P[i].cap;
831 capMeshes[i].traverse(ch=>{
832 if(ch.isMesh&&ch.material&&ch.material.emissive){
833 ch.material.emissive=new THREE.Color(P[i].col);
834 ch.material.emissiveIntensity=norm*.6*glow;
835 }
836 });
837 }
838 }
839 for(let e=0;e<10;e++){edgeGMs[e].opacity=sim.edgeGlow[e]*.1*glow}
840
841 // === FLUX PARTICLES: travel along bars when active ===
842 const showF=G.showFlux;
843 for(let e=0;e<10;e++){
844 const intensity=sim.edgeGlow[e];
845 const mag=sim.barMag[e]||0; // remanent magnetization
846 const vis=Math.max(intensity,mag*0.5); // visible from either active glow or residual mag
847 const bp=barEndpoints[e];
848 if(!bp)continue;
849 const speed=(vis*3+sim.rotorSpeed*0.3)*dt;
850 for(let p=0;p<fluxParticles[e].length;p++){
851 const fp=fluxParticles[e][p];
852 fp.u+=speed;
853 if(fp.u>1)fp.u-=1;
854 fp.mesh.position.lerpVectors(bp.from,bp.to,fp.u);
855 fp.mat.opacity=showF?vis*0.7*glow:0;
856 const sc=0.5+vis*0.7;
857 fp.mesh.scale.setScalar(sc);
858 }
859 }
860
861 // === FIELD LINE LOOPS: show B-field around active bars ===
862 for(let e=0;e<10;e++){
863 const intensity=sim.edgeGlow[e];
864 const mag=sim.barMag[e]||0;
865 const vis=Math.max(intensity,mag*0.4);
866 for(let li=0;li<fieldLineLoops[e].length;li++){
867 fieldLineLoops[e][li].mat.opacity=showF?vis*0.3*glow:0;
868 }
869 }
870
871 // === CENTRAL VORTEX: rotate and show when flux is present ===
872 for(const vl of vortexLines){
873 vl.mat.opacity=showF?sim.flux*0.6*glow:0;
874 vl.mesh.rotation.y+=sim.rotorSpeed*dt*(vl.isKe?1:-0.618);
875 }
876
877 const fl=sim.flux;
878 cntM.opacity=fl*.4*glow;
879 cntM2.opacity=fl*.35*glow;
880 if(centralS){centralS.scale.setScalar(1+fl*.25)}
881 if(centralI){
882 centralI.rotation.y+=sim.rotorSpeed*.025;
883 centralI.rotation.x+=sim.rotorSpeed*.015;
884 }
885 ctrL.intensity=fl*2.5*glow;
886 ctrL.color.setHSL(.75+Math.sin(t*.001)*.1,.6,.5);
887 keL.intensity=sim.rotorSpeed*.8*glow;
888
889 if(!drag){sph.t+=.0008;updCam()}
890
891 // UI
892 for(let i=0;i<5;i++){
893 const norm=Math.min(1.5,sim.charge[i]/P[i].cap);
894 const bar=document.getElementById('cf'+i),val=document.getElementById('cv'+i);
895 if(bar)bar.style.width=(norm/1.5*100)+'%';
896 if(val)val.textContent=norm.toFixed(2);
897 }
898 document.getElementById('fluxBar').style.width=Math.min(100,fl*120)+'%';
899 document.getElementById('fluxV').textContent=fl.toFixed(2);
900 document.getElementById('pairBar').style.width=Math.min(100,sim.pairFeedback*500)+'%';
901 document.getElementById('pairV').textContent=sim.pairFeedback.toFixed(3);
902
903 // Hysteresis display
904 document.getElementById('satBar').style.width=(sim.coreSaturation*100)+'%';
905 document.getElementById('satV').textContent=sim.coreSaturation.toFixed(2);
906 for(let i=0;i<5;i++){
907 const rb=document.getElementById('rem'+i);
908 const rv=document.getElementById('remV'+i);
909 if(rb)rb.style.width=(sim.remnant[i]/0.3*100)+'%';
910 if(rv)rv.textContent=sim.remnant[i].toFixed(3);
911 }
912 // Hysteresis state label
913 const hs=document.getElementById('hystState');
914 if(sim.coreSaturation>0.4)hs.textContent='⚡ locked in — cannot stop';
915 else if(sim.coreSaturation>0.15)hs.textContent='🔄 building — approaching lock-in';
916 else if(sim.coreSaturation>0.01)hs.textContent='⏳ residual magnetization';
917 else hs.textContent='dormant';
918
919 // Resonances
920 for(let r=0;r<3;r++){
921 const el=document.getElementById('res'+r);
922 if(el)el.style.width=Math.min(100,sim.resonance[r]*100)+'%';
923 }
924
925 // Fire sequence
926 if(sim.fireHistory.length>0){
927 const el=document.getElementById('fire-seq');
928 let h='';
929 sim.fireHistory.slice(-30).forEach(f=>{h+=`<b style="color:${P[f.node].css}">${P[f.node].c}</b> `});
930 el.innerHTML=h;
931 if(sim.rotorSpeed>0.05){
932 const rpm=(sim.rotorSpeed/(2*Math.PI)*60).toFixed(0);
933 const state=sim.pairFeedback>0.01?'⚡ self-sustaining':'⏳ decaying';
934 document.getElementById('rotor-label').textContent=
935 '克 '+rpm+' rpm · '+state+' · E='+sim.totalEnergy.toFixed(2);
936 } else if(sim.totalFirings>0&&!sim.running){
937 document.getElementById('rotor-label').textContent='stopped · '+sim.totalFirings+' fires total';
938 }
939 }
940
941 drawRotor();
942 renderer.render(scene,camera);
943 }
944 animate(0);
945 window.onresize=()=>{camera.aspect=innerWidth/innerHeight;camera.updateProjectionMatrix();renderer.setSize(innerWidth,innerHeight)};
946 </script>
947 </body>
948 </html>
949