Files
aibis-dream/step2_14.html
2025-12-04 20:38:02 +08:00

879 lines
31 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>情绪数据析出终端 V10 - 终极形态</title>
<style>
:root {
--c-main: #00ffcc;
--c-dim: #004433;
--c-alert: #ff3333;
--c-pure: #ffffff;
--bg: #050505;
}
body {
background-color: #000;
color: var(--c-main);
font-family: 'Courier New', monospace;
margin: 0;
height: 100vh;
overflow: hidden;
display: grid;
grid-template-columns: 1fr 380px;
grid-template-rows: 1fr 180px;
user-select: none;
}
/* 1. 主池 */
#tank-area {
grid-column: 1 / 2; grid-row: 1 / 2;
position: relative;
background: radial-gradient(circle at 50% 50%, #0a1a1a 0%, #000 90%);
border-right: 1px solid #333;
border-bottom: 1px solid #333;
overflow: hidden;
cursor: crosshair;
transition: background 1s;
}
#tank-area.purified {
background: radial-gradient(circle at 50% 50%, #0a2a2a 0%, #000 90%);
}
#tank-area.sampling {
cursor: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="%23ff3333" stroke-width="2"><circle cx="12" cy="12" r="8"/><line x1="12" y1="4" x2="12" y2="20"/><line x1="4" y1="12" x2="20" y2="12"/></svg>') 12 12, crosshair;
}
/* 2. 右侧面板 */
#scope-panel {
grid-column: 2 / 3; grid-row: 1 / 2;
background: #080808; border-bottom: 1px solid #333;
display: flex; flex-direction: column;
padding: 15px; gap: 15px;
box-shadow: inset 10px 0 20px rgba(0,0,0,0.5);
overflow-y: auto;
}
.panel-group {
display: flex; flex-direction: column; gap: 5px;
background: #020202; border: 1px solid #222; padding: 10px;
}
.label { font-size: 10px; color: #666; letter-spacing: 1px; font-weight: bold; margin-bottom: 5px; }
/* A. 示波器 */
#wave-monitor {
height: 100px; background: #000500;
border: 1px solid #333;
position: relative;
overflow: hidden;
transition: all 0.5s;
}
/* 调准后的发光效果 */
#wave-monitor.locked {
border-color: var(--c-main);
box-shadow: 0 0 20px rgba(0, 255, 204, 0.15);
background: #001a15;
}
.scope-grid {
position: absolute; top:0; left:0; width:100%; height:100%;
background-image:
linear-gradient(rgba(0, 255, 204, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 255, 204, 0.05) 1px, transparent 1px);
background-size: 20px 20px;
pointer-events: none;
}
#waveCanvas { width: 100%; height: 100%; position: relative; z-index: 1; }
/* B. 参数数值 */
.data-row {
display: flex; justify-content: space-between;
font-size: 12px; border-bottom: 1px solid #111; padding: 4px 0;
color: #555;
}
.data-row.matched {
color: var(--c-main);
text-shadow: 0 0 8px rgba(0, 255, 204, 0.4);
}
/* C. 缓冲区 (进度条) */
.buffer-track {
height: 10px; background: #111; border: 1px solid #444;
margin-top: 5px; position: relative;
}
.buffer-fill {
height: 100%; width: 0%; background: var(--c-alert);
transition: width 0.2s;
box-shadow: 0 0 8px var(--c-alert);
}
.buffer-text {
text-align: right; font-size: 10px; color: #666; margin-top: 4px;
}
/* D. 分析图容器 */
#analysis-wrapper {
flex: 1; display: flex; flex-direction: column; gap: 10px;
display: none; /* 初始隐藏 */
}
#analysis-container {
height: 200px;
border: 1px solid #333;
background: #000;
position: relative;
}
.axis-txt {
position: absolute; font-size: 9px; color: #444; pointer-events: none;
}
/* E. 药剂控制区 */
#reagent-controls {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr;
gap: 5px;
height: 80px;
}
.reagent-btn {
background: #111; border: 1px solid #333;
color: #666; font-family: inherit; font-size: 10px;
cursor: pointer; transition: 0.2s;
display: flex; flex-direction: column; align-items: center; justify-content: center;
}
.reagent-btn:hover { border-color: #666; color: #fff; }
.reagent-btn:active { background: #222; }
/* 布局: 上下左右 */
#btn-up { grid-column: 2; grid-row: 1; }
#btn-left { grid-column: 1; grid-row: 2; }
#btn-right { grid-column: 3; grid-row: 2; }
#btn-down { grid-column: 2; grid-row: 2; }
.chem-icon { width: 8px; height: 12px; border: 1px solid #555; margin-bottom: 2px; position: relative; }
.chem-fill { position: absolute; bottom: 0; width: 100%; height: 50%; background: #444; transition: 0.3s; }
.reagent-btn:hover .chem-fill { height: 80%; background: var(--c-main); box-shadow: 0 0 5px var(--c-main); }
#btn-inject {
width: 100%; height: 40px;
margin-top: 10px;
background: #111; border: 1px solid #333; color: #444;
font-weight: bold; letter-spacing: 2px;
cursor: not-allowed; display: none;
}
#btn-inject.ready {
background: var(--c-main); color: #000; border-color: var(--c-main);
box-shadow: 0 0 15px var(--c-main); cursor: pointer;
animation: pulseInject 1s infinite;
}
@keyframes pulseInject { 50% { box-shadow: 0 0 25px var(--c-main); } }
/* 3. 底部控制 */
#control-panel {
grid-column: 1 / 3; grid-row: 2 / 3;
background: #0c0c0c; border-top: 1px solid #333;
display: flex; align-items: center; padding: 0 40px; gap: 40px;
}
.slider-box { flex: 1; }
.slider-head { display: flex; justify-content: space-between; margin-bottom: 8px; font-size: 10px; color: #888; }
input[type=range] { width: 100%; cursor: pointer; }
#btn-main {
width: 160px; height: 60px;
background: #111; border: 2px solid #333; color: #555;
font-family: inherit; font-weight: bold; letter-spacing: 2px;
transition: 0.3s; cursor: not-allowed;
}
#btn-main.ready {
border-color: var(--c-main); color: var(--c-main);
box-shadow: 0 0 15px rgba(0, 255, 204, 0.2); cursor: pointer;
}
#btn-main.active {
background: var(--c-alert); border-color: #f00; color: #000;
box-shadow: 0 0 30px rgba(255, 0, 0, 0.4);
animation: pulseBtn 1s infinite;
}
@keyframes pulseBtn { 50% { box-shadow: 0 0 10px rgba(255,0,0,0.2); } }
/* 浮动文字 */
.pop-text {
position: absolute; pointer-events: none; color: #fff; font-size: 12px; font-weight: bold;
animation: flyUp 1s forwards; z-index: 100;
}
@keyframes flyUp { to { transform: translateY(-30px); opacity: 0; } }
</style>
</head>
<body>
<!-- 主画面 -->
<div id="tank-area">
<div style="position:absolute; top:15px; left:20px; z-index:10;">
<div style="font-size:10px; color:#444;">SYSTEM STATUS</div>
<div id="sys-status" style="font-size:14px; color:#666; margin-top:4px;">STANDBY</div>
</div>
<canvas id="mainCanvas"></canvas>
</div>
<!-- 右侧信息 -->
<div id="scope-panel">
<!-- 1. 示波器 -->
<div class="panel-group">
<div class="label">SIGNAL WAVEFORM</div>
<div id="wave-monitor">
<div class="scope-grid"></div>
<canvas id="waveCanvas"></canvas>
</div>
</div>
<!-- 2. 数据监视 -->
<div class="panel-group">
<div class="label">RESONANCE METRICS</div>
<div class="data-row" id="row-0"><span>FREQUENCY</span> <span id="txt-0">00</span></div>
<div class="data-row" id="row-1"><span>NOISE_LVL</span> <span id="txt-1">00</span></div>
<div class="data-row" id="row-2"><span>AMPLITUDE</span> <span id="txt-2">00</span></div>
</div>
<!-- 3. 样本/分析结果 -->
<div class="panel-group" style="flex:1; display:flex; flex-direction:column;">
<div class="label" style="display:flex; justify-content:space-between;">
<span>ANALYSIS & MODULATION</span>
</div>
<!-- 样本进度条 (复刻 Step2_12) -->
<div id="buffer-ui">
<div class="buffer-track">
<div class="buffer-fill" id="buffer-fill"></div>
</div>
<div class="buffer-text" id="buffer-text">0 / 5 FRAGMENTS</div>
</div>
<!-- 占位符 -->
<div id="analysis-placeholder" style="flex:1; border:1px dashed #333; display:flex; align-items:center; justify-content:center; color:#333; font-size:10px; min-height: 100px; margin-top: 10px;">
AWAITING DATA
</div>
<!-- 分析与调制界面 -->
<div id="analysis-wrapper" style="margin-top: 10px;">
<div id="analysis-container">
<canvas id="mapCanvas" width="338" height="200"></canvas>
<div class="axis-txt" style="top:5px; left:50%; transform:translateX(-50%);">HIGH AROUSAL</div>
<div class="axis-txt" style="bottom:5px; left:50%; transform:translateX(-50%);">LOW AROUSAL</div>
<div class="axis-txt" style="top:50%; left:5px; transform:translateY(-50%);">NEG</div>
<div class="axis-txt" style="top:50%; right:5px; transform:translateY(-50%);">POS</div>
</div>
<!-- 药剂控制器 -->
<div class="label" style="margin-top:10px; text-align:center; color:#888;">REAGENT INJECTION</div>
<div id="reagent-controls">
<button class="reagent-btn" id="btn-up"><div class="chem-icon"><div class="chem-fill"></div></div>STIMULANT</button>
<button class="reagent-btn" id="btn-down"><div class="chem-icon"><div class="chem-fill"></div></div>SEDATIVE</button>
<button class="reagent-btn" id="btn-left"><div class="chem-icon"><div class="chem-fill"></div></div>AGITATOR</button>
<button class="reagent-btn" id="btn-right"><div class="chem-icon"><div class="chem-fill"></div></div>EUPHORIC</button>
</div>
<button id="btn-inject">INJECT REAGENT</button>
</div>
</div>
</div>
<!-- 控制台 -->
<div id="control-panel">
<div class="slider-box">
<div class="slider-head"><span>FREQUENCY</span> <span id="val-0">0</span></div>
<input type="range" id="s-0" min="0" max="100" value="0">
</div>
<div class="slider-box">
<div class="slider-head"><span>NOISE_LVL</span> <span id="val-1">0</span></div>
<input type="range" id="s-1" min="0" max="100" value="0">
</div>
<div class="slider-box">
<div class="slider-head"><span>AMPLITUDE</span> <span id="val-2">0</span></div>
<input type="range" id="s-2" min="0" max="100" value="0">
</div>
<button id="btn-main">INITIATE</button>
</div>
<script>
/* -----------------------------------------------------------
系统配置
----------------------------------------------------------- */
const TARGETS = { 0: 30, 1: 60, 2: 85 };
const TOLERANCE = 5;
let state = {
p: [0, 0, 0], // Freq, Noise, Amp
matches: [false, false, false],
ready: false, // 波形调准
active: false, // 正在采样
phase: 0, // 0:Tuning, 1:Collecting, 2:Modulating, 3:Purifying, 4:Done
collected: 0,
needed: 5,
// 2D Map State
targetPos: { x: -60, y: -50 }, // Initial offset (High Arousal, Negative)
targetLocked: false
};
// Canvas Setup
const cMain = document.getElementById('mainCanvas');
const ctx = cMain.getContext('2d');
const cWave = document.getElementById('waveCanvas');
const wCtx = cWave.getContext('2d');
const cMap = document.getElementById('mapCanvas');
const mCtx = cMap.getContext('2d');
let w, h, ww, wh;
function resize() {
const tank = document.getElementById('tank-area');
w = cMain.width = tank.offsetWidth;
h = cMain.height = tank.offsetHeight;
ww = cWave.width = document.getElementById('wave-monitor').offsetWidth;
wh = cWave.height = document.getElementById('wave-monitor').offsetHeight;
initDust();
}
window.addEventListener('resize', resize);
setTimeout(resize, 100);
/* -----------------------------------------------------------
1. 视觉:碎片与背景
----------------------------------------------------------- */
let dusts = [];
let fragments = [];
let particles = []; // 净化粒子
class Dust {
constructor() { this.reset(); }
reset() {
this.x = Math.random() * w;
this.y = Math.random() * h;
this.z = Math.random() * 2 + 0.5;
this.vx = (Math.random() - 0.5) * 0.2;
this.vy = (Math.random() - 0.5) * 0.2;
this.blink = Math.random() * 100;
}
update() {
this.x += this.vx; this.y += this.vy;
this.blink += 0.05;
if(this.x < 0 || this.x > w || this.y < 0 || this.y > h) this.reset();
}
draw() {
const a = (Math.sin(this.blink) + 1) * 0.15 + 0.05;
ctx.fillStyle = `rgba(0, 255, 204, ${a})`;
ctx.fillRect(this.x, this.y, this.z, this.z);
}
}
function initDust() {
dusts = [];
for(let i=0; i<300; i++) dusts.push(new Dust());
}
class GlitchFragment {
constructor() {
this.x = w/2 + (Math.random()-0.5)*200;
this.y = h/2 + (Math.random()-0.5)*200;
this.life = 0;
this.dead = false;
this.sucking = false;
this.purified = false; // 是否被净化
this.alpha = 1.0;
// 形状
this.blocks = [];
const count = 4 + Math.random()*4;
for(let i=0; i<count; i++) {
this.blocks.push({
dx: (Math.random()-0.5)*20, dy: (Math.random()-0.5)*20,
w: 5+Math.random()*10, h: 5+Math.random()*10
});
}
}
update() {
// 吸取动画
if(this.sucking && !this.purified) {
this.blocks.forEach(b => { b.w *= 0.8; b.h *= 0.8; });
if(this.blocks[0].w < 0.5) this.dead = true;
return;
}
// 净化动画
if(this.purified) {
this.alpha -= 0.02;
this.blocks.forEach(b => {
b.dx += (Math.random()-0.5)*2;
b.dy += (Math.random()-0.5)*2;
});
if(this.alpha <= 0) this.dead = true;
return;
}
this.life++;
this.x += Math.sin(this.life*0.02)*0.5;
this.y += Math.cos(this.life*0.03)*0.5;
}
draw() {
if(this.dead) return;
ctx.save();
ctx.translate(this.x, this.y);
ctx.globalAlpha = this.alpha;
let color = '#ff3333';
let shadow = '#ff0000';
// 净化变色
if(this.purified) {
color = '#ffffff';
shadow = '#00ffcc';
} else if (Math.random() > 0.9) {
color = '#fff'; // Glitch flash
}
ctx.fillStyle = color;
ctx.shadowBlur = 15; ctx.shadowColor = shadow;
this.blocks.forEach(b => {
const jx = (Math.random()-0.5)*2;
const jy = (Math.random()-0.5)*2;
ctx.fillRect(b.dx + jx, b.dy + jy, b.w, b.h);
});
ctx.restore();
}
hit(mx, my) {
const dist = Math.hypot(mx - this.x, my - this.y);
return dist < 30;
}
}
class Antibody {
constructor(tx, ty) {
this.x = Math.random() * w;
this.y = h + 10; // 从底部升起
this.tx = tx; this.ty = ty; // 目标碎片位置
this.speed = 5 + Math.random()*5;
this.dead = false;
}
update() {
const dx = this.tx - this.x;
const dy = this.ty - this.y;
const dist = Math.hypot(dx, dy);
this.x += (dx/dist) * this.speed;
this.y += (dy/dist) * this.speed;
if(dist < 10) this.dead = true; // 击中
}
draw() {
ctx.fillStyle = '#00ffcc';
ctx.beginPath(); ctx.arc(this.x, this.y, 2, 0, Math.PI*2); ctx.fill();
}
}
/* -----------------------------------------------------------
2. 示波器逻辑 (使用 Step2_12 算法)
----------------------------------------------------------- */
let waveTime = 0;
function drawWave() {
// 清除
wCtx.clearRect(0, 0, ww, wh);
const vFreq = state.p[0] / 100;
const vNoise = state.p[1] / 100;
const vAmp = state.p[2] / 100;
const cy = wh / 2;
// 样式
const color = state.ready ? '#00ffcc' : '#006655';
wCtx.strokeStyle = color;
wCtx.lineWidth = state.ready ? 2 : 1;
wCtx.shadowBlur = state.ready ? 10 : 0;
wCtx.shadowColor = '#00ffcc';
wCtx.beginPath();
let penDown = true;
for(let x = 0; x < ww; x += 2) {
// 1. Freq 影响基础波形
const baseFreq = 0.02 + vFreq * 0.1;
let yRaw = Math.sin(x * baseFreq + waveTime * (2 + vFreq * 5));
// 添加谐波
if(vFreq > 0.3) yRaw += Math.sin(x * baseFreq * 2 + waveTime) * 0.3;
if(vFreq > 0.6) yRaw += Math.cos(x * baseFreq * 5 - waveTime) * 0.2;
// 方波削顶
if (vFreq > 0.8) yRaw = Math.sign(yRaw) * 1.1;
// 2. Amp 影响振幅 + 脉冲
let amp = 10 + vAmp * 30;
if (vAmp > 0.2) {
const pulseSpeed = 2 + vAmp * 5;
const envelope = 0.8 + Math.sin(waveTime * pulseSpeed) * 0.4;
amp *= envelope;
}
// 3. Noise 影响
let noise = 0;
if (vNoise > 0.1) {
noise = (Math.random() - 0.5) * vNoise * 30;
if (Math.random() < 0.01 * vNoise) noise += (Math.random() - 0.5) * 100;
}
const y = cy + (yRaw * amp) + noise;
// 断线处理
if (Math.abs(y - cy) > wh/2) penDown = false;
if (x === 0 || !penDown) wCtx.moveTo(x, y);
else wCtx.lineTo(x, y);
penDown = true;
}
wCtx.stroke();
waveTime += 0.05;
}
/* -----------------------------------------------------------
3. 核心状态控制
----------------------------------------------------------- */
function updateParam(idx, val) {
val = parseInt(val);
state.p[idx] = val;
document.getElementById(`val-${idx}`).innerText = val;
document.getElementById(`txt-${idx}`).innerText = val.toString().padStart(2,'0');
state.matches[idx] = Math.abs(val - TARGETS[idx]) <= TOLERANCE;
const el = document.getElementById(`row-${idx}`);
if(state.matches[idx]) el.classList.add('matched');
else el.classList.remove('matched');
checkTotalReady();
}
function checkTotalReady() {
const all = state.matches.every(x=>x);
state.ready = all;
const btn = document.getElementById('btn-main');
const status = document.getElementById('sys-status');
const monitor = document.getElementById('wave-monitor');
if(all) {
btn.classList.add('ready');
monitor.classList.add('locked'); // 波形背景发光
if(state.phase === 0) {
status.innerText = "RESONANCE STABLE - READY";
status.style.color = "#00ffcc";
}
} else {
btn.classList.remove('ready');
monitor.classList.remove('locked');
if(state.phase === 0) {
status.innerText = "TUNING REQUIRED...";
status.style.color = "#666";
}
}
}
// 主按钮逻辑
document.getElementById('btn-main').onclick = () => {
if(state.phase === 0) { // TUNING -> COLLECTING
if(!state.ready) return;
startCollection();
} else if (state.phase === 1) { // COLLECTING -> ABORT
resetSystem();
}
};
function startCollection() {
state.phase = 1;
state.active = true;
state.collected = 0;
fragments = []; // 重置旧的
const btn = document.getElementById('btn-main');
btn.classList.add('active');
btn.innerText = "ABORT";
document.getElementById('tank-area').classList.add('sampling');
document.getElementById('sys-status').innerText = "COLLECTING EMOTION SAMPLES...";
document.getElementById('sys-status').style.color = "#ff3333";
document.getElementById('analysis-wrapper').style.display = 'none';
document.getElementById('analysis-placeholder').style.display = 'flex';
updateSampleUI();
}
function resetSystem() {
state.phase = 0;
state.active = false;
state.targetPos = { x: -60, y: -50 }; // Reset target
state.targetLocked = false;
const btn = document.getElementById('btn-main');
btn.classList.remove('active');
btn.innerText = "INITIATE";
document.getElementById('tank-area').classList.remove('sampling');
document.getElementById('tank-area').classList.remove('purified');
document.getElementById('sys-status').innerText = "TUNING REQUIRED...";
document.getElementById('sys-status').style.color = "#666";
fragments = [];
particles = [];
document.getElementById('analysis-wrapper').style.display = 'none';
document.getElementById('analysis-placeholder').style.display = 'flex';
document.getElementById('btn-inject').style.display = 'none';
checkTotalReady(); // Re-check status text
}
// 收集碎片逻辑
function spawnLoop() {
// 只在收集阶段生成,且数量有限
if(state.phase === 1 && state.collected < state.needed && fragments.length < 8) {
if(Math.random() > 0.97) fragments.push(new GlitchFragment());
}
requestAnimationFrame(spawnLoop);
}
spawnLoop();
cMain.addEventListener('mousedown', e => {
if(state.phase !== 1) return; // 只有收集阶段可以点击
const r = cMain.getBoundingClientRect();
const mx = e.clientX - r.left;
const my = e.clientY - r.top;
for(let f of fragments) {
if(!f.sucking && !f.dead && f.hit(mx, my)) {
f.sucking = true;
state.collected++;
updateSampleUI();
spawnFloatText(e.clientX, e.clientY, "SAMPLE +1");
if(state.collected >= state.needed) {
finishCollection();
}
break;
}
}
});
function updateSampleUI() {
const pct = (state.collected / state.needed) * 100;
document.getElementById('buffer-fill').style.width = pct + '%';
document.getElementById('buffer-text').innerText = `${state.collected} / ${state.needed} FRAGMENTS`;
}
function spawnFloatText(x, y, text) {
const el = document.createElement('div');
el.className = 'pop-text';
el.innerText = text;
el.style.left = x + 'px';
el.style.top = y + 'px';
document.body.appendChild(el);
setTimeout(()=>el.remove(), 1000);
}
function finishCollection() {
state.phase = 2; // Analysis / Modulation
state.active = false; // 停止生成和点击
document.getElementById('tank-area').classList.remove('sampling');
document.getElementById('sys-status').innerText = "ANALYSIS COMPLETE. MODULATION REQUIRED.";
document.getElementById('sys-status').style.color = "#00ffcc";
document.getElementById('btn-main').classList.remove('active');
document.getElementById('btn-main').innerText = "LOCKED"; // 锁定主按钮
document.getElementById('btn-main').style.cursor = "default";
// Show Analysis
document.getElementById('analysis-placeholder').style.display = 'none';
document.getElementById('analysis-wrapper').style.display = 'flex';
drawAnalysisLoop();
}
/* -----------------------------------------------------------
4. 分析与调制 (Modulation)
----------------------------------------------------------- */
// 药剂按钮绑定
function moveTarget(dx, dy) {
if(state.phase !== 2 || state.targetLocked) return;
state.targetPos.x += dx;
state.targetPos.y += dy;
checkModulation();
}
document.getElementById('btn-up').onclick = () => moveTarget(0, -5);
document.getElementById('btn-down').onclick = () => moveTarget(0, 5);
document.getElementById('btn-left').onclick = () => moveTarget(-5, 0);
document.getElementById('btn-right').onclick = () => moveTarget(5, 0);
function checkModulation() {
const dist = Math.hypot(state.targetPos.x, state.targetPos.y);
const btn = document.getElementById('btn-inject');
if(dist < 5) {
state.targetLocked = true;
state.targetPos.x = 0;
state.targetPos.y = 0;
btn.style.display = 'block';
setTimeout(() => btn.classList.add('ready'), 100);
document.getElementById('sys-status').innerText = "COMPOUND BALANCED. READY TO INJECT.";
}
}
// 注入药剂
document.getElementById('btn-inject').onclick = () => {
if(!state.targetLocked) return;
startPurification();
};
function startPurification() {
state.phase = 3;
document.getElementById('btn-inject').classList.remove('ready');
document.getElementById('btn-inject').innerText = "INJECTING...";
document.getElementById('tank-area').classList.add('purified'); // 背景变色
document.getElementById('sys-status').innerText = "NEUTRALIZING AGENT RELEASED...";
// 发射抗体
fragments.forEach(f => {
// 为每个碎片生成几个抗体粒子
for(let i=0; i<3; i++) {
particles.push(new Antibody(f.x, f.y));
}
// 稍微延迟后碎片开始净化
setTimeout(() => { f.purified = true; }, 1000 + Math.random()*1000);
});
}
function drawAnalysisLoop() {
if(state.phase < 2) return; // Stop if reset
const cw = cMap.width;
const ch = cMap.height;
const cx = cw/2;
const cy = ch/2;
mCtx.clearRect(0,0,cw,ch);
// Grid
mCtx.strokeStyle = '#333';
mCtx.lineWidth = 1;
mCtx.beginPath();
mCtx.moveTo(cx, 0); mCtx.lineTo(cx, ch);
mCtx.moveTo(0, cy); mCtx.lineTo(cw, cy);
mCtx.stroke();
mCtx.strokeStyle = '#222';
mCtx.beginPath();
mCtx.arc(cx, cy, 30, 0, Math.PI*2);
mCtx.arc(cx, cy, 60, 0, Math.PI*2);
mCtx.stroke();
// Target Point
const tx = cx + state.targetPos.x;
const ty = cy + state.targetPos.y;
// Dashed Line to Center
mCtx.strokeStyle = '#555';
mCtx.setLineDash([4,4]);
mCtx.beginPath();
mCtx.moveTo(cx, cy);
mCtx.lineTo(tx, ty);
mCtx.stroke();
mCtx.setLineDash([]);
// Draw Point
mCtx.fillStyle = state.targetLocked ? '#fff' : '#ff3333';
mCtx.shadowBlur = 10;
mCtx.shadowColor = state.targetLocked ? '#00ffcc' : '#ff0000';
mCtx.beginPath();
mCtx.arc(tx, ty, 6, 0, Math.PI*2);
mCtx.fill();
mCtx.shadowBlur = 0;
requestAnimationFrame(drawAnalysisLoop);
}
/* -----------------------------------------------------------
5. 主循环
----------------------------------------------------------- */
function loop() {
requestAnimationFrame(loop);
ctx.clearRect(0,0,w,h);
dusts.forEach(d => { d.update(); d.draw(); });
// 净化阶段检测
if(state.phase === 3) {
// 处理抗体
particles.forEach((p,i) => {
p.update();
p.draw();
if(p.dead) particles.splice(i, 1);
});
// 检查是否全部清除
if(fragments.length === 0 && particles.length === 0) {
state.phase = 4;
document.getElementById('sys-status').innerText = "PURIFICATION COMPLETE. SYSTEM CLEAN.";
document.getElementById('sys-status').style.color = "#fff";
document.getElementById('btn-inject').innerText = "DONE";
}
}
// 绘制连线 (只在未净化时)
if(state.phase < 3 && fragments.length > 1) {
ctx.strokeStyle = 'rgba(255, 50, 50, 0.3)';
ctx.beginPath();
fragments.forEach((f1, i) => {
fragments.forEach((f2, j) => {
if(i<j && !f1.dead && !f2.dead) {
const d = Math.hypot(f1.x-f2.x, f1.y-f2.y);
if(d < 150) {
ctx.moveTo(f1.x, f1.y);
ctx.lineTo(f2.x, f2.y);
}
}
});
});
ctx.stroke();
}
// 碎片
fragments.forEach((f, i) => {
f.update();
f.draw();
if(f.dead) fragments.splice(i,1);
});
drawWave();
}
loop();
// Init
[0,1,2].forEach(i => document.getElementById(`s-${i}`).oninput = e => updateParam(i, e.target.value));
</script>
</body>
</html>