feat(motion): M305 —— 列表增删(错位淡入/退场后才移除)+ 进度插值(到点一次收束)

- 新轨道两条:components/motion/pump/{rows,progress}.rs(纯算术,连拍 advance(dt) 可判)
  · rows:两条清单(画出来的顺序 / 真值)+ 逐行可见度;退场行钉在原位、走完才移除
  · progress:真实读数一跳,画出来的值沿 short4 追上去;到 100% 一次脉冲(0→1→0,不循环)
- 接线:pages/download/model.rs::from_snapshot(scope 内唯一 update 侧入口)
  · 行清单 observe → 差分;退场行的数据进 leaving 池(否则退场行画不出来)
  · 进度 observe(key = 版本 id)
- 视图:rows/list.rs 换成 keyed::Column(按 id 做连续匹配)+ 逐行 revealed(裁切框收放)
  + 同色遮罩淡出;install.rs 画插值后的值 + 收束
- MotionScale::NONE:不插轨道、不留幽灵,值严格等于最终值、零采样
- 顺手:rows/tests.rs 重新挂上 mod(此前一直没编译),i18n 扫描补两个新文件

判据:motion::tests::{rows,progress}、rows/motion_tests(Tree::diff 后未变行状态盒不变)、
出图 DevDocs/artifacts/m305(probe.py 量行距与墨色)
This commit is contained in:
AskaEth committed 2026-11-13 21:42:18 +08:00
1 parent b9536c5833
commit ae33674293
24 files changed
+2175 -153

No files matched your search

@@ -0,0 +1,41 @@
//! 进度插值的**公开采样口**(M305):页面只拿得到「这一帧这根条到几成」。
//!
//! ## 谁起跑 / 谁采样
//!
//! | 侧 | 函数 | 干什么 |
//! |---|---|---|
//! | update | [`observe`] | 报「哪一根条 + 真实进度」;差分与起跑在泵里 |
//! | view | [`frame`] | 这一帧画出来的值(**追值**,不是整数值硬跳)+ 收束强度 |
//!
//! **为什么 `key` 与 `target` 两个参数都要传**:泵记的是「上次报的 key + 目标」,视图报的是
//! 这一帧模型里的「key + 目标」。两者不一致 ⇒ 有人改了模型却没走 [`observe`](截图 harness、
//! 单测直接构造模型、或将来某个新入口忘了接线)⇒ 那时 [`value`] **直接返回目标**:
//! 宁可无动效,也不许把界面卡在中间值(同 `motion::rail` 的口径)。
//!
//! ## `MotionScale::NONE`
//!
//! [`observe`] 不插轨道 ⇒ 值严格等于目标、收束恒为 0 ⇒ `needs_frames()` 不会
//! 因为进度变化而为真。
use super::pump::ProgressFrame;
use super::PUMP;
/// **update 侧**:报「哪一根条(`key`)+ 真实进度」。返回 `true` = 真的起跑了追值。
pub fn observe(key: &str, target: f32) -> bool {
let started = PUMP.with(|pump| pump.borrow_mut().observe_progress(key, target));
if started {
super::reset_frame_clock();
}
started
}
/// 一帧的完整进度采样(`value` + `settle`):页面一次取完,避免两次进 thread-local。
pub fn frame(key: &str, target: f32) -> ProgressFrame {
PUMP.with(|pump| {
let pump = pump.borrow();
ProgressFrame {
value: pump.progress_value(key, target),
settle: pump.progress_settle(key, target),
}
})
}