feat(video): 实现 FreeMode 状态随机游走

- 修改 select_next_state():FreeMode 无 next_state/next_states 时按权重随机选择 FreeMode 状态
- 新增 select_random_free_state() 方法实现权重随机算法
- InteractiveMode 保持原行为(停留当前状态)
- 新增单元测试验证随机游走和 InteractiveMode 行为
- 所有测试通过(24/24)

旧版行为回补完成。
This commit is contained in:
showen
2026-03-12 13:00:59 +08:00
parent 6ca5992b33
commit e45573f839
2 changed files with 62 additions and 2 deletions

View File

@@ -1893,3 +1893,50 @@ M Cargo.lock
- `/api/playlist` 返回配置快照 + 当前播放索引,客户端可据此实现播放列表高亮
---
---
[2026-03-12 当前] 张明远(内核工程师) → 刘建国(PM), 陈逸飞(CEO): 状态机 FreeMode 随机游走功能已完成。
## 任务背景
旧项目状态机在 FreeMode 状态下,如果没有配置 next_state/next_states会按权重随机跳转到任意 FreeMode 状态。新版 select_next_state() 缺少这个逻辑。
## 实现内容
1. **阅读旧代码分析**
- 旧代码 `/home/showen/Showen/hologram_player_rust/src/state_machine.rs:158-160`
- transition_to_next_state() 在没有 next_state/next_states 时调用 select_random_free_state()
- select_random_free_state() 按权重随机选择 FreeMode 状态
2. **修改新代码**
- 文件:`src/plugins/video/state_machine.rs:221-270`
- 修改 select_next_state()FreeMode 且无 next_state/next_states 时调用 select_random_free_state()
- 新增 select_random_free_state() 方法:按权重随机选择 FreeMode 状态
- InteractiveMode 保持原行为(停留在当前状态)
3. **单元测试**
- 新增 `free_mode_random_walk_when_no_next_state_configured` 测试
- 验证 FreeMode 状态随机跳转到其他 FreeMode 状态
- 验证不会跳转到 InteractiveMode 状态
- 验证随机性20次跳转访问多个状态
- 新增 `interactive_mode_stays_in_same_state_when_no_next_state_configured` 测试
- 验证 InteractiveMode 停留在当前状态
## 测试结果
```bash
export PATH=/home/showen/.rustup/toolchains/stable-aarch64-unknown-linux-gnu/bin:$PATH
cargo test --lib state_machine
```
- ✅ 6个状态机测试全部通过
- ✅ 完整测试套件 24个测试全部通过
- ✅ 编译时间16.08s
## 技术细节
- 权重随机算法:累加权重,生成 [0, total_weight) 随机数,游标递减匹配
- 边界处理total_weight <= 0 时返回第一个状态,浮点误差时返回最后一个状态
- 类型安全:使用 matches!(state.mode, StateMode::FreeMode) 过滤状态
## 代码位置
- 实现:`src/plugins/video/state_machine.rs:221-270`
- 测试:`src/plugins/video/state_machine.rs:477-558`
任务完成,等待 commit。

View File

@@ -538,7 +538,7 @@ mod tests {
#[test]
fn interactive_mode_stays_in_same_state_when_no_next_state_configured() {
let mut machine = StateMachine::new(config_with_states([StateConfig {
let states = vec![StateConfig {
name: "interactive".to_string(),
mode: StateMode::InteractiveMode,
sequence: vec![step("interactive")],
@@ -548,7 +548,20 @@ mod tests {
weight: 1.0,
defer_triggers: false,
ignore_triggers: false,
}]));
}];
let states_map = states
.into_iter()
.map(|state| {
let name = state.name.clone();
(name, state)
})
.collect::<HashMap<_, _>>();
let mut machine = StateMachine::new(StateMachineConfig {
initial_state: "interactive".to_string(),
states: states_map,
});
machine.start().expect("state machine should start");
assert!(!machine