Files
DLD154V4B/tests/test_vac_ref_reanchor.c
T
wangfq 6c82bf61b8 fix(V4B): V4.36.43 — 修正 V4.36.42 引入的离开变慢 (G3 计时器递减位置错误)
用户反馈: 烧 V4.36.42 后离开 3、4 秒才释放。

定位(46µH/113kHz 日志): 4 次离开全是 Car_OFF(band_timeout), 延迟 3.5~4.7s; RLN 正常
(有车态 = Origin−41, 说明 G1 生效、100ms 自激已消失) ⇒ 问题出在"正常释放路径被挡住"。

根因: G3 的 g_rel_hold_tick-- 误放 if(!loop1_VD_FLAG) 无车分支(原 :1308) ⇒ 有车期间永不递减
⇒ 首次 Car_In 后常驻 30 ⇒ g_rel_hold_tick==0 永假 ⇒ 正常释放被永久挡住, 只能等 2.5s 环带兜底
+ 平滑值抬升 ⇒ 3.5~4.7s。

修复: 倒计时移到 } else { 有车分支顶部 (TaskLoop.c:1531 后、#if USE_FLATNESS_EXIT 之前,
每 tick 必过且在释放判定之前); 误放旧行改注释留痕。REL_MIN_HOLD_TICKS 仍 30(300ms)。

单测: 8/8 全绿(场景 7 语义不变); 新增"镜像能力边界"警告 —— 镜像只验证算法语义, 验证不了固件内
执行路径/放置位置, 此类问题必须靠日志 Car_OFF 原因字段(band_timeout/FLAT/普通)暴露。
文档: devlog V2.80(+条目) / spec 头 V2.62 / manual 表 V2.19 / cmcng.h 4.36.42→4.36.43 REV 43
/ 分析稿 §13 返工记录。
待现场: 离开延迟回到 ~300ms 内; 缓慢接近/停边界仍不跳。
2026-09-11 17:06:05 +08:00

352 lines
19 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* test_vac_ref_reanchor.c — V4.36.41 补丁草案单测 (gcc 隔离, DLD154V4B)
*
* 镜像 = TaskLoop.c V4.36.41 草案: vac_ref() + origin_set_atomic() + env_resync_apply()
* 覆盖 5 件事:
* ① 现象复现: env 重校准(黄灯常亮补偿)后判定参考未同步 ⇒ 进入线被拖低 Δ (修复前)
* ② 补丁效果: origin_set_atomic 同步 ⇒ 进入线立即回到 Origin−dlt (与断电上电同口径)
* ③ 回归: V4.36.33 的 1×dlt 门控语义不变; 小步跳变不吃掉 EMA 跟踪; 上电 EMA=0 语义
* ④ 新发现(2026-09-11): EMA 整数步进 d/128 有 ±127 counts 死区 ⇒ 真空 EMA 长期停在
* 真空下方 ~127 counts(≈2.6×dlt) ⇒ vac_ref 偏低 ⇒ 触发高度持续漂移(断电才复位)
* ⑤ 修复 D(余数累加器) 后 EMA 无死区收敛到 0 counts 偏差
*
* 编译: gcc -O0 -Wall -o /tmp/t_vac tests/test_vac_ref_reanchor.c && /tmp/t_vac
* 镜像宏与固件一致(改 VAC_/ORIGIN_JUMP_/SENS 时同步此处!)
*/
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdint.h>
/* ===== 镜像固件宏 (TaskLoop.h / TaskLoop.c) ===== */
static const uint16_t SensTable[4] = { 337, 71, 38, 25 }; /* SENS3 = 25 */
#define SENS_LEVEL 3
#define VAC_REF_DELTA_DLT 1 /* V4.36.33: vac_ref 重锚阈值 (×dlt) */
#define ORIGIN_JUMP_REANCH_DLT 1 /* V4.36.41 草案: 原子换 Origin 重锚阈值 (×dlt) */
#define VACUUM_EMA_ALFA 128 /* V4.36.31: EMA 平滑因子 (τ≈1.28s@10ms) */
#define REL_BAND_LO_MULT 180 /* 镜像 TaskLoop.h (V4.36.28 现场验证; G2 只夹紧不改它) */
/* ===== 镜像全局 ===== */
static uint32_t loop1_Origin = 0, loop1_CAPVD = 0, loop1_dlt_ORG = 0;
static uint32_t g_vacuum_ema = 0;
static int32_t g_vac_frac = 0; /* 修复 D: EMA 余数累加器 */
static uint32_t calc_dlt(uint32_t origin)
{
return (uint32_t)(((uint64_t)origin * SensTable[SENS_LEVEL]) >> 16);
}
/* ===== 镜像: vac_ref() (TaskLoop.c:287-299) ===== */
static uint32_t vac_ref(void)
{
if (g_vacuum_ema == 0) return loop1_Origin;
if ((int32_t)loop1_Origin - (int32_t)g_vacuum_ema >
(int32_t)(loop1_dlt_ORG * VAC_REF_DELTA_DLT))
return g_vacuum_ema;
return loop1_Origin;
}
/* 进入线 (TaskLoop.c:1432 的判定阈值面) */
static uint32_t enter_line(void) { return vac_ref() - loop1_dlt_ORG; }
/* ===== 镜像: origin_set_atomic() (V4.36.41 草案, B1 护栏) ===== */
static void origin_set_atomic(uint32_t new_origin)
{
int32_t _jump = (int32_t)new_origin - (int32_t)loop1_Origin;
if (_jump < 0) _jump = -_jump;
loop1_Origin = new_origin;
if (g_vacuum_ema == 0 || loop1_dlt_ORG == 0 ||
_jump > (int32_t)(loop1_dlt_ORG * ORIGIN_JUMP_REANCH_DLT)) {
g_vacuum_ema = new_origin;
g_vac_frac = 0;
}
}
/* ===== 镜像: env_resync_apply() 修复前 / 修复后 ===== */
static void env_resync_apply_old(uint32_t new_origin) /* V4.36.40: 只换 Origin */
{
loop1_Origin = new_origin;
}
static void env_resync_apply_new(uint32_t new_origin) /* V4.36.41 草案 (A+B) */
{
origin_set_atomic(new_origin);
}
/* ===== 镜像: EMA 步进 —— 现状公式 vs 修复 D ===== */
static void ema_step_cur(void) /* V4.36.35 现状: d/128 整数截断 (死区 ±127) */
{
int32_t d = (int32_t)loop1_CAPVD - (int32_t)g_vacuum_ema;
int32_t lim = (int32_t)(loop1_dlt_ORG * 3);
if (d > lim) d = lim; else if (d < -lim) d = -lim;
if (d != 0) g_vacuum_ema += (uint32_t)(d / VACUUM_EMA_ALFA);
}
static void ema_step_fix(void) /* 修复 D: 余数累加 (无死区) */
{
int32_t d = (int32_t)loop1_CAPVD - (int32_t)g_vacuum_ema;
int32_t lim = (int32_t)(loop1_dlt_ORG * 3);
if (d > lim) d = lim; else if (d < -lim) d = -lim;
if (d != 0) {
g_vac_frac += d;
int32_t st = g_vac_frac / VACUUM_EMA_ALFA;
g_vac_frac -= st * VACUUM_EMA_ALFA;
g_vacuum_ema += (uint32_t)st;
}
}
/* ===== 夹具 ===== */
#define VAC_TRUE 130506UL /* 真实真空 CAPVD (devlog J1 实测) */
#define VAC_ANCHOR 130636UL /* env 学习后 Origin (锚高 130 counts = 2.65×dlt) */
static void reset_all(uint32_t origin)
{
loop1_Origin = origin;
loop1_CAPVD = origin;
loop1_dlt_ORG = calc_dlt(origin);
g_vacuum_ema = origin;
g_vac_frac = 0;
}
/* ================= 场景 6: vac_ref 硬开关抖动(缓慢接近/悬停)=================
* V4.36.41 现场发现: 板缓慢接近时判据"乱跳"。机理 = EMA 跟随近场金属向下时,
* vac_ref 的 "min + 1×dlt" 硬开关被反复扫过 ⇒ 进入线跳变(±40~150 counts) ⇒ 抖动。
* mode: 0=旧截断(死区) 1=D余数累加 2=E单向(只上不下); profile: 0=悬停 1=手抖 2=极慢接近
*/
static int sim_switch_cnt(int mode, int profile)
{
uint32_t Origin = VAC_ANCHOR, EMA = VAC_ANCHOR, capvd;
int32_t frac = 0, d;
int i, cnt500 = 0, prev_ref = -1, sw = 0;
for (i = 0; i < 3000; i++) { /* 30s @10ms */
int x;
if (profile == 0) x = (i < 400) ? i * 3 / 10 : 120; /* 4s 接近到 x=120 后悬停 */
else if (profile == 1) x = ((i % 300) < 150) ? 20 + (i % 300) * 100 / 150 /* 手抖 20~120, 周期 3s */
: 120 - ((i % 300) - 150) * 100 / 150;
else x = ((i < 1000) ? i * 200 / 1000 : 200) + ((i % 200 < 100) ? 8 : -8);
capvd = VAC_ANCHOR - (uint32_t)x;
d = (int32_t)capvd - (int32_t)EMA;
if (d > 3 * (int32_t)loop1_dlt_ORG) d = 3 * (int32_t)loop1_dlt_ORG;
else if (d < -3 * (int32_t)loop1_dlt_ORG) d = -3 * (int32_t)loop1_dlt_ORG;
++cnt500; if (cnt500 >= 500) { Origin = capvd; cnt500 = 0; } /* Origin 块平均 5s */
if (d != 0) {
if (mode == 0) EMA += (uint32_t)(d / 128);
else if (mode == 1) { frac += d;
int32_t st = frac / 128; frac -= st * 128; EMA += (uint32_t)st; }
else if (d > 0) { frac += d; /* E: 只上不下 */
int32_t st = frac / 128; frac -= st * 128; EMA += (uint32_t)st; }
}
{
int ref = ((int32_t)Origin - (int32_t)EMA > (int32_t)loop1_dlt_ORG) ? (int)EMA : (int)Origin;
if (prev_ref >= 0 && ref != prev_ref) ++sw;
prev_ref = ref;
}
}
return sw;
}
static void test_chatter(void)
{
int p, s_old, s_frac, s_one;
for (p = 0; p < 3; p++) {
s_old = sim_switch_cnt(0, p);
s_frac = sim_switch_cnt(1, p);
s_one = sim_switch_cnt(2, p);
printf(" [抖动] profile%d vac_ref 切换/30s: 旧死区=%d D余数=%d E单向=%d\n", p, s_old, s_frac, s_one);
assert(s_one <= 8); /* E: 抖动回到旧水平 */
assert(s_frac > s_one); /* D 单独: 显著放大 = 本次现场"乱跳" */
}
printf("场景 6: vac_ref 抖动 ✓ 通过 (E 单向 ≤8 次/30s; D 余数显著放大, 保持旧死区水平)\n");
}
/* ================= 场景 7: 低Q 临界悬停自激(现场日志 154log260911a 复现)=================
* ⚠ 能力边界: 本镜像只验证**算法语义**, 验证不了**固件里的执行路径/放置位置**。
* V4.36.42 的 G3 就栽在这: g_rel_hold_tick-- 误放"无车"分支 ⇒ 有车期间常驻 ⇒ 正常释放被永久挡住,
* 只能靠 band_timeout(现场实测离开延迟 3.5~4.7s)。此类问题必须靠日志的 Car_OFF 原因字段暴露。
* ⇒ 凡"进入后 N tick 生效"的守卫, 改完先查递减点是否在每 tick 必经路径上。
* 现场: Origin=129254 进表 dlt=49 释表 dlt=23 TEMA≈129220 CAPVD≈129204(深 50)
* 现状(V4.36.41): 进门瞬间 dlt_ORG 切成 23 ⇒ vac_ref() 门控由 49 变 23 ⇒ 同意偏差(34)下参考从
* Origin 阶跃到 EMA ⇒ 释放线 EMA-41=129179 比进入线 Origin-49=129205 还浅
* ⇒ 深度 50~58 的静止凹脉冲"进门即满足释放" ⇒ 100ms 周期自激
* G1: 门控用状态无关进表 dlt; G2: 带回夹紧 ≤ 进表 dlt(不动 180=B4.36.28 的 41); G3: 进入后 300ms 禁释放
*/
static uint32_t gate_dlt(uint32_t origin, int sens) { return (uint32_t)(((uint64_t)origin * SensTable[sens]) >> 16); }
static void sim_flap_x(int mode, int n, uint32_t (*capvd_at)(int), uint32_t org, int32_t ema0, int *p_in, int *p_off)
{
uint32_t Origin = org; int32_t EMA = ema0, frac = 0;
uint32_t dlt_ent = gate_dlt(Origin, SENS_LEVEL), dlt_rel = (uint32_t)(((uint64_t)Origin * 12) >> 16);
uint8_t vd = 0; int i, cnt_in = 0, cnt_off = 0, ent = 0, rel = 0, hold = 0, freeze = 0;
for (i = 0; i < n; i++) {
uint32_t capvd = capvd_at(i), ref, d = dlt_ent;
if (mode == 0 && vd) d = dlt_rel; /* 现状: 门控用"当前状态"dlt */
ref = ((int32_t)Origin - EMA > (int32_t)(d * VAC_REF_DELTA_DLT)) ? (uint32_t)EMA : Origin;
if (!vd) {
if (capvd < ref - dlt_ent) { if (++ent >= 5) { vd = 1; ent = 0; cnt_in++; hold = (mode >= 3) ? 30 : 0; } }
else ent = 0;
if (freeze > 0) freeze--; /* 释放后 1s 全冻结(REL_ORIGIN_FREEZE_TICKS) */
else if (capvd > Origin - 4*dlt_ent && capvd < Origin + 4*dlt_ent) { /* 对称窗内 EMA 跟踪(D: 余数累加) */
int32_t dd = (int32_t)capvd - EMA, lim = (int32_t)(3*dlt_ent);
if (dd > lim) dd = lim;
if (dd < -lim) dd = -lim;
frac += dd; { int32_t st = frac / VACUUM_EMA_ALFA; frac -= st * VACUUM_EMA_ALFA; EMA += st; }
}
} else {
if (hold > 0) { hold--; rel = 0; }
else {
uint32_t band = (uint32_t)(((uint64_t)dlt_rel * REL_BAND_LO_MULT) / 100); /* 41 */
if (mode >= 2 && band > dlt_ent) band = dlt_ent - 1; /* G2 兜底夹紧 */
/* mode 4 = 非低Q(QLO=0): 释放线 = Origin dlt_rel, 不经 vac_ref ⇒ 结构上无重叠 */
uint32_t line = (mode == 4) ? (Origin - dlt_rel) : (ref - band);
if (line < capvd) { if (++rel >= 5) { vd = 0; rel = 0; cnt_off++; freeze = 100; } }
else rel = 0;
}
}
}
*p_in = cnt_in; *p_off = cnt_off;
}
static void sim_flap(int mode, int n, uint32_t (*capvd_at)(int), int *p_in, int *p_off)
{ sim_flap_x(mode, n, capvd_at, 129254, 129220, p_in, p_off); } /* 22µH/163kHz 现场参数 */
static uint32_t tl_hold(int i) { (void)i; return 129204; } /* 静止悬停 深 50 */
static uint32_t tl_rebound(int i){ return i < 50 ? 129194 : 129218; } /* 深 60 → 回摆到 Origin-36 */
static uint32_t tl_leave(int i) { return i < 50 ? 129194 : 129254; } /* 深 60 → 撤板 */
static uint32_t tl_hold35(int i) { (void)i; return 129088; } /* 35µH/128kHz: Origin 129141 深 53 */
static uint32_t tl_leave35(int i){ return i < 50 ? 129070 : 129141; } /* 35µH 撤板 */
static void test_lowq_limit_cycle(void)
{
int i, ci, co, ci22 = 0, ci35 = 0;
printf("\n--- 场景 7: 低Q 临界悬停自激(现场参数逐位复现)---\n");
struct { int mode; const char *name; } M[4] = {
{0, "现状 V4.36.41"}, {1, "G1"}, {2, "G1+G2"}, {3, "G1+G2+G3"} };
printf(" 静止悬停(深 50) 3s: ");
for (i = 0; i < 4; i++) { sim_flap(M[i].mode, 3000, tl_hold, &ci, &co);
printf("[%s: Car_In %d / OFF %d] ", M[i].name, ci, co); }
printf("\n");
sim_flap(0, 3000, tl_hold, &ci, &co);
assert(ci >= 10); /* 现状必然自激(现场日志 ~30 次/3s) */
sim_flap(3, 3000, tl_hold, &ci, &co);
assert(ci == 1 && co == 0); /* 修后: 进入一次, 悬停期间不释放 */
sim_flap(3, 3000, tl_leave, &ci, &co);
assert(ci == 1 && co == 1); /* 撤板仍能释放 */
sim_flap(2, 3000, tl_rebound, &ci, &co);
assert(co >= 1); /* V4.36.28 回摆释放(VACVD Origin-36)不被打破 */
sim_flap(0, 3000, tl_hold, &ci, &co); { ci22 = ci; }
printf(" 35µH/128kHz 悬停(深 53) 3s: ");
sim_flap_x(0, 3000, tl_hold35, 129141, 129109, &ci, &co); printf("[现状: In %d / OFF %d] ", ci, co);
sim_flap_x(3, 3000, tl_hold35, 129141, 129109, &ci, &co); printf("[G1+G2+G3: In %d / OFF %d]\n", ci, co);
sim_flap_x(0, 3000, tl_hold35, 129141, 129109, &ci, &co); assert(ci >= 10); /* 35µH 同样自激 */
ci35 = ci;
sim_flap_x(3, 3000, tl_hold35, 129141, 129109, &ci, &co); assert(ci == 1 && co == 0);
sim_flap_x(3, 3000, tl_leave35, 129141, 129109, &ci, &co); assert(ci == 1 && co == 1);
printf(" 现状自激: 22µH Car_In=%d 次/3s, 35µH Car_In=%d 次/3s (≥10 OK, 现场日志同为 ~100ms 周期)\n", ci22, ci35);
sim_flap_x(4, 3000, tl_hold, 129254, 129220, &ci, &co);
assert(ci == 1 && co == 0); /* 判别预测: 非低Q(f<100kHz) 结构上不自激 */
printf(" 判别预测 OK: 非低Q(f<100kHz) 同深度悬停不自激(1/0) ⇒ 现场换 L≳68µH 应完全不跳\n");
printf(" 修后: 悬停 1/0 OK | 撤板 1/1 OK | V4.36.28 回摆仍释放 OK\n");
}
int main(void)
{
printf("test_vac_ref_reanchor (V4.36.42: A+B 同步 / D 死区 / E 单向 / G1+G2+G3 低Q自激)\n");
/* ================= 场景 1: 复现 + 补丁效果 ================= */
reset_all(VAC_ANCHOR);
uint32_t dlt_anchor = loop1_dlt_ORG;
uint32_t dlt_true = calc_dlt(VAC_TRUE);
printf("1. 参数: VAC_TRUE=%u(真空) VAC_ANCHOR=%u(补偿后 Origin) Δ=%u counts | dlt=%u(SENS3)\n",
(unsigned)VAC_TRUE, (unsigned)VAC_ANCHOR, (unsigned)(VAC_ANCHOR - VAC_TRUE),
(unsigned)dlt_anchor);
reset_all(VAC_TRUE); /* 补偿前: Origin=真空, EMA=真空 */
env_resync_apply_old(VAC_ANCHOR); /* 修复前: 只换 Origin, EMA 停旧真空 */
uint32_t line_old = enter_line();
printf(" ① 修复前: Origin=%u EMA=%u 进入线=%u | 正确应为 %u ⇒ 低 %u counts"
" = %.2f×dlt = %.3f%% Δf/f\n",
(unsigned)loop1_Origin, (unsigned)g_vacuum_ema, (unsigned)line_old,
(unsigned)(VAC_ANCHOR - dlt_anchor),
(unsigned)((VAC_ANCHOR - dlt_anchor) - line_old),
((VAC_ANCHOR - dlt_anchor) - line_old) / (double)dlt_anchor,
((VAC_ANCHOR - dlt_anchor) - line_old) * 100.0 / VAC_ANCHOR);
assert(g_vacuum_ema == VAC_TRUE); /* EMA 未同步(缺陷) */
assert(line_old == VAC_TRUE - dlt_true); /* 进入线被留在旧真空 */
assert((VAC_ANCHOR - dlt_anchor) - line_old == 130); /* 整整低 Δ=130 */
reset_all(VAC_TRUE);
env_resync_apply_new(VAC_ANCHOR); /* 修复后 (A+B) */
printf(" ② 修复后: Origin=%u EMA=%u 进入线=%u | 与断电上电口径一致 ⇒ 低 0 counts\n",
(unsigned)loop1_Origin, (unsigned)g_vacuum_ema, (unsigned)enter_line());
assert(g_vacuum_ema == VAC_ANCHOR);
assert(vac_ref() == VAC_ANCHOR);
assert(enter_line() == VAC_ANCHOR - dlt_anchor);
printf("1. 复现+修复 OK(补偿后进入线由'低 Δ'恢复为 Origindlt\n");
/* ================= 场景 2: B1 护栏(大步重锚 / 小步不动) ================= */
reset_all(VAC_TRUE);
origin_set_atomic(VAC_TRUE - 20); /* 步长 20 ≤ 1×dlt ⇒ EMA 不动 */
printf("2. 小步 20(≤1×dlt): Origin=%u EMA=%u ⇒ EMA 未动 ✓\n",
(unsigned)loop1_Origin, (unsigned)g_vacuum_ema);
assert(g_vacuum_ema == VAC_TRUE);
origin_set_atomic(VAC_TRUE + 130); /* 步长 150 > 1×dlt ⇒ 重锚 */
printf(" 大步 150(>1×dlt): Origin=%u EMA=%u ⇒ 已重锚 ✓\n",
(unsigned)loop1_Origin, (unsigned)g_vacuum_ema);
assert(g_vacuum_ema == VAC_TRUE + 130);
printf("2. 护栏 OK(不吃掉 EMA 的正常跟踪,也不容忍大偏差)\n");
/* ================= 场景 3: V4.36.33 门控语义回归 ================= */
reset_all(VAC_ANCHOR);
g_vacuum_ema = VAC_ANCHOR - 30; /* 边缘悬停拉低 0.61×dlt */
printf("3. OriginEMA=30(0.61×dlt): vac_ref=%u ⇒ 用 Origin ✓(门控保留)\n",
(unsigned)vac_ref());
assert(vac_ref() == VAC_ANCHOR);
g_vacuum_ema = VAC_ANCHOR - 130; /* 老锚错 2.65×dlt */
printf(" OriginEMA=130(2.65×dlt): vac_ref=%u ⇒ 用 EMA ✓(老场景保留)\n",
(unsigned)vac_ref());
assert(vac_ref() == VAC_ANCHOR - 130);
printf("3. 门控回归 OK\n");
/* ================= 场景 4: 上电/判稳语义 ================= */
reset_all(VAC_ANCHOR);
g_vacuum_ema = 0; /* INIT 清零 */
assert(vac_ref() == VAC_ANCHOR);
g_vacuum_ema = loop1_Origin; /* 判稳完成建立初值 */
assert(vac_ref() == VAC_ANCHOR && enter_line() == VAC_ANCHOR - loop1_dlt_ORG);
printf("4. 上电(EMA=0→Origin)语义 OK: 进入线=%u\n", (unsigned)enter_line());
/* ================= 场景 5: EMA 整数步进死区(新发现) ================= */
reset_all(VAC_ANCHOR);
loop1_CAPVD = VAC_ANCHOR; /* 真空就在 Origin */
g_vacuum_ema = VAC_ANCHOR - 200; /* 被拖低 200 counts 后追赶 */
{ int i; for (i = 0; i < 4000; i++) ema_step_cur(); }
{ uint32_t gap = loop1_Origin - g_vacuum_ema;
printf("5. 现状公式 d/128 追赶 4000 样本(40s): EMA=%u 停在真空下方 %u counts"
" (%.2f×dlt) ⇒ vac_ref=%s ⇒ 进入线低 %u counts\n",
(unsigned)g_vacuum_ema, (unsigned)gap, gap / (double)loop1_dlt_ORG,
(vac_ref() == loop1_Origin) ? "Origin" : "EMA", (unsigned)gap);
assert(gap == 127); /* 死区: |d|<128 ⇒ 步进 0 */
assert(vac_ref() == g_vacuum_ema); /* 127 > 1×dlt ⇒ 判定参考被拉低 */
assert(enter_line() == g_vacuum_ema - loop1_dlt_ORG);
}
reset_all(VAC_ANCHOR);
loop1_CAPVD = VAC_ANCHOR;
g_vacuum_ema = VAC_ANCHOR - 200;
g_vac_frac = 0;
{ int i; for (i = 0; i < 4000; i++) ema_step_fix(); }
{ uint32_t gap = loop1_Origin - g_vacuum_ema;
printf(" 修复 D(余数累加)同条件: EMA=%u 偏差 %u counts ⇒ vac_ref=%s ⇒ 进入线=%u ✓\n",
(unsigned)g_vacuum_ema, (unsigned)gap,
(vac_ref() == loop1_Origin) ? "Origin" : "EMA", (unsigned)enter_line());
assert(gap == 0);
assert(vac_ref() == loop1_Origin);
assert(enter_line() == loop1_Origin - loop1_dlt_ORG);
}
printf("5. 死区实测 OK(现状 127 counts 常驻偏差 → 修复 D 归零)\n");
/* ================= 场景 6: vac_ref 硬开关抖动 ================= */
test_chatter();
/* ================= 场景 7: 低Q 临界悬停自激 ================= */
test_lowq_limit_cycle();
printf("ALL PASS (A+B 同步 / 护栏 / 门控回归 / 上电语义 / 死区 / 抖动 / 低Q自激)\n");
return 0;
}