现场(2026-09-02实测): 线圈旁金属板A上电把Origin学低(128096),A拿走CAPVD弹回 真空场(128856) dev=+761(0.59%),被36321a1双向保护"dev>0永不更新"永久冻结 → B触发需809cnt(正常54cnt) 灵敏度假性降低且不恢复(V4.20分钟级可自愈) 修复: dev>+4dlt高位两段式受控重校准 ① 60s(ENV_RESYNC_WAIT)高位连续未回摆 → 判定永久环境变化(瞬态铁块类不污染) ② 学习基线B: 连续空闲窗200tick均值 + settle判稳(连续2窗漂移≤0.1%) ③ 空闲原子替换Origin, 清freeze/ORG/entry残留, slow snap, 黄灯恢复 - 学习期打断(回窗/dev<0/断线重连)→重算; 30s预算超时放弃保留旧Origin - 黄灯模式4: 环境学习中常亮(优先级: 断开快闪>学习常亮>N短闪>灭) - tests/test_env_resync.c 5场景ALL PASS(恢复66s/瞬态不学/打断重算/放弃/不震荡) - docs: devlog V2.22 + spec V2.3 + 产品手册 V2.7 同步
280 lines
10 KiB
C
280 lines
10 KiB
C
/*
|
||
* test_env_resync.c — 环境重校准(受控基线更新) gcc 隔离单测 (DLD154V4B V4.24)
|
||
*
|
||
* 验证对象: TaskLoop.c vd1_task() 无车段 dev>0 分支 (V4.24 两段式)
|
||
* ─ 现场场景 (2026-09-02 实测): 线圈旁金属板A上电 → Origin 学低(128096)
|
||
* A 拿走 → CAPVD 弹回真空场(128856) dev=+761 → 双向保护(36321a1)永不更新
|
||
* → B 触发需 809cnt (正常 54cnt) 灵敏度假性降低
|
||
* ─ 修复: dev>+4dlt 高位连续 60s(ENV_RESYNC_WAIT) 未回摆 → 判定永久环境变化
|
||
* → 学习基线 B (连续空闲窗均值 + settle 判稳) → 空闲原子替换 Origin
|
||
*
|
||
* 运行: gcc -Wall tests/test_env_resync.c && ./a.out
|
||
* 期望: 4 场景 ALL PASS (瞬态 60s 内短停不学; 永久变化 ~66s 恢复)
|
||
*/
|
||
|
||
#include <assert.h>
|
||
#include <stdio.h>
|
||
#include <stdint.h>
|
||
|
||
/*========== 与固件一致的参数宏 (TaskLoop.c vd1_task 内) ==========*/
|
||
#define STABLE_ORIGIN_PPT 1 // 判稳带: 窗口均值漂移 ≤ Origin×1/1000 (0.1%)
|
||
#define STABLE_SETTLE_WINDOWS 2 // 连续 2 窗漂移达标
|
||
#define FREEZE_TIMEOUT 1000 // 打印饱和(dev>0 不再超时更新 Origin)
|
||
#define ENV_RESYNC_WAIT 6000 // V4.24: dev>+4dlt 高位连续 60s → 判定永久环境变化
|
||
#define ENV_LEARN_WINDOW 200 // V4.24: 环境学习窗口 2s
|
||
#define ENV_LEARN_MAX_TICKS 3000 // V4.24: 学习预算 30s, 超时放弃
|
||
|
||
/*========== 与固件同名的 env 全局镜像 (TaskLoop.c :80-90) ==========*/
|
||
static uint8_t g_env_resync = 0;
|
||
static uint16_t g_env_wait_cnt = 0;
|
||
static uint32_t g_env_sum = 0;
|
||
static uint16_t g_env_cnt = 0;
|
||
static uint32_t g_env_prev = 0;
|
||
static uint8_t g_env_settle = 0;
|
||
static uint16_t g_env_tick = 0;
|
||
|
||
/* 场景镜像 (loop1_* 对应量) */
|
||
static uint32_t sim_Origin;
|
||
static uint32_t sim_CAPVD;
|
||
static int32_t sim_dlt; /* 进阈 dlt */
|
||
|
||
/* 事件计数 */
|
||
static int ev_learn_start = 0;
|
||
static int ev_applied = 0;
|
||
static int ev_abort = 0;
|
||
|
||
static void env_reset(void)
|
||
{
|
||
g_env_resync = 0;
|
||
g_env_wait_cnt = 0;
|
||
g_env_sum = 0;
|
||
g_env_cnt = 0;
|
||
g_env_prev = 0;
|
||
g_env_settle = 0;
|
||
g_env_tick = 0;
|
||
}
|
||
|
||
/* 与 TaskLoop.c env_resync_abort/apply 逐行一致 */
|
||
static void env_resync_abort(void)
|
||
{
|
||
g_env_resync = 0;
|
||
g_env_sum = 0;
|
||
g_env_cnt = 0;
|
||
g_env_prev = 0;
|
||
g_env_settle = 0;
|
||
g_env_tick = 0;
|
||
}
|
||
|
||
static void env_resync_apply(uint32_t new_origin)
|
||
{
|
||
(void)new_origin;
|
||
/* 固件此处打印 + 替换 Origin + 清 freeze/ORG/entry + slow snap */
|
||
ev_applied++;
|
||
env_resync_abort();
|
||
}
|
||
|
||
/*========== 模拟 vd1_task 无车段基线块 env 逻辑 (逐行复刻 V4.24) ==========*/
|
||
static void sim_tick(void)
|
||
{
|
||
int32_t dev = (int32_t)sim_CAPVD - (int32_t)sim_Origin;
|
||
int32_t freeze_band = sim_dlt * 4;
|
||
|
||
if (dev < freeze_band && dev > -freeze_band) {
|
||
/* 对称窗口内 → 正常基线跟踪 (env 相关) */
|
||
g_env_wait_cnt = 0;
|
||
if (g_env_resync) ev_abort++;
|
||
env_resync_abort();
|
||
} else if (dev < 0) {
|
||
/* 车方向 → 冻结 (env 相关) */
|
||
g_env_wait_cnt = 0;
|
||
if (g_env_resync) ev_abort++;
|
||
env_resync_abort();
|
||
} else {
|
||
/* dev>0 高位 — V4.24 两段式 */
|
||
if (!g_env_resync) {
|
||
g_env_wait_cnt++;
|
||
if (g_env_wait_cnt >= ENV_RESYNC_WAIT) {
|
||
g_env_resync = 1;
|
||
g_env_wait_cnt = 0;
|
||
g_env_sum = 0;
|
||
g_env_cnt = 0;
|
||
g_env_prev = 0;
|
||
g_env_settle = 0;
|
||
g_env_tick = 0;
|
||
ev_learn_start++;
|
||
}
|
||
} else {
|
||
g_env_tick++;
|
||
g_env_sum += sim_CAPVD;
|
||
g_env_cnt++;
|
||
if (g_env_cnt >= ENV_LEARN_WINDOW) {
|
||
uint32_t _m = g_env_sum / g_env_cnt;
|
||
uint32_t _drift, _band;
|
||
g_env_sum = 0;
|
||
g_env_cnt = 0;
|
||
if (g_env_prev != 0) {
|
||
_drift = (_m > g_env_prev) ? (_m - g_env_prev) : (g_env_prev - _m);
|
||
_band = _m * STABLE_ORIGIN_PPT / 1000;
|
||
if (_drift <= _band) {
|
||
g_env_settle++;
|
||
} else {
|
||
g_env_settle = 0;
|
||
}
|
||
if (g_env_settle >= STABLE_SETTLE_WINDOWS) {
|
||
sim_Origin = _m; /* env_resync_apply 替换 */
|
||
env_resync_apply(_m);
|
||
return; /* 本 tick 不再走进入判定 */
|
||
}
|
||
}
|
||
g_env_prev = _m;
|
||
}
|
||
if (g_env_tick >= ENV_LEARN_MAX_TICKS) {
|
||
ev_abort++;
|
||
env_resync_abort();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/*========== 测试工具 ==========*/
|
||
static void t_set(uint32_t origin, uint32_t capvd, int32_t dlt)
|
||
{
|
||
sim_Origin = origin;
|
||
sim_CAPVD = capvd;
|
||
sim_dlt = dlt;
|
||
env_reset();
|
||
ev_learn_start = 0;
|
||
ev_applied = 0;
|
||
ev_abort = 0;
|
||
}
|
||
|
||
/*========== 场景 ==========*/
|
||
static void test_permanent_change_recovers(void)
|
||
{
|
||
/* ① 常量高位稳定 (A 拿走): Origin=128095, 空场 CAPVD=128856, dev=+761>192 */
|
||
t_set(128095UL, 128856UL, 48);
|
||
|
||
int tick = 0;
|
||
for (; tick < 7000 && !ev_applied; tick++)
|
||
sim_tick();
|
||
|
||
printf("常量高位稳定: 学习启动=%d 替换=%d 替换tick=%d resync=%d\n",
|
||
ev_learn_start, ev_applied, tick, g_env_resync);
|
||
assert(ev_learn_start == 1);
|
||
assert(ev_applied == 1);
|
||
assert(tick == 6600); /* 6000 等待 + 3×200 窗 */
|
||
assert(sim_Origin == 128856UL);
|
||
assert(g_env_resync == 0); /* 替换后复位, 黄灯灭 */
|
||
|
||
/* 替换后 dev≈0 → 走窗口分支, 不再进 freeze/学习 */
|
||
for (int i = 0; i < 500; i++)
|
||
sim_tick();
|
||
assert(ev_applied == 1); /* 不再重复替换 */
|
||
printf(" 场景1 PASS — 永久环境变化 ~66s 自动恢复, Origin 128095->128856\n\n");
|
||
}
|
||
|
||
static void test_transient_never_learns(void)
|
||
{
|
||
/* ② 瞬态高位短停 (铁块模拟): 40s(4000tick) 高位后回摆 → 不学习 */
|
||
t_set(128095UL, 128856UL, 48);
|
||
for (int i = 0; i < 4000; i++)
|
||
sim_tick();
|
||
assert(ev_learn_start == 0);
|
||
|
||
/* 回摆进窗口 (CAPVD 回 Origin 附近), wait 清零 */
|
||
sim_CAPVD = 128100UL; /* dev=+5 窗口内 */
|
||
sim_tick();
|
||
assert(g_env_wait_cnt == 0);
|
||
|
||
/* 再次高位 59s(5900tick) — 仍不足 60s 连续 → 不学 */
|
||
sim_CAPVD = 128856UL;
|
||
for (int i = 0; i < 5900; i++)
|
||
sim_tick();
|
||
assert(ev_learn_start == 0);
|
||
assert(ev_applied == 0);
|
||
assert(sim_Origin == 128095UL);
|
||
printf("瞬态高位短停: 不学习不替换, Origin 保持 (等待计数=%d)\n", g_env_wait_cnt);
|
||
printf(" 场景2 PASS — 铁块类瞬态(60s内回摆)不触发重校准\n\n");
|
||
}
|
||
|
||
static void test_learn_interrupted_restarts(void)
|
||
{
|
||
/* ③ 学习期打断重算: 60s 后进入学习, 途中目标靠近(回窗) → abort → 重来并最终恢复 */
|
||
t_set(128095UL, 128856UL, 48);
|
||
|
||
/* 第一次 60s + 学习 100 tick 后, B 靠近把 CAPVD 压回窗口 */
|
||
for (int i = 0; i < 6000; i++)
|
||
sim_tick(); /* 进入学习 */
|
||
assert(g_env_resync == 1);
|
||
for (int i = 0; i < 100; i++)
|
||
sim_tick(); /* 学习窗1 未完 */
|
||
sim_CAPVD = 128000UL; /* dev=-95 窗口内(B 目标) */
|
||
sim_tick();
|
||
assert(g_env_resync == 0); /* 打断退出, 黄灯灭 */
|
||
assert(ev_abort == 1);
|
||
|
||
/* 目标离开, 高位重新 60s → 学习 3 窗 → 替换 */
|
||
sim_CAPVD = 128856UL;
|
||
for (int i = 0; i < 7000 && !ev_applied; i++)
|
||
sim_tick();
|
||
|
||
printf("学习期打断: abort=%d 重新学习启动=%d 最终替换tick计数=%d Origin=%lu\n",
|
||
ev_abort, ev_learn_start, 0, (unsigned long)sim_Origin);
|
||
assert(ev_applied == 1);
|
||
assert(ev_learn_start == 2);
|
||
assert(sim_Origin == 128856UL);
|
||
printf(" 场景3 PASS — 学习期任何打断都重算, 完成后恢复\n\n");
|
||
}
|
||
|
||
static void test_unstable_learn_gives_up(void)
|
||
{
|
||
/* ④ 学习期每窗均值漂移 >0.1% (CAPVD 高位大幅交替 128856±200..400)
|
||
* → settle 永远到不了 2 → 30s 预算超时放弃, 保留旧 Origin */
|
||
t_set(128095UL, 128856UL, 48);
|
||
for (int i = 0; i < 6000; i++)
|
||
sim_tick(); /* 进入学习 */
|
||
assert(g_env_resync == 1);
|
||
|
||
int tick = 0;
|
||
for (; tick < 3500 && !ev_applied && g_env_resync; tick++) {
|
||
/* 交替高值: 窗与窗之间均值漂移 ~200 > band(~129), 且 dev 恒 >192 */
|
||
sim_CAPVD = ((tick / ENV_LEARN_WINDOW) & 1) ? 129256UL : 129056UL;
|
||
sim_tick();
|
||
}
|
||
|
||
printf("学习期不稳: 预算放弃 tick=%d applied=%d resync=%d Origin=%lu\n",
|
||
tick, ev_applied, g_env_resync, (unsigned long)sim_Origin);
|
||
assert(ev_applied == 0);
|
||
assert(g_env_resync == 0); /* 超时放弃, 黄灯灭 */
|
||
assert(ev_abort >= 1);
|
||
assert(sim_Origin == 128095UL); /* Origin 不被污染 */
|
||
printf(" 场景4 PASS — 学习不稳定 30s 放弃, 宁可灵敏度低不冒险污染\n\n");
|
||
}
|
||
|
||
static void test_no_learning_after_replace(void)
|
||
{
|
||
/* ⑤ 替换后 Origin≈CAPVD → dev≈0 正常窗口跟踪, 不再触发二次学习 */
|
||
t_set(128095UL, 128856UL, 48);
|
||
for (int i = 0; i < 7000 && !ev_applied; i++)
|
||
sim_tick();
|
||
assert(ev_applied == 1);
|
||
|
||
int before = ev_learn_start;
|
||
for (int i = 0; i < 6000; i++)
|
||
sim_tick(); /* 新 Origin 下高位条件消失 */
|
||
assert(ev_learn_start == before); /* 无新学习 */
|
||
printf("替换后稳态: 高位条件消失, 无重复学习 (Origin=%lu)\n", (unsigned long)sim_Origin);
|
||
printf(" 场景5 PASS — 恢复后进入正常跟踪, 不震荡\n\n");
|
||
}
|
||
|
||
int main(void)
|
||
{
|
||
test_permanent_change_recovers();
|
||
test_transient_never_learns();
|
||
test_learn_interrupted_restarts();
|
||
test_unstable_learn_gives_up();
|
||
test_no_learning_after_replace();
|
||
printf("ALL PASS (5 scenarios)\n");
|
||
return 0;
|
||
}
|