Files
DLD154V4B/tests/test_env_resync.c
T
wangfq 7fd120fdf3 feat(V4B): V4.25 — 环境异常黄灯预判: 高位持续5s即常亮(将进入重学)
背景: V4.24 黄灯只在60s进入学习后才亮, 前60s操作员无可视反馈
改动: poll_yellow_led 模式4条件放宽 = g_env_resync || g_env_wait_cnt>=ENV_WARN_WAIT
  - ENV_WARN_WAIT=500tick(~5s) 放 TaskLoop.h 文件级(poll_yellow_led 可见)
  - dev>+4dlt 高位连续5s未回摆 → 黄灯预判常亮; 累计60s才真正学习(黄灯保持)
  - 预判≠学习: 回摆/目标靠近(wait清零) → 黄灯即灭, 不触发学习
tests/test_env_resync.c 扩展: 预判点亮时刻=500tick / 瞬态4000tick预判亮但不学
  / 打断回摆后预判灭; 5场景ALL PASS + 语法0 error
docs: devlog V2.23 + spec V2.4(ENV_WARN_WAIT行) + 产品手册 V2.8 同步
2026-09-02 18:40:39 +08:00

297 lines
11 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_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_WARN_WAIT 500 // V4.25: 高位连续 ~5s → 黄灯预判常亮 (TaskLoop.h)
#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;
}
/* V4.25: 黄灯模式4 条件镜像 (TaskLoop.c poll_yellow_led) */
static int env_warn_active(void)
{
return g_env_resync || g_env_wait_cnt >= ENV_WARN_WAIT;
}
/* 与 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;
int warn_at = 0;
for (; tick < 7000 && !ev_applied; tick++) {
sim_tick();
if (!warn_at && env_warn_active())
warn_at = tick + 1; /* 首次黄灯预判条件成立 (wait>=500) */
}
printf("常量高位稳定: 黄灯预判=%d 学习启动=%d 替换=%d 替换tick=%d resync=%d\n",
warn_at, ev_learn_start, ev_applied, tick, g_env_resync);
assert(warn_at == 500); /* V4.25: 高位 5s 即黄灯常亮预判 */
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) 高位后回摆 → 不学习
* 但 5s 后黄灯预判已亮 (wait>=500) — 预判亮 ≠ 进入学习 */
t_set(128095UL, 128856UL, 48);
for (int i = 0; i < 4000; i++)
sim_tick();
assert(ev_learn_start == 0);
assert(env_warn_active() == 1); /* 黄灯预判已亮 (wait=4000) */
printf("瞬态高位短停: 4000tick 时黄灯预判亮=%d (未学习)\n", env_warn_active());
/* 回摆进窗口 (CAPVD 回 Origin 附近), wait 清零 → 黄灯预判灭 */
sim_CAPVD = 128100UL; /* dev=+5 窗口内 */
sim_tick();
assert(g_env_wait_cnt == 0);
assert(env_warn_active() == 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);
assert(env_warn_active() == 0); /* wait 清零, 黄灯预判灭 */
/* 目标离开, 高位重新 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;
}