分享图
动画工坊
引擎就绪

Defining the Scope

I'm currently focused on defining the scope. The user needs a high-fidelity SVG animation, which is technically about controlling pressure variation. My problem is how to simulate this technically and precisely. I'm focusing on TRIZ, and IFR as the starting point for solution generation.

Refining the Approach

I've been thinking about an "Electrical Proportional Valve + Pressure Closed-Loop" setup for automated scanning. The core is a controller driving the valve according to a preset curve (ramp, step, sine) with closed-loop pressure control via feedback. Sensors gather data synchronously. The design requirements include the ideal final result, resource optimization, visual guidance, autoplay, proper layout, Chinese text, and staying in a single HTML file with embedded CSS/JS.

Clarifying the Components

My thought process has progressed to deconstructing the technical concept for animation. I've broken down the closed-loop system into essential elements: controller, valve, air flow, pressure chamber, reference sensor, device under test, and a real-time graph. I am focusing on a Ramp -> Hold -> Release cycle for the animation, matching the "action sequence". The aesthetic direction will be "tech".

Visualizing the Aesthetics

I'm now deep in aesthetics. I'm leaning towards an industrial, data-driven, minimalist high-tech style. I'm envisioning a dark theme with deep blue/slate backgrounds, accented by cyan, neon green, and orange. For the data, I'm thinking monospace for numbers and a clean sans-serif for labels. The visuals will be clean SVG lines with glowing effects and smooth transitions. The layout will be a split-screen for wide displays and stacked for narrow ones, possibly using a responsive grid or fixed aspect SVG.

Constructing the Layout

I'm now focused on the SVG elements and animation logic, particularly for a resolution of 1200x800 to ensure perfect scaling. The visual elements I'm drafting include a dark background with a grid, a control unit with glowing indicators, a mechanical valve, thick piping, a transparent test chamber, sensors as small nodes, and a graph plotter. I'll be using JavaScript and requestAnimationFrame for a smooth simulation loop, perfect for closed-loop control. I want maximum control over all simulation variables.

Implementing the Mechanics

Now, I'm working on the simulation's core mechanics and visual feedback. I'm focusing on the relationship between target and actual pressure, valve opening, and airflow. The real-time graph will be appended with points. Numeric readouts, airflow particles, and valve coil colors will update dynamically. Finally, the test chamber will visually represent pressure. Concise, small text labels are being used.

Visualizing the Feedback

I'm now emphasizing the closed-loop feedback in my visuals. I'm going to create pulsating lines between the sensor, controller, and valve to clearly represent the signal flow. I am also working on a step-by-step implementation plan that will use HTML, CSS, SVG and JavaScript.

Refining the Simulation

I'm now integrating new elements into the simulation, focusing on the math for pressure updates and DOM manipulation. I'm building pressure lag using low-pass filters or spring physics and ensuring feedback via glowing wires and a clear legend. I've also finalized the color palette for a striking visual pop and the SVG layout. I've got a system diagram on the left, graph and UI on the right, and added the gas supply and proportional valve components.

Designing the Visualization

I'm now detailing the visual representation of the system components. I'm focusing on the test chamber as a rounded rectangle with the reference sensor attached on top with a wire to the controller, and the controller is now a box above the valve. Pipes will be thick gray paths with an animated dashed overlay to indicate airflow. I've started the JavaScript simulation logic with the 'ramp', 'hold', 'vent' and 'idle' phases, with simulated PID control and UI updates, and am ready to start testing.

Highlighting the Control Loop

I'm now emphasizing the automation aspect, drawing the "Control Loop" explicitly with animated glowing dots along the data flow lines to visually represent the self-regulation. I've also re-confirmed my constraints, ensuring a precise, technical UI with non-obstructive labels, auto-play, and Chinese text support. I'm focusing on the graph and visual diagram for the majority of the space.

Optimizing the Animation

I'm now zeroing in on optimizing the SVG animation for the real-time graph. I've decided to update the <path>'s d attribute using JavaScript for high-fidelity vector rendering, avoiding the jerkiness of other approaches. The visual polish involves a glowing grid, chamber filling with pressure, and animated data packets in the feedback loop. I'll also use realistically formatted numbers. I will implement a scrolling window approach for the graph.

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>闭环自动调压系统 IFR 演示</title>
    <style>
        @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Noto+Sans+SC:wght@300;500;700&display=swap');

        :root {
            --bg-color: #050914;
            --panel-bg: rgba(14, 21, 37, 0.7);
            --grid-color: rgba(41, 58, 92, 0.3);
            --text-main: #e2e8f0;
            --text-muted: #64748b;
            --accent-target: #00f0ff; /* 目标曲线/信号 */
            --accent-actual: #39ff14; /* 实际反馈 */
            --accent-flow: #ff7b00;   /* 气流 */
            --accent-ui: #3b82f6;
            --border-color: rgba(59, 130, 246, 0.3);
            
            font-family: 'Noto Sans SC', sans-serif;
            background-color: var(--bg-color);
            color: var(--text-main);
            margin: 0;
            padding: 0;
            height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            overflow: hidden;
        }

        /* 极简网格背景 */
        body::before {
            content: '';
            position: absolute;
            top: 0; left: 0; width: 100%; height: 100%;
            background-image: 
                linear-gradient(var(--grid-color) 1px, transparent 1px),
                linear-gradient(90deg, var(--grid-color) 1px, transparent 1px);
            background-size: 40px 40px;
            z-index: -1;
            opacity: 0.5;
        }

        .container {
            width: 95vw;
            max-width: 1400px;
            aspect-ratio: 16 / 9;
            background: var(--panel-bg);
            border: 1px solid var(--border-color);
            border-radius: 12px;
            box-shadow: 0 0 40px rgba(0, 0, 0, 0.8), inset 0 0 20px rgba(59, 130, 246, 0.1);
            position: relative;
            backdrop-filter: blur(10px);
            display: flex;
            flex-direction: column;
            overflow: hidden;
        }

        header {
            padding: 16px 24px;
            border-bottom: 1px solid var(--border-color);
            display: flex;
            justify-content: space-between;
            align-items: center;
            background: linear-gradient(90deg, rgba(14,21,37,1) 0%, rgba(14,21,37,0) 100%);
        }

        h1 {
            margin: 0;
            font-size: 16px;
            font-weight: 700;
            letter-spacing: 2px;
            display: flex;
            align-items: center;
            gap: 12px;
        }

        h1::before {
            content: '';
            display: block;
            width: 8px;
            height: 8px;
            background: var(--accent-actual);
            border-radius: 50%;
            box-shadow: 0 0 10px var(--accent-actual);
            animation: pulse-dot 2s infinite;
        }

        .subtitle {
            font-size: 12px;
            color: var(--text-muted);
            font-family: 'JetBrains Mono', monospace;
        }

        .main-content {
            flex: 1;
            position: relative;
            display: flex;
        }

        svg {
            width: 100%;
            height: 100%;
            display: block;
        }

        /* 文字样式类,用于 SVG 内部 */
        .svg-text { font-family: 'Noto Sans SC', sans-serif; font-size: 12px; fill: var(--text-muted); }
        .svg-val { font-family: 'JetBrains Mono', monospace; font-size: 14px; fill: var(--text-main); font-weight: bold; }
        .svg-title { font-family: 'Noto Sans SC', sans-serif; font-size: 14px; fill: var(--text-main); font-weight: bold; letter-spacing: 1px; }

        /* 动画类 */
        @keyframes pulse-dot {
            0% { box-shadow: 0 0 0 0 rgba(57, 255, 20, 0.7); }
            70% { box-shadow: 0 0 0 6px rgba(57, 255, 20, 0); }
            100% { box-shadow: 0 0 0 0 rgba(57, 255, 20, 0); }
        }

        @keyframes flow {
            from { stroke-dashoffset: 20; }
            to { stroke-dashoffset: 0; }
        }

        .pipe-flow {
            stroke: var(--accent-flow);
            stroke-width: 4;
            stroke-dasharray: 10 10;
            animation: flow 0.5s linear infinite;
            opacity: 0;
            transition: opacity 0.2s;
        }

        .data-pulse {
            fill: var(--accent-actual);
            filter: drop-shadow(0 0 5px var(--accent-actual));
            opacity: 0;
        }

        .signal-pulse {
            fill: var(--accent-target);
            filter: drop-shadow(0 0 5px var(--accent-target));
            opacity: 0;
        }

        .legend {
            position: absolute;
            bottom: 20px;
            right: 24px;
            display: flex;
            gap: 16px;
            font-size: 12px;
            color: var(--text-muted);
            background: rgba(0,0,0,0.5);
            padding: 8px 12px;
            border-radius: 6px;
            border: 1px solid rgba(255,255,255,0.1);
        }

        .legend-item { display: flex; align-items: center; gap: 6px; }
        .legend-color { width: 12px; height: 3px; border-radius: 2px; }
        .bg-target { background: var(--accent-target); box-shadow: 0 0 8px var(--accent-target); }
        .bg-actual { background: var(--accent-actual); box-shadow: 0 0 8px var(--accent-actual); }

        .info-panel {
            position: absolute;
            top: 20px;
            right: 24px;
            font-size: 12px;
            line-height: 1.6;
            color: var(--text-muted);
            max-width: 250px;
            pointer-events: none;
        }
        
        .highlight { color: var(--accent-target); font-weight: bold; }
        
    </style>
</head>
<body>

    <div class="container">
        <header>
            <h1>比例阀闭环压力控制系统 <span class="subtitle">| IFR (Ideal Final Result) Demonstration</span></h1>
            <div class="subtitle">STATUS: <span style="color: var(--accent-actual)">AUTO_SCANNING</span></div>
        </header>

        <div class="main-content">
            <!-- 绝对定位的说明文字,严格控制位置和大小,不遮挡主体 -->
            <div class="info-panel">
                方案原理:利用 <span class="highlight">电气比例阀</span> 与 <span class="highlight">参考传感器</span> 形成物理闭环。<br>
                最终理想解:系统 <span class="highlight">自主补偿</span> 误差,代替人工旋钮,实现高精度 (±0.5kPa)、可重复的自动压力曲线扫描。
            </div>

            <!-- SVG 画布:ViewBox 设定协调比例 -->
            <svg viewBox="0 0 1400 700" preserveAspectRatio="xMidYMid meet">
                <defs>
                    <!-- 发光滤镜 -->
                    <filter id="glow-cyan" x="-20%" y="-20%" width="140%" height="140%">
                        <feGaussianBlur stdDeviation="4" result="blur" />
                        <feComposite in="SourceGraphic" in2="blur" operator="over" />
                    </filter>
                    <filter id="glow-green" x="-20%" y="-20%" width="140%" height="140%">
                        <feGaussianBlur stdDeviation="4" result="blur" />
                        <feComposite in="SourceGraphic" in2="blur" operator="over" />
                    </filter>
                    
                    <!-- 渐变 -->
                    <linearGradient id="chamber-grad" x1="0" y1="1" x2="0" y2="0">
                        <stop offset="0%" stop-color="#0e1525" />
                        <stop offset="100%" stop-color="#1a2b4c" stop-opacity="0.8" />
                    </linearGradient>

                    <!-- 动态腔体填充掩码 -->
                    <clipPath id="chamber-clip">
                        <rect id="chamber-fill-rect" x="220" y="500" width="160" height="0" />
                    </clipPath>
                    
                    <pattern id="dot-pattern" x="0" y="0" width="10" height="10" patternUnits="userSpaceOnUse">
                        <circle cx="2" cy="2" r="1" fill="rgba(255,255,255,0.1)"></circle>
                    </pattern>
                </defs>

                <!-- ================= 左侧:物理系统原理图 ================= -->
                
                <!-- 气源 -->
                <g transform="translate(40, 380)">
                    <rect x="0" y="0" width="60" height="140" rx="30" fill="#1c2541" stroke="#3b82f6" stroke-width="2"/>
                    <rect x="20" y="-15" width="20" height="15" fill="#3b82f6" />
                    <text x="30" y="170" class="svg-text" text-anchor="middle">清洁气源</text>
                    <text x="30" y="70" class="svg-title" text-anchor="middle" transform="rotate(-90 30 70)">AIR</text>
                </g>

                <!-- 管道网络 (底层) -->
                <path d="M 70 365 L 70 320 L 160 320" fill="none" stroke="#2a3b5c" stroke-width="12" stroke-linecap="round" stroke-linejoin="round"/>
                <path d="M 240 320 L 300 320 L 300 380" fill="none" stroke="#2a3b5c" stroke-width="12" stroke-linecap="round" stroke-linejoin="round"/>
                
                <!-- 气流动画 (覆盖层,由 JS 控制透明度) -->
                <path id="pipe-flow-in" d="M 70 365 L 70 320 L 160 320" fill="none" class="pipe-flow" />
                <path id="pipe-flow-out" d="M 240 320 L 300 320 L 300 380" fill="none" class="pipe-flow" />

                <!-- 电气比例阀 -->
                <g transform="translate(160, 270)">
                    <!-- 阀体 -->
                    <rect x="0" y="30" width="80" height="60" rx="8" fill="#1a2538" stroke="#475569" stroke-width="2"/>
                    <line x1="10" y1="60" x2="70" y2="60" stroke="#334155" stroke-width="2"/>
                    <polygon points="40,40 25,75 55,75" fill="none" stroke="#475569" stroke-width="2"/>
                    <!-- 电磁线圈 -->
                    <rect x="15" y="0" width="50" height="30" rx="4" fill="#0f172a" stroke="var(--accent-target)" stroke-width="1"/>
                    <path d="M 20 5 L 60 5 M 20 15 L 60 15 M 20 25 L 60 25" stroke="rgba(0,240,255,0.3)" stroke-width="2"/>
                    <!-- 动态状态指示 -->
                    <rect id="valve-indicator" x="30" y="8" width="20" height="14" rx="2" fill="#000" />
                    
                    <text x="40" y="-15" class="svg-title" text-anchor="middle">电气比例阀</text>
                    <text x="40" y="110" class="svg-text" text-anchor="middle">执行机构</text>
                </g>

                <!-- 测试腔室 (DUT & Reference) -->
                <g transform="translate(220, 380)">
                    <rect x="0" y="0" width="160" height="120" rx="12" fill="url(#chamber-grad)" stroke="#3b82f6" stroke-width="2"/>
                    <rect x="0" y="0" width="160" height="120" rx="12" fill="url(#dot-pattern)" opacity="0.5"/>
                    
                    <!-- 压力填充可视化 -->
                    <rect x="0" y="0" width="160" height="120" rx="12" fill="rgba(255, 123, 0, 0.2)" clip-path="url(#chamber-clip)"/>
                    <text x="80" y="145" class="svg-text" text-anchor="middle">测试环境腔体</text>
                    
                    <!-- 动态压力数值展示于腔体中央 -->
                    <text id="chamber-val" x="80" y="65" class="svg-val" text-anchor="middle" style="font-size: 20px; fill: var(--accent-flow)">0.00 kPa</text>
                </g>

                <!-- 传感器网络 -->
                <!-- 被测传感器 (DUT) -->
                <g transform="translate(240, 350)">
                    <rect x="0" y="0" width="30" height="30" rx="4" fill="#2d3748" stroke="#a0aec0" stroke-width="1.5"/>
                    <circle cx="15" cy="15" r="6" fill="#4a5568"/>
                    <line x1="15" y1="30" x2="15" y2="45" stroke="#a0aec0" stroke-width="2"/>
                    <text x="15" y="-10" class="svg-text" text-anchor="middle">被测件</text>
                </g>

                <!-- 参考标准传感器 (闭环反馈) -->
                <g transform="translate(330, 350)">
                    <rect x="0" y="0" width="30" height="30" rx="4" fill="#1a202c" stroke="var(--accent-actual)" stroke-width="1.5"/>
                    <circle cx="15" cy="15" r="8" fill="#2c7a7b"/>
                    <line x1="15" y1="30" x2="15" y2="45" stroke="var(--accent-actual)" stroke-width="2"/>
                    <text x="15" y="-10" class="svg-text" text-anchor="middle" fill="var(--accent-actual)">标准传感器</text>
                </g>


                <!-- 集成控制板 (Controller) -->
                <g transform="translate(140, 60)">
                    <rect x="0" y="0" width="260" height="100" rx="8" fill="#0b1120" stroke="var(--border-color)" stroke-width="2"/>
                    <text x="20" y="25" class="svg-title">控制器 (PID/生成曲线)</text>
                    
                    <rect x="20" y="45" width="220" height="40" rx="4" fill="#000" stroke="#334155"/>
                    <text x="30" y="62" class="svg-text">Target:</text>
                    <text id="ctrl-target" x="120" y="62" class="svg-val" fill="var(--accent-target)">0.00</text>
                    <text x="30" y="78" class="svg-text">Feedback:</text>
                    <text id="ctrl-actual" x="120" y="78" class="svg-val" fill="var(--accent-actual)">0.00</text>
                    
                    <circle id="status-led" cx="230" cy="25" r="4" fill="var(--accent-actual)" filter="url(#glow-green)"/>
                </g>

                <!-- 数据与控制连线 (闭环精髓) -->
                <!-- 反馈线 (传感器 -> 控制器) -->
                <path d="M 345 350 L 345 220 L 380 220 L 380 110 L 400 110" fill="none" stroke="rgba(57, 255, 20, 0.2)" stroke-width="2" stroke-dasharray="4 4"/>
                <path id="feedback-wire" d="M 345 350 L 345 220 L 380 220 L 380 110 L 400 110" fill="none" stroke="transparent" stroke-width="4"/>
                <!-- 动态反馈信号点 (JS 控制) -->
                <circle id="feedback-dot" cx="0" cy="0" r="4" class="data-pulse" />

                <!-- 控制线 (控制器 -> 比例阀) -->
                <path d="M 140 110 L 120 110 L 120 285 L 160 285" fill="none" stroke="rgba(0, 240, 255, 0.2)" stroke-width="2" stroke-dasharray="4 4"/>
                <path id="control-wire" d="M 140 110 L 120 110 L 120 285 L 160 285" fill="none" stroke="transparent" stroke-width="4"/>
                <!-- 动态控制信号点 (JS 控制) -->
                <circle id="control-dot" cx="0" cy="0" r="4" class="signal-pulse" />


                <!-- ================= 右侧:实时数据曲线 (HMI 界面) ================= -->
                
                <g transform="translate(520, 60)">
                    <!-- 仪表盘外框 -->
                    <rect x="0" y="0" width="820" height="560" rx="12" fill="#080c16" stroke="#1e293b" stroke-width="1"/>
                    
                    <!-- 坐标系背景网格 -->
                    <g opacity="0.2">
                        <!-- 水平线 -->
                        <line x1="60" y1="40" x2="780" y2="40" stroke="var(--text-muted)" stroke-width="1"/>
                        <line x1="60" y1="140" x2="780" y2="140" stroke="var(--text-muted)" stroke-width="1" stroke-dasharray="2 2"/>
                        <line x1="60" y1="240" x2="780" y2="240" stroke="var(--text-muted)" stroke-width="1" stroke-dasharray="2 2"/>
                        <line x1="60" y1="340" x2="780" y2="340" stroke="var(--text-muted)" stroke-width="1" stroke-dasharray="2 2"/>
                        <line x1="60" y1="440" x2="780" y2="440" stroke="var(--text-muted)" stroke-width="1" stroke-dasharray="2 2"/>
                    </g>
                    
                    <!-- 坐标轴 -->
                    <line x1="60" y1="40" x2="60" y2="440" stroke="var(--text-muted)" stroke-width="2"/>
                    <line x1="60" y1="440" x2="780" y2="440" stroke="var(--text-muted)" stroke-width="2"/>
                    
                    <!-- Y轴标签 (表示 0 - 100 kPa) -->
                    <text x="50" y="45" class="svg-text" text-anchor="end">100</text>
                    <text x="50" y="145" class="svg-text" text-anchor="end">75</text>
                    <text x="50" y="245" class="svg-text" text-anchor="end">50</text>
                    <text x="50" y="345" class="svg-text" text-anchor="end">25</text>
                    <text x="50" y="445" class="svg-text" text-anchor="end">0</text>
                    <text x="30" y="30" class="svg-title" text-anchor="end">kPa</text>
                    
                    <!-- X轴标签 -->
                    <text x="770" y="465" class="svg-text" text-anchor="end">时间 (t)</text>
                    
                    <!-- 曲线裁切区域 (防止线画到轴外面) -->
                    <clipPath id="graph-clip">
                        <rect x="60" y="20" width="720" height="420" />
                    </clipPath>

                    <!-- 动态绘制的数据图 -->
                    <g clip-path="url(#graph-clip)">
                        <!-- 目标曲线 (预设) -->
                        <path id="graph-target" d="" fill="none" stroke="var(--accent-target)" stroke-width="2" stroke-dasharray="5 5" filter="url(#glow-cyan)"/>
                        <!-- 实际反馈曲线 (闭环结果) -->
                        <path id="graph-actual" d="" fill="none" stroke="var(--accent-actual)" stroke-width="3" filter="url(#glow-green)"/>
                    </g>

                    <!-- 实时数值游标与数据面板 -->
                    <g id="cursor-group" transform="translate(780, 0)">
                        <line x1="0" y1="40" x2="0" y2="440" stroke="#475569" stroke-width="1" stroke-dasharray="2 2"/>
                        
                        <!-- 目标值圆点 -->
                        <circle id="cursor-target-pt" cx="0" cy="440" r="5" fill="var(--bg-color)" stroke="var(--accent-target)" stroke-width="2"/>
                        <!-- 实际值圆点 -->
                        <circle id="cursor-actual-pt" cx="0" cy="440" r="5" fill="var(--bg-color)" stroke="var(--accent-actual)" stroke-width="2"/>
                    </g>

                    <!-- 状态与参数面板 (下方) -->
                    <rect x="60" y="480" width="720" height="60" rx="6" fill="#0f172a" stroke="#1e293b"/>
                    
                    <text x="80" y="505" class="svg-text">当前工况阶段:</text>
                    <text id="ui-phase" x="80" y="525" class="svg-title" fill="#e2e8f0">初始化...</text>
                    
                    <text x="280" y="505" class="svg-text">目标压力设定 (SP):</text>
                    <text id="ui-target" x="280" y="525" class="svg-val" fill="var(--accent-target)">0.00 kPa</text>
                    
                    <text x="480" y="505" class="svg-text">实时闭环反馈 (PV):</text>
                    <text id="ui-actual" x="480" y="525" class="svg-val" fill="var(--accent-actual)">0.00 kPa</text>

                    <text x="680" y="505" class="svg-text">控制偏差:</text>
                    <text id="ui-error" x="680" y="525" class="svg-val" fill="#ef4444">0.00</text>
                </g>

            </svg>
        </div>
        
        <div class="legend">
            <div class="legend-item"><div class="legend-color bg-target"></div>目标预设曲线</div>
            <div class="legend-item"><div class="legend-color bg-actual"></div>传感器实时反馈</div>
        </div>
    </div>

    <script>
        /**
         * 闭环压力控制系统 - 物理模拟与视觉渲染引擎
         * 聚焦 IFR 展示:自动调节,精准追踪曲线
         */
        
        // 1. 系统参数与状态
        const SYSTEM = {
            maxPressure: 100, // 最大量程 kPa
            target: 0,
            actual: 0,
            phase: 'IDLE',
            time: 0,
            history: [] // 存储 {t, target, actual} 用于画图
        };

        // 2. 仿真物理参数
        const PHYSICS = {
            volume: 10,        // 腔体相对容积
            valveCapacity: 15, // 阀门最大流量
            leakage: 0.2,      // 自然微漏率
            kp: 2.5,           // 比例系数 (PID 的 P)
            ki: 0.1,           // 积分系数 (PID 的 I)
            integral: 0
        };

        // 3. UI/SVG 渲染配置
        const RENDER = {
            graphWidth: 720,
            graphHeight: 400,
            graphXOffset: 60,
            graphYOffset: 440, // 底部 Y 坐标 (值=0)
            timeWindow: 30,    // X轴显示多少秒的数据
            fps: 60,
            dt: 1 / 60
        };

        // DOM 元素获取
        const els = {
            targetGraph: document.getElementById('graph-target'),
            actualGraph: document.getElementById('graph-actual'),
            cursorTarget: document.getElementById('cursor-target-pt'),
            cursorActual: document.getElementById('cursor-actual-pt'),
            uiPhase: document.getElementById('ui-phase'),
            uiTarget: document.getElementById('ui-target'),
            uiActual: document.getElementById('ui-actual'),
            uiError: document.getElementById('ui-error'),
            ctrlTarget: document.getElementById('ctrl-target'),
            ctrlActual: document.getElementById('ctrl-actual'),
            chamberVal: document.getElementById('chamber-val'),
            chamberClipRect: document.getElementById('chamber-fill-rect'),
            valveIndicator: document.getElementById('valve-indicator'),
            pipeIn: document.getElementById('pipe-flow-in'),
            pipeOut: document.getElementById('pipe-flow-out'),
            fbDot: document.getElementById('feedback-dot'),
            ctrlDot: document.getElementById('control-dot'),
            fbWire: document.getElementById('feedback-wire'),
            ctrlWire: document.getElementById('control-wire')
        };

        // 路径长度计算 (用于信号动画)
        const fbLength = els.fbWire.getTotalLength();
        const ctrlLength = els.ctrlWire.getTotalLength();

        /**
         * 曲线生成器:模拟实际工况的复杂需求
         * 包含:斜坡升压 -> 恒压保压 -> 阶跃升压 -> 正弦波动模拟 -> 泄压释放
         */
        function getTargetPressure(t) {
            const cycle = t % 40; // 40秒一个完整周期
            
            if (cycle < 2) {
                SYSTEM.phase = '初始化休眠';
                return 0;
            } else if (cycle < 10) {
                SYSTEM.phase = '斜坡平滑升压 (10 kPa/s)';
                return (cycle - 2) * 10; // 0 到 80
            } else if (cycle < 16) {
                SYSTEM.phase = '高压稳定保压';
                return 80;
            } else if (cycle < 18) {
                SYSTEM.phase = '阶跃加压';
                return 95;
            } else if (cycle < 28) {
                SYSTEM.phase = '交变压力测试 (正弦扫描)';
                // 中心 70, 振幅 20, 周期 4秒
                return 70 + 20 * Math.sin((cycle - 18) * Math.PI / 2);
            } else if (cycle < 32) {
                SYSTEM.phase = '低压稳定保压';
                return 50;
            } else if (cycle < 36) {
                SYSTEM.phase = '斜坡受控泄压';
                return 50 - (cycle - 32) * 12.5; // 降到0
            } else {
                SYSTEM.phase = '排空与复位';
                return 0;
            }
        }

        /**
         * 物理引擎:简易闭环与气动模拟
         */
        function updatePhysics() {
            SYSTEM.time += RENDER.dt;
            
            // 1. 读取目标曲线
            SYSTEM.target = getTargetPressure(SYSTEM.time);
            
            // 2. 计算误差 (反馈闭环)
            let error = SYSTEM.target - SYSTEM.actual;
            
            // 3. PI 控制算法计算阀门开度控制信号 (-1 到 1)
            PHYSICS.integral += error * RENDER.dt;
            // 防积分饱和
            if(PHYSICS.integral > 50) PHYSICS.integral = 50;
            if(PHYSICS.integral < -50) PHYSICS.integral = -50;
            
            let controlSignal = (error * PHYSICS.kp + PHYSICS.integral * PHYSICS.ki) / 100;
            
            // 限制阀门动作范围
            if (controlSignal > 1) controlSignal = 1;
            if (controlSignal < -1) controlSignal = -1; // 负值代表排气阀开启(简易模拟两位三通或双阀)

            // 4. 模拟气流进出腔体
            let flowRate = 0;
            if (controlSignal > 0) {
                // 充气
                flowRate = controlSignal * PHYSICS.valveCapacity;
                els.pipeIn.style.opacity = Math.min(controlSignal * 2, 1);
                els.pipeOut.style.opacity = 0;
                els.valveIndicator.setAttribute('fill', '#ef4444'); // 红/橙表示进气
            } else if (controlSignal < 0) {
                // 排气
                flowRate = controlSignal * PHYSICS.valveCapacity * 1.5; // 排气通常较快
                els.pipeIn.style.opacity = 0;
                els.pipeOut.style.opacity = Math.min(Math.abs(controlSignal) * 2, 1);
                els.valveIndicator.setAttribute('fill', '#3b82f6'); // 蓝表示排气
            } else {
                // 保压
                els.pipeIn.style.opacity = 0;
                els.pipeOut.style.opacity = 0;
                els.valveIndicator.setAttribute('fill', '#22c55e'); // 绿表示稳态闭合
            }

            // 自然泄漏
            flowRate -= PHYSICS.leakage * (SYSTEM.actual / SYSTEM.maxPressure);

            // 腔体压力更新 (P = nRT/V) -> dP = dq / V
            SYSTEM.actual += (flowRate / PHYSICS.volume) * RENDER.dt;
            
            // 物理限制
            if (SYSTEM.actual < 0) SYSTEM.actual = 0;
            
            // 5. 记录历史数据用于绘图
            SYSTEM.history.push({
                t: SYSTEM.time,
                target: SYSTEM.target,
                actual: SYSTEM.actual
            });
            
            // 保持数组不过大,仅保留窗口期数据
            if (SYSTEM.history.length > (RENDER.timeWindow / RENDER.dt) + 10) {
                SYSTEM.history.shift();
            }
        }

        /**
         * 渲染引擎:将物理状态反映到 SVG DOM 上
         */
        function updateDOM() {
            const h = SYSTEM.history;
            if (h.length === 0) return;

            const current = h[h.length - 1];
            
            // --- 1. 更新数值文本 ---
            const tStr = current.target.toFixed(2);
            const aStr = current.actual.toFixed(2);
            const eStr = Math.abs(current.target - current.actual).toFixed(2);
            
            els.uiTarget.textContent = `${tStr} kPa`;
            els.uiActual.textContent = `${aStr} kPa`;
            els.ctrlTarget.textContent = tStr;
            els.ctrlActual.textContent = aStr;
            els.chamberVal.textContent = `${aStr} kPa`;
            els.uiPhase.textContent = SYSTEM.phase;
            
            els.uiError.textContent = `±${eStr} kPa`;
            // 动态变色:误差小则绿,大则红 (体现高精度)
            els.uiError.setAttribute('fill', eStr < 1.0 ? '#22c55e' : '#ef4444');

            // --- 2. 绘制波形图 (Scrolling HMI 风格) ---
            let targetPath = '';
            let actualPath = '';
            
            const timeMax = current.t;
            const timeMin = Math.max(0, timeMax - RENDER.timeWindow);
            
            for (let i = 0; i < h.length; i++) {
                const pt = h[i];
                if (pt.t < timeMin) continue; // 丢弃屏幕外的数据
                
                // 映射坐标 (X: 0->Window, Y: Max->0)
                const x = RENDER.graphXOffset + ((pt.t - timeMin) / RENDER.timeWindow) * RENDER.graphWidth;
                const yTarget = RENDER.graphYOffset - (pt.target / SYSTEM.maxPressure) * RENDER.graphHeight;
                const yActual = RENDER.graphYOffset - (pt.actual / SYSTEM.maxPressure) * RENDER.graphHeight;
                
                if (targetPath === '') {
                    targetPath = `M ${x} ${yTarget}`;
                    actualPath = `M ${x} ${yActual}`;
                } else {
                    targetPath += ` L ${x} ${yTarget}`;
                    actualPath += ` L ${x} ${yActual}`;
                }
            }
            
            els.targetGraph.setAttribute('d', targetPath);
            els.actualGraph.setAttribute('d', actualPath);
            
            // --- 3. 更新游标位置 ---
            // 游标固定在右侧,随曲线高度上下移动
            const cursorYTarget = RENDER.graphYOffset - (current.target / SYSTEM.maxPressure) * RENDER.graphHeight;
            const cursorYActual = RENDER.graphYOffset - (current.actual / SYSTEM.maxPressure) * RENDER.graphHeight;
            els.cursorTarget.setAttribute('cy', cursorYTarget);
            els.cursorActual.setAttribute('cy', cursorYActual);

            // --- 4. 更新物理示意图动画 ---
            // 腔体液位/颜色填充指示
            const fillHeight = (current.actual / SYSTEM.maxPressure) * 120; // 腔体高度120
            els.chamberClipRect.setAttribute('y', 500 + (120 - fillHeight));
            els.chamberClipRect.setAttribute('height', fillHeight);

            // 信号流动动画 (体现闭环数据流)
            // 控制信号:控制器 -> 阀门
            const ctrlPos = (SYSTEM.time * 200) % ctrlLength;
            const ctrlPt = els.ctrlWire.getPointAtLength(ctrlPos);
            els.ctrlDot.setAttribute('cx', ctrlPt.x);
            els.ctrlDot.setAttribute('cy', ctrlPt.y);
            els.ctrlDot.style.opacity = Math.abs(current.target - current.actual) > 0.5 ? 1 : 0.2; // 调整中高亮

            // 反馈信号:传感器 -> 控制器 (反向流动)
            const fbPos = fbLength - ((SYSTEM.time * 250) % fbLength);
            const fbPt = els.fbWire.getPointAtLength(fbPos);
            els.fbDot.setAttribute('cx', fbPt.x);
            els.fbDot.setAttribute('cy', fbPt.y);
            els.fbDot.style.opacity = 1; // 始终反馈
        }

        /**
         * 主循环
         */
        function loop() {
            updatePhysics();
            updateDOM();
            requestAnimationFrame(loop);
        }

        // 初始化启动
        window.addEventListener('DOMContentLoaded', () => {
            // 强制重置状态,保证 iframe 每次重载都从头开始
            SYSTEM.time = 0;
            SYSTEM.history = [];
            PHYSICS.integral = 0;
            // 启动动画循环
            requestAnimationFrame(loop);
        });

    </script>
</body>
</html>
积分规则:每轮对话扣10分