diff --git a/project-management/PJ2026-01/showcase/hwlab-first-principles/app.js b/project-management/PJ2026-01/showcase/hwlab-first-principles/app.js index f595f5cb..cb23b209 100644 --- a/project-management/PJ2026-01/showcase/hwlab-first-principles/app.js +++ b/project-management/PJ2026-01/showcase/hwlab-first-principles/app.js @@ -1,317 +1,475 @@ -const STAGES = [ +const INITIAL_EVENT = { + id: "E00", + phase: "observe", + actor: "AGENT", + title: "任务接管与控制边界绑定", + summary: "ALTA-7 接收目标,并绑定工程、目标板、探针与证据空间。", + input: "任务目标、CONSTAR_workspace、STM32F103、CMSIS-DAP", + decision: "先读取工程事实,不把预设代码差异当作问题结论", + tool: "task.bind", + output: "控制边界建立;下一步读取工程与时钟树", + log: "$ task.bind freq-1khz-accuracy\ncontext workspace=CONSTAR_workspace\ntarget board=STM32F103 probe=CMSIS-DAP\nresult session boundary ready", + observed: "任务、工程和真实硬件已完成上下文绑定", + hypothesis: "待验证:TIM2 时钟推导与板级配置不一致", + action: "等待授权读取工程事实", + next: "检查 Keil 工程与 TIM2 配置", + confidence: 42, + target: "none", +}; + +const RUN_EVENTS = [ { - title: "分析工程", - summary: "识别 TIM2 输入捕获路径与 APB 时钟倍频条件。", - log: "scan TIM2 capture path · APB1 prescaler = 2", + id: "E01", + phase: "observe", + actor: "AGENT", + title: "读取工程事实,而不是直接改代码", + summary: "扫描 Keil 工程、时钟树和 TIM2 输入捕获链路。", + input: ".uvprojx、system_stm32f1xx.c、tim.c、frequency.c", + decision: "优先确认运行时钟来源,避免围绕错误前提修改换算公式", + tool: "workspace.read", + output: "APB1 prescaler=2;TIM2 时钟需应用定时器倍频规则", + log: "$ workspace.read --scope clock,capture\nread 71-FREQ.uvprojx\nread Core/Src/system_stm32f1xx.c\nread Core/Src/tim.c\nfound APB1 prescaler = 2\nfound TIM2 input capture on CH1", + observed: "APB1 分频为 2;TIM2 位于 APB1,输入捕获使用 CH1", + hypothesis: "固定 TIMER_CLOCK_HZ 可能忽略定时器倍频规则", + action: "对照时钟树与频率换算实现", + next: "建立可证伪的误差假设", + confidence: 58, + target: "none", }, { - title: "修改代码", - summary: "修正定时器时钟计算,并注入实板标定偏移。", - log: "patch Core/Src/frequency.c · +5 -3", + id: "E02", + phase: "hypothesize", + actor: "AGENT", + title: "形成可证伪的根因假设", + summary: "把时钟推导错误与捕获偏移拆成两个可独立验证的变量。", + input: "TIM2 实际时钟规则、固定常量、历史 1 kHz 偏差", + decision: "先修正时钟来源,再应用已有板级 capture offset;不改变滤波策略", + tool: "decision.record", + output: "假设 H1:时钟推导是主因;预期修复后误差进入 ±0.10%", + log: "$ decision.record H1\nfact TIM2 clock derives from APB1 x2 rule\nconflict fixed TIMER_CLOCK_HZ bypasses board clock tree\nscope clock derivation + calibrated capture offset\nexclude input filter policy", + observed: "代码使用固定时钟常量,板级配置包含可用标定偏移", + hypothesis: "H1:时钟推导错误是主因,capture offset 是剩余偏差", + action: "记录假设与验收阈值", + next: "制定最小修改与实板验证计划", + confidence: 71, + target: "none", }, { - title: "Keil 编译", - summary: "使用受控工程配置生成可追溯固件产物。", - log: "build 0 error(s), 0 warning(s) · freq.axf", + id: "E03", + phase: "plan", + actor: "AGENT", + title: "提交最小可验证计划", + summary: "代码修改、构建、下载、UART 与独立物理测量形成闭环。", + input: "假设 H1、受控工具清单、真实 HWPOD 资源", + decision: "任何软件输出都不能替代 ioProbe 的独立物理事实", + tool: "plan.commit", + output: "P1 Patch → P2 Build → P3 Flash → P4 UART → P5 ioProbe", + log: "$ plan.commit\nP1 apply minimal clock/capture patch\nP2 build Keil target 71-FREQ\nP3 flash through CMSIS-DAP\nP4 inspect UART runtime facts\nP5 measure FREQ_OUT with ioProbe", + observed: "代码工具和真实硬件工具均已就绪", + hypothesis: "H1 可通过单次受控闭环验证", + action: "锁定计划和证据边界", + next: "生成最小代码变更", + confidence: 74, + target: "none", }, { - title: "下载固件", - summary: "通过 CMSIS-DAP 将本次构建产物写入目标板。", - log: "flash 32.18 KiB · verify OK · reset target", + id: "E04", + phase: "act", + actor: "AGENT", + title: "基于假设生成最小代码变更", + summary: "动态读取定时器时钟,并应用已有板级捕获偏移。", + input: "frequency.c、board_clock、calibration.capture_offset", + decision: "只修改频率换算边界,不触碰输入滤波与其他模块", + tool: "apply_patch", + output: "frequency.c +5 −3;变更绑定假设 H1", + log: "$ apply_patch Core/Src/frequency.c\nremove TIMER_CLOCK_HZ assumption\nadd get_timer_clock_hz()\nadd calibration.capture_offset\nresult +5 -3 · patch_sha=8a91c2d", + observed: "修改范围可限制在 capture_to_hz()", + hypothesis: "最小变更足以消除主偏差,且不会改变滤波行为", + action: "生成并审查代码 Diff", + next: "调用 Keil 构建验证编译事实", + confidence: 79, + target: "none", + patch: true, }, { - title: "UART 采集", - summary: "读取固件运行状态,确认输入捕获链路稳定。", - log: "uart capture stable · sample_count = 128", + id: "E05", + phase: "act", + actor: "TOOL", + title: "AGENT 调用 Keil 构建固件", + summary: "构建工具返回可追溯产物,AGENT 检查错误、警告与摘要。", + input: "71-FREQ.uvprojx、E04 patch_sha=8a91c2d", + decision: "只有构建产物与当前 Patch 一致,才能进入真实板下载", + tool: "keil.build", + output: "0 errors、0 warnings;freq.axf 32.18 KiB", + log: "$ keil.build --target 71-FREQ\ncompile frequency.c\nlink freq.axf\nProgram Size: Code=28964 RO-data=1984 RW-data=312 ZI-data=4168\n0 Error(s), 0 Warning(s)\nartifact sha256:4f7c...9b2a", + observed: "当前 Patch 已生成唯一固件产物", + hypothesis: "软件修复可进入目标板运行验证", + action: "审查构建结果与产物摘要", + next: "通过 CMSIS-DAP 写入目标板", + confidence: 84, + target: "none", + evidence: 3, }, { - title: "物理测量", - summary: "由 ioProbe 独立测量 FREQ_OUT,形成物理事实。", - log: "probe CH1 frequency · 999.8 Hz · error -0.02%", + id: "E06", + phase: "act", + actor: "TOOL", + title: "AGENT 操作 CMSIS-DAP 写入固件", + summary: "目标板擦除、下载、校验和复位均由当前 Session 记录。", + input: "freq.axf sha256:4f7c...9b2a、STM32F103 target", + decision: "Flash verify 必须通过,且写入摘要必须匹配 E05 产物", + tool: "cmsis-dap.flash", + output: "32.18 KiB 写入并校验通过;目标板已复位", + log: "$ cmsis-dap.flash freq.axf\nconnect STM32F103 · SWD 4MHz\nerase sectors 0-31\nwrite 32.18 KiB\nverify OK · artifact matched\nreset target", + observed: "真实目标板已运行 E05 对应固件", + hypothesis: "若 H1 正确,UART 频率输出应接近 1 kHz", + action: "控制调试探针完成下载与校验", + next: "读取 UART 运行事实", + confidence: 87, + target: "probe", + evidence: 4, + }, + { + id: "E07", + phase: "verify", + actor: "HWPOD", + title: "AGENT 读取 UART 运行事实", + summary: "连续采样确认捕获链路稳定,但不把软件自报值当作最终证据。", + input: "UART2 115200、当前目标板运行态", + decision: "UART 可证明固件链路正常;最终精度仍交给 ioProbe 独立判断", + tool: "uart.capture", + output: "128 samples;capture stable;overflow=0", + log: "$ uart.capture --port UART2 --samples 128\n[boot] 71-FREQ rev.C\n[timer] clock=72000000Hz\n[capture] stable samples=128 overflow=0\n[reported] frequency=1000.1Hz", + observed: "固件稳定运行,软件自报频率 1000.1 Hz", + hypothesis: "H1 获得运行态支持,但仍需外部测量排除自证循环", + action: "读取并界定 UART 事实", + next: "调用 ioProbe 测量 FREQ_OUT", + confidence: 91, + target: "uart", + evidence: 5, + }, + { + id: "E08", + phase: "verify", + actor: "HWPOD", + title: "AGENT 调用 ioProbe 独立测量", + summary: "物理测量不依赖固件自报值,直接采集 FREQ_OUT。", + input: "ioProbe CH1、FREQ_OUT、目标 1000.0 Hz", + decision: "以独立物理测量决定是否接受 H1 与当前 Patch", + tool: "ioprobe.measure", + output: "等待物理采样结果", + log: "$ ioprobe.measure --channel CH1 --metric frequency\narm FREQ_OUT\nsample physical waveform\nwindow stable\nresult pending", + observed: "UART 链路通过,物理输出已接入 ioProbe CH1", + hypothesis: "物理频率应落入 1000.0 Hz ±0.10%", + action: "采集真实波形并计算频率", + next: "根据物理事实接受或拒绝封存", + confidence: 93, + target: "ioprobe", + evidence: 6, }, ]; -const EVIDENCE = [ +const SUCCESS_EVENT = { + id: "E09", + phase: "reflect", + actor: "AGENT", + title: "汇总证据并接受当前能力", + summary: "物理结果支持 H1;AGENT 仅在完整证据链通过后允许封存。", + input: "Patch、Build、Flash、UART、ioProbe 999.8 Hz", + decision: "误差 −0.02% 满足阈值,H1 被证据支持,当前 Patch 可复用", + tool: "evidence.aggregate", + output: "Aggregate PASS;允许封存 freq-1khz-accuracy", + log: "$ evidence.aggregate\npatch PASS · E04\nbuild PASS · E05\nflash PASS · E06\nuart PASS · E07\nphysical PASS · 999.8Hz (-0.02%)\nverdict Aggregate PASS", + observed: "ioProbe 实测 999.8 Hz,误差 −0.02%", + hypothesis: "H1 已被软件、运行态和独立物理证据共同支持", + action: "形成 Aggregate PASS 裁决", + next: "封存为可重复回归 Case", + confidence: 99, + target: "none", +}; + +const FAILURE_EVENT = { + id: "E09", + phase: "reflect", + actor: "AGENT", + title: "拒绝封存,并返回诊断循环", + summary: "物理事实推翻当前完成假设;AGENT 阻断结果并生成下一假设。", + input: "Patch、Build、Flash、UART 通过,但 ioProbe 仅测得 972.4 Hz", + decision: "软件链路通过不能覆盖物理失败;拒绝封存并重新打开诊断", + tool: "plan.revise", + output: "Aggregate BLOCKED;新假设 H2:滤波窗口或 capture offset 仍有偏差", + log: "$ evidence.aggregate\nsoftware chain PASS\nphysical FAIL · 972.4Hz (-2.76%)\nverdict Aggregate BLOCKED\n$ plan.revise\nnext H2 inspect filter window + capture offset samples", + observed: "ioProbe 实测 972.4 Hz,与 UART 自报值冲突", + hypothesis: "H2:输入滤波窗口或 capture offset 仍存在板级偏差", + action: "拒绝封存,计划回到工程诊断", + next: "读取滤波窗口与原始 capture 样本", + confidence: 56, + target: "none", +}; + +const EVIDENCE_DETAILS = [ { - idle: "任务尚未进入受控执行。", - passed: "任务 freq-1khz-accuracy 已绑定 Workspace、Board、Probe 与目标指标。", + idle: "E00 尚未进入执行态。", + passed: "E00:任务、Workspace、Board 和 Probe 已绑定当前 Session。", }, { - idle: "尚无 Agent 执行 trace。", - active: "Agent 正在受控工程上下文中分析、修改与执行。", - passed: "Agent trace 已封存:工程分析与代码变更均可回放。", + idle: "尚无 Agent 行动 Trace。", + active: "E01–E09:AGENT 正在持续记录观察、决策、工具与结果。", + passed: "E01–E09:Agent Trace 完整,可回放每次决策与工具调用。", }, { - idle: "尚无本次构建产物。", - passed: "Keil 构建成功:0 error(s),0 warning(s),产物摘要已记录。", + idle: "E04 尚未生成代码变更。", + passed: "E04:Patch +5 −3,与根因假设 H1 建立明确关联。", }, { - idle: "目标板尚未写入本次固件。", - passed: "CMSIS-DAP 下载与校验通过,固件摘要与构建产物一致。", + idle: "E05 尚无构建产物。", + passed: "E05:Keil 0 errors、0 warnings,产物摘要已封存。", }, { - idle: "UART 运行事实尚未采集。", - passed: "UART 连续采样 128 次,输入捕获状态稳定,无溢出。", + idle: "E06 尚未操作真实目标板。", + passed: "E06:CMSIS-DAP 下载、校验和复位通过,产物摘要一致。", }, { - idle: "ioProbe 物理测量尚未开始。", - active: "ioProbe 正在独立采集 FREQ_OUT 波形。", - passed: "ioProbe 实测 999.8 Hz,误差 −0.02%,满足 ±0.10% 阈值。", - failed: "ioProbe 实测 972.4 Hz,误差 −2.76%,超出 ±0.10% 阈值。", + idle: "E07 尚未采集运行事实。", + passed: "E07:UART 连续 128 samples,捕获稳定且 overflow=0。", }, { - idle: "Aggregate 尚未汇总,不能保存为回归 Case。", - passed: "代码、构建、下载、运行与物理测量证据完整:Aggregate PASS。", - blocked: "物理测量未通过:Aggregate BLOCKED,阻止错误结果进入回归集。", + idle: "E08 尚未获得独立物理事实。", + active: "E08:AGENT 正在调用 ioProbe 采集 FREQ_OUT。", + passed: "E08:ioProbe 999.8 Hz,误差 −0.02%,满足阈值。", + failed: "E08:ioProbe 972.4 Hz,误差 −2.76%,推翻完成假设。", + }, + { + idle: "E09 尚未形成聚合裁决。", + passed: "E09:完整证据链 Aggregate PASS,可封存回归 Case。", + blocked: "E09:Aggregate BLOCKED;AGENT 已拒绝封存并返回诊断。", }, ]; -const stageButtons = [...document.querySelectorAll("[data-stage]")]; -const evidenceButtons = [...document.querySelectorAll("[data-evidence]")]; +const PATCH_HTML = ` +
static float capture_to_hz(uint32_t ticks){- return TIMER_CLOCK_HZ / ticks;- // APB prescaler assumed to be 1+ const uint32_t timer_clock = get_timer_clock_hz();+ const float calibrated_ticks =+ (float)ticks - calibration.capture_offset;+ return timer_clock / calibrated_ticks;}+ // Evidence link: hypothesis H1 / action E04执行过程将绑定代码、构建、下载、串口与物理测量事实。
- `; - return; - } - - const status = stageButtons[index].dataset.status || "idle"; - const statusLabel = { - idle: "PENDING", - active: "RUNNING", - passed: "VERIFIED", - failed: "FAILED", - }[status]; - - stageDetail.innerHTML = ` - ${String(index + 1).padStart(2, "0")} · ${statusLabel} -${stage.summary}
- `; + if (status === "idle") button.removeAttribute("data-status"); + else button.dataset.status = status; + if (label) button.querySelector("span").textContent = label; + if (selectedEvidence === index) showEvidence(index); } function showEvidence(index) { selectedEvidence = index; evidenceButtons.forEach((button, buttonIndex) => { - button.classList.toggle("is-selected", buttonIndex === index); + button.classList.toggle("is-selected", index === buttonIndex); }); - const status = evidenceButtons[index]?.dataset.status || "idle"; - const detail = EVIDENCE[index]?.[status] || EVIDENCE[index]?.idle; - evidenceDetail.textContent = detail || "暂无证据详情。"; + evidenceDetail.textContent = EVIDENCE_DETAILS[index]?.[status] || EVIDENCE_DETAILS[index]?.idle; } -function appendLog(message, tone = "") { - const line = document.createElement("span"); - line.className = tone ? `log-${tone}` : ""; - line.textContent = `${runLog.textContent.trim() ? "\n" : ""}${message}`; - runLog.append(line); - runLog.scrollTop = runLog.scrollHeight; -} - -function resetDemo({ keepFailure = true } = {}) { - runToken += 1; +function setScopeMode(mode) { + scopeMode = mode; window.cancelAnimationFrame(scopeAnimationFrame); scopeAnimationFrame = 0; - if (!keepFailure) failureMode.checked = false; - - stageButtons.forEach((button) => { - button.removeAttribute("data-status"); - button.classList.remove("is-selected"); - }); - evidenceButtons.forEach((button, index) => { - button.removeAttribute("data-status"); - button.classList.remove("is-selected"); - if (index === 5) button.querySelector("span").textContent = "ioProbe 待测"; - if (index === 6) button.querySelector("span").textContent = "Aggregate 待定"; - }); - - selectedStage = -1; - selectedEvidence = -1; - traceCounter.textContent = "0 / 6"; - runLog.innerHTML = '$ hwlab case run freq-1khz-accuracy\n 尚未执行'; - evidenceDetail.textContent = "选择证据节点查看边界事实。"; - actualFrequency.innerHTML = "— Hz"; - frequencyError.textContent = "—"; - scopeStatus.textContent = "等待采集"; - benchState.textContent = "设备待命"; - uartOutput.textContent = "[idle] waiting for firmware\n[idle] measurement channel ready"; - uartIndicator.classList.remove("is-active"); - setRunState("idle", "等待指令"); - selectStage(-1); - drawScope(0); -} - -async function runDemo() { - if (currentState === "running") return; - - resetDemo(); - const token = runToken; - const shouldFail = failureMode.checked; - setRunState("running", "闭环执行中"); - benchState.textContent = "HWPOD 已锁定"; - scopeStatus.textContent = "同步设备"; - runLog.innerHTML = ""; - appendLog("$ hwlab case run freq-1khz-accuracy", "muted"); - setEvidenceStatus(0, "passed"); - setEvidenceStatus(1, "active"); - - for (let index = 0; index < STAGES.length; index += 1) { - if (token !== runToken) return; - - setStageStatus(index, "active"); - selectStage(index); - traceCounter.textContent = `${index} / 6`; - appendLog(`→ ${STAGES[index].title}`, "active"); - - if (index === 3) benchState.textContent = "正在写入固件"; - if (index === 4) { - benchState.textContent = "固件运行中"; - uartIndicator.classList.add("is-active"); - uartOutput.textContent = "[boot] 71-FREQ rev.C\n[capture] timer input ready"; - } - if (index === 5) { - benchState.textContent = "ioProbe 采集中"; - scopeStatus.textContent = "采集中"; - setEvidenceStatus(5, "active", "ioProbe 采集中"); - } - - const shouldContinue = await delay(index === 5 ? 850 : 650, token); - if (!shouldContinue) return; - - if (index === 5 && shouldFail) { - setStageStatus(index, "failed"); - appendLog("probe CH1 frequency · 972.4 Hz · outside tolerance", "fail"); - traceCounter.textContent = "5 / 6"; - setEvidenceStatus(5, "failed", "ioProbe 972.4 Hz"); - setEvidenceStatus(6, "blocked", "Aggregate BLOCKED"); - actualFrequency.innerHTML = "972.4 Hz"; - frequencyError.textContent = "−2.76%"; - scopeStatus.textContent = "阈值超限"; - benchState.textContent = "物理验证未通过"; - uartOutput.textContent = "[capture] stable, samples=128\n[result] freq=972.4Hz, tolerance=FAIL"; - setRunState("failed", "证据阻断"); - selectStage(index); - showEvidence(6); - drawScope(0); - return; - } - - setStageStatus(index, "passed"); - appendLog(STAGES[index].log, "pass"); - traceCounter.textContent = `${index + 1} / 6`; - selectStage(index); - - if (index === 1) setEvidenceStatus(1, "passed"); - if (index === 2) setEvidenceStatus(2, "passed"); - if (index === 3) setEvidenceStatus(3, "passed"); - if (index === 4) { - setEvidenceStatus(4, "passed"); - uartOutput.textContent = "[capture] stable, samples=128\n[timer] clock=72000000Hz, overflow=0"; - } - } - - setEvidenceStatus(5, "passed", "ioProbe 999.8 Hz"); - setEvidenceStatus(6, "passed", "Aggregate PASS"); - actualFrequency.innerHTML = "999.8 Hz"; - frequencyError.textContent = "−0.02%"; - scopeStatus.textContent = "测量通过"; - benchState.textContent = "验证完成 · PASS"; - uartOutput.textContent = "[capture] stable, samples=128\n[result] freq=999.8Hz, tolerance=PASS"; - appendLog("verdict Aggregate PASS · evidence sealed", "pass"); - setRunState("passed", "Aggregate PASS"); - showEvidence(6); - drawScope(0); -} - -function showToast(message) { - window.clearTimeout(toastTimer); - toast.textContent = message; - toast.classList.add("is-visible"); - toastTimer = window.setTimeout(() => toast.classList.remove("is-visible"), 2600); + if (mode === "sampling") animateScope(); + else drawScope(0); } function drawScope(phase = 0) { @@ -329,12 +487,12 @@ function drawScope(phase = 0) { const cssWidth = width / ratio; const cssHeight = height / ratio; scopeContext.clearRect(0, 0, cssWidth, cssHeight); - scopeContext.fillStyle = "#0c1418"; + scopeContext.fillStyle = "#0b1418"; scopeContext.fillRect(0, 0, cssWidth, cssHeight); - - scopeContext.strokeStyle = "rgba(94, 132, 143, 0.18)"; + scopeContext.strokeStyle = "rgba(95, 135, 145, 0.18)"; scopeContext.lineWidth = 1; - for (let x = 0; x <= cssWidth; x += cssWidth / 10) { + + for (let x = 0; x <= cssWidth; x += cssWidth / 9) { scopeContext.beginPath(); scopeContext.moveTo(x, 0); scopeContext.lineTo(x, cssHeight); @@ -347,32 +505,30 @@ function drawScope(phase = 0) { scopeContext.stroke(); } - const amplitude = cssHeight * 0.27; + const idle = scopeMode === "idle"; + const failed = scopeMode === "failed"; + const amplitude = cssHeight * 0.28; const center = cssHeight * 0.5; - const period = currentState === "failed" ? 51 : 43; - const waveColor = currentState === "failed" ? "#ff766e" : "#53dce1"; - const muted = currentState === "idle"; - + const period = failed ? 52 : 43; scopeContext.beginPath(); for (let x = 0; x <= cssWidth; x += 2) { - const signal = muted - ? Math.sin(x / 25) * 1.2 - : Math.sin((x + phase) * ((Math.PI * 2) / period)) * amplitude; - const noise = currentState === "running" ? Math.sin((x + phase * 1.9) / 5) * 0.8 : 0; + const signal = idle ? Math.sin(x / 24) * 1.1 : Math.sin((x + phase) * ((Math.PI * 2) / period)) * amplitude; + const noise = scopeMode === "sampling" ? Math.sin((x + phase * 1.7) / 5) * 0.8 : 0; const y = center + signal + noise; if (x === 0) scopeContext.moveTo(x, y); else scopeContext.lineTo(x, y); } - scopeContext.strokeStyle = muted ? "#496169" : waveColor; - scopeContext.lineWidth = 1.4; - scopeContext.shadowColor = muted ? "transparent" : waveColor; - scopeContext.shadowBlur = muted ? 0 : 5; + const color = failed ? "#ff766e" : idle ? "#486169" : "#50dbe0"; + scopeContext.strokeStyle = color; + scopeContext.lineWidth = 1.3; + scopeContext.shadowColor = idle ? "transparent" : color; + scopeContext.shadowBlur = idle ? 0 : 5; scopeContext.stroke(); scopeContext.shadowBlur = 0; } function animateScope(timestamp = 0) { - if (currentState !== "running") { + if (scopeMode !== "sampling") { scopeAnimationFrame = 0; return; } @@ -380,8 +536,171 @@ function animateScope(timestamp = 0) { scopeAnimationFrame = window.requestAnimationFrame(animateScope); } -stageButtons.forEach((button, index) => { - button.addEventListener("click", () => selectStage(index)); +function resetDemo({ keepFailure = true } = {}) { + runToken += 1; + if (!keepFailure) failureMode.checked = false; + shownEvents = []; + selectedEventId = "E00"; + selectedEvidence = -1; + currentLoop = "observe"; + patchGenerated = false; + timeline.innerHTML = ""; + loopButtons.forEach((button) => button.removeAttribute("data-status")); + loopButtons.find((button) => button.dataset.loop === "observe").dataset.status = "active"; + evidenceButtons.forEach((button, index) => { + button.removeAttribute("data-status"); + button.classList.remove("is-selected"); + if (index === 7) button.querySelector("span").textContent = "Verdict 待定"; + }); + + patchAction.textContent = "尚未生成变更"; + patchScore.textContent = "WAITING"; + codeDiff.innerHTML = 'AGENT 必须先获得工程事实并验证假设,代码变更才会出现。