Files
DLD154V4B/tests/test_env_resync.c
T
wangfq 672f143e99 feat(V4B): V4.24 — 环境重校准:永久环境变化自动恢复基线(金属板A拿走场景)
现场(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 同步
2026-09-02 18:10:28 +08:00

280 lines
10 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_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;
}