Files
DLD154V4B/tests/test_env_resync.c
T
wangfq 47cb1d944e fix(V4B): V4.25 黄灯预判改锁存语义 — 触发后回摆保持常亮, 环境空闲稳定5s才灭
原(V4.25首个提交): 黄灯条件 wait_cnt>=5s, 回摆(wait清零)即灭 — 用户反馈应锁存:
一旦5s黄灯常亮, 高位回摆/目标靠近也保持常亮; 只有"新的没有变化的环境"
超过5s 确认恢复, 黄灯才灭。

改动:
- 新增 g_env_warn 预判锁存标志(只增不清): dev>+4dlt 高位累计满
  ENV_WARN_WAIT=500tick(~5s) → g_env_warn=1, 黄灯常亮
- 新增 g_env_recover_cnt 恢复确认: 环境回窗口内且 dev 未进进入线(>-dlt)
  连续 ENV_RECOVER_WAIT=500tick(~5s) → 解除锁存, 黄灯灭
  - 目标进确认区(dev<-dlt)/深压(dev<-4dlt) → 恢复计时清零
  - 高位重现 → 恢复计时清零, 黄灯保持
- abort/apply 均不清 g_env_warn (替换后也需恢复确认5s才灭)
- 黄灯模式4条件 = g_env_resync || g_env_warn
tests 5 场景: 回摆保持常亮→稳定5s灭→高位重现再锁存; 学习打断/放弃后锁存保持
docs: devlog V2.23 + spec V2.4 + 手册 V2.8 同步锁存语义
2026-09-02 19:03:49 +08:00

336 lines
14 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_RECOVER_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;
static uint8_t g_env_warn = 0;
static uint16_t g_env_recover_cnt = 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;
g_env_warn = 0;
g_env_recover_cnt = 0;
}
/* V4.25: 黄灯模式4 条件镜像 (TaskLoop.c poll_yellow_led) — 预判锁存 */
static int env_warn_active(void)
{
return g_env_resync || g_env_warn;
}
/* 与 TaskLoop.c env_resync_abort/apply 逐行一致 */
static void env_resync_abort(void)
{
/* V4.25: abort 只退出学习, 不清预判锁存 g_env_warn (环境未恢复前黄灯保持) */
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();
/* V4.25: 恢复确认 — 空闲(dev>-dlt)连续 5s → 解除预判锁存 */
if (dev > -(int32_t)sim_dlt) {
if (g_env_warn) {
g_env_recover_cnt++;
if (g_env_recover_cnt >= ENV_RECOVER_WAIT) {
g_env_warn = 0;
g_env_recover_cnt = 0;
}
}
} else {
g_env_recover_cnt = 0; /* 目标进入确认区 → 恢复计时重来 */
}
} else if (dev < 0) {
/* 车方向 → 冻结 (env 相关) */
g_env_wait_cnt = 0;
if (g_env_resync) ev_abort++;
env_resync_abort();
g_env_recover_cnt = 0; /* 目标深压 → 恢复计时清零 */
} else {
/* dev>0 高位 — V4.24 两段式 / V4.25 预判锁存 */
g_env_recover_cnt = 0; /* 高位异常中 → 恢复确认失效 */
if (!g_env_resync) {
g_env_wait_cnt++;
if (g_env_wait_cnt >= ENV_WARN_WAIT)
g_env_warn = 1; /* 预判锁存 (只增不清) */
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); /* 学习结束 */
assert(env_warn_active() == 1);/* V4.25: 替换后黄灯保持 (恢复确认前不灭) */
/* 替换后 dev≈0 → 窗口分支; 空闲连续 5s 恢复确认 → 黄灯灭 */
for (int i = 0; i < 500; i++)
sim_tick();
assert(ev_applied == 1); /* 不再重复替换 */
assert(env_warn_active() == 0);/* 恢复确认 5s 后黄灯灭 */
printf(" 场景1 PASS — 永久环境变化 5s 预判锁存, ~66s 自动恢复, 恢复确认后灭\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 清零, 但预判锁存保持 (V4.25 核心语义) */
sim_CAPVD = 128100UL; /* dev=+5 窗口内 */
sim_tick();
assert(g_env_wait_cnt == 0);
assert(env_warn_active() == 1); /* 回摆不清黄灯, 保持常亮 */
/* 窗口内连续 499 tick (累计 500 恢复确认) → 环境稳定, 解除预判 */
for (int i = 0; i < 499; i++)
sim_tick();
assert(env_warn_active() == 0); /* 新环境稳定 5s, 黄灯灭 */
assert(g_env_recover_cnt == 0);
/* 再次高位 59s(5900tick): 5s 后预判重新锁存, 但 <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);
assert(env_warn_active() == 1); /* 高位重现 → 预判重新锁存 */
printf("瞬态高位短停: 回摆保持常亮→稳定5s灭→高位重现再锁存 (未学习)\n");
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() == 1); /* 预判锁存保持 (环境未确认恢复) */
assert(g_env_recover_cnt == 0); /* 目标在进入确认区, 恢复计时清零 */
/* 目标离开, 高位重新 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);
assert(env_warn_active() == 1); /* 替换后黄灯保持到恢复确认 */
for (int i = 0; i < 500; i++)
sim_tick(); /* 窗口恢复确认 5s */
assert(env_warn_active() == 0); /* 黄灯灭 */
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 warn=%d Origin=%lu\n",
tick, ev_applied, g_env_resync, g_env_warn, (unsigned long)sim_Origin);
assert(ev_applied == 0);
assert(g_env_resync == 0); /* 超时退出学习 */
assert(env_warn_active() == 1); /* 环境仍异常 → 黄灯保持锁存 */
assert(ev_abort >= 1);
assert(sim_Origin == 128095UL); /* Origin 不被污染 */
printf(" 场景4 PASS — 学习不稳定 30s 放弃保留旧 Origin, 黄灯保持异常指示\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); /* 无新学习 */
assert(env_warn_active() == 0); /* 恢复确认 5s 后黄灯已灭 */
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;
}