灯光模拟HarmonyOS应用实战-62-保证近光为何还要再点一次-普通灯光与实操超时分支对齐
灯光模拟HarmonyOS应用实战-62-保证近光为何还要再点一次-普通灯光与实操超时分支对齐
在灯光模拟训练中,“执行一次动作”和“维持某个状态”看起来很接近,落到判题代码里却可能产生完全不同的结果。
这篇文章面向维护 HarmonyOS/ArkTS 训练类状态机的开发者;分析依据是本地 The_kemusan 工程中 QuestionBank.ets 与 Index.ets 的静态分支,目标是把判题差异压缩成一个可审计的最小改动。
当前题库中的 p_meet 指令写的是“夜间与机动车会车:保证近光灯状态”。如果上一条指令结束后,灯光本来就处于近光状态,学员看到这句话时,很自然地会认为当前状态已经符合要求,不需要再点击一次“近光灯”。但从源码分支看,同样是 ACTION_LOW + LIGHT_LOW + 等待超时,普通灯光模式和实操模式会走向不同结果:
- 普通灯光模式的
handleTimeout会在超时点再次查看灯态;若目标动作是ACTION_LOW,当前灯态也已经是LIGHT_LOW,则按正确结果收口。 - 实操模式的
handlePracticalTimeout只要当前指令仍未完成,就按失败结果收口,没有为p_meet保留已经满足近光状态的分支。
这篇文章只处理这一个差异:保留普通模式现有行为,把 p_meet 的实操超时结果与之对齐,并用差异回归锁住边界。文中的修改代码都是建议方案,尚未写入项目源码。

读完本文,你会得到四个可以直接用于代码评审和回归测试的结果:
- 用同一条时间序列解释普通模式与实操模式为何出现不同判定。
- 把“已有近光能否满足指令”收敛为一个没有页面副作用的纯判断。
- 用差异矩阵锁定唯一允许变化的
p_meet + LIGHT_LOW业务格子。 - 分开记录源码事实、建议实现、构建结果与设备操作,避免把静态分析写成已经落地。
一、先把问题压缩成一个可重复的时间序列
这个问题不是“近光按钮能不能点击”,也不是“灯态能不能切换”。真正需要比较的是:两个训练入口在相同输入条件下,超时回调给出了不同结论。
可以把场景压缩为下面五步:
| 时间点 | 普通灯光模式 | 实操模式 |
|---|---|---|
| T0 | 上一条操作结束 | 上一条操作结束 |
| T1 | 当前灯态为 LIGHT_LOW | 当前灯态为 LIGHT_LOW |
| T2 | 新指令期望 ACTION_LOW | 新指令为 p_meet,动作字段也是 ACTION_LOW |
| T3 | 学员没有再次点击近光灯 | 学员没有再次点击近光灯 |
| T4 | handleTimeout 查看现有灯态并可判正确 | handlePracticalTimeout 直接进入失败收口 |
这组输入的核心不是“学员什么都没有做”,而是“指令出现时,目标状态已经成立”。如果一条指令强调“保证近光灯状态”,判题时就必须回答一个明确的问题:
这条指令要求产生一次新的近光动作,还是要求超时前保持近光状态?
从当前普通模式的超时逻辑看,项目已经存在“现有状态可以满足 ACTION_LOW”这一行为。差异产生在实操超时分支没有复用它。
为了避免讨论被页面样式、按钮动画、语音播放和历史记录分散,本文只观察三个输入和一个输出:
interface TimeoutObservation {
expectedAction: string
currentLightState: string
commandId: string
passed: boolean
}
这段结构不是要加入业务模型,而是帮助我们限定分析范围。expectedAction、currentLightState 和 commandId 足以描述本次分支差异,passed 是需要对齐的结果。页面布局、计时器展示和语音内容都不参与这个最小判断。
二、从 PracticalCommand 与 p_meet 确认源码事实
实操指令来自 QuestionBank.ets。当前 PracticalCommand 主要携带编号、文本、动作和分类,p_meet 使用 ACTION_LOW 表示近光要求。
下面是只保留本文相关字段的等价摘录,字段含义与当前源码一致,排版为了讲解做了收缩,并非逐字复制源码:
export interface PracticalCommand {
id: string
text: string
action: string
category: string
}
export const PRACTICAL_COMMANDS: PracticalCommand[] = [
{
id: 'p_meet',
text: '夜间与机动车会车:保证近光灯状态',
action: ACTION_LOW,
category: '会车'
}
]
这里有两个可以直接从源码确认的事实。
第一,p_meet 的文案包含“保证近光灯状态”。它描述的是会车场景应当处于什么灯态,并没有在文案中要求“必须重新点击近光灯按钮”。
第二,它的数据模型仍然把这条要求放在 action 字段里,值为 ACTION_LOW。因此,判题代码如果只比较动作事件,就会把“需要近光”解释成“需要再发生一次近光点击”;如果在超时点同时读取灯态,则可能把它解释成“当前近光已经满足”。
源码事实与本文建议需要分开看:
| 内容 | 当前可以确认的事实 | 本文不直接断言的内容 |
|---|---|---|
| 指令数据 | p_meet 的 action 为 ACTION_LOW | 产品设计者最初是否有意要求重复点击 |
| 指令文案 | 文案强调“保证近光灯状态” | 所有含 ACTION_LOW 的指令都应接受已有状态 |
| 普通超时 | ACTION_LOW + LIGHT_LOW 存在正确分支 | 该分支是否覆盖了全部业务场景 |
| 实操超时 | 当前没有对应的状态满足分支 | 真机上已经有人因此失败 |
| 本文方案 | 只建议让 p_meet 复用现有状态语义 | 尚未修改源码,也没有执行运行验证 |
最后一列很重要。源码结构可以说明“某个输入会进入哪个分支”,却不能替代真实设备操作记录。本文没有把静态阅读推断写成已经发生的用户故障。
三、两条超时路径的差异究竟在哪里

普通灯光模式的 handleTimeout 并非一律把超时判为失败。它先确认回调仍对应当前训练,再为近光状态保留一个补偿判断。以下代码只表达决定性条件,是结构等价的收缩版,不是对源码逐字复制:
private handleTimeout(expectedAction: string): void {
if (!this.isExamActive) {
return
}
if (expectedAction === ACTION_LOW &&
this.currentLightState === LIGHT_LOW) {
// 普通模式:目标为近光,而且超时点已经处于近光。
this.completeCurrentCommand(true)
return
}
this.completeCurrentCommand(false)
}
真正决定本文结论的是第二个 if:即使等待期间没有收到新的近光点击,只要超时点满足 ACTION_LOW + LIGHT_LOW,普通模式仍可按正确结果结束当前指令。
实操模式的 handlePracticalTimeout 也会先处理活动状态和当前指令等保护条件,但在这些保护条件之后,没有与普通模式对应的灯态判断。它的关键结构可以压缩为下面的等价伪代码,同样不是逐字源码:
private handlePracticalTimeout(commandId: string): void {
if (!this.isPracticalExamActive) {
return
}
if (this.currentPracticalCommand?.id !== commandId) {
return
}
// 实操模式:当前指令仍在等待时,直接按失败收口。
this.completeCurrentPracticalCommand(false)
}
因此,差异不在 ACTION_LOW 常量,也不在 LIGHT_LOW 的表示方式,而在两个超时回调的最后一段:
- 普通分支:先问“目标状态是否已经满足”,再决定失败。
- 实操分支:保护条件通过后,直接失败。
p_meet:恰好把状态语义写在文案里,却进入了没有状态补偿的实操分支。
还要注意,超时函数通常不仅负责返回布尔值。它还可能清理定时器、更新提示语、增加得分、写入记录或切换下一条命令。因此,修复时不宜复制整段普通模式代码到实操模式。复制越多,两个入口以后越容易再次分叉。
更合适的边界是只抽取“超时点是否已满足近光”这一小块纯判断,原有收口流程仍由各自入口负责。
四、先用特征测试固定当前分支差异
在改变结果之前,先写一个能描述现状的特征测试。它的作用不是证明哪个结果更合理,而是让维护者清楚看到:当前两个入口对同一组关键输入确实给出了不同输出。
如果项目已经有可调用的超时测试入口,可以直接驱动页面状态;如果页面状态不方便构造,也可以先通过很薄的适配层暴露结果。下面用 runOrdinaryTimeout 和 runPracticalTimeout 表示这两个入口,它们是测试设计示例,并非项目现有函数:
describe('当前超时分支特征', () => {
it('普通模式在 ACTION_LOW 与 LIGHT_LOW 同时成立时通过', () => {
const result = runOrdinaryTimeout({
expectedAction: ACTION_LOW,
currentLightState: LIGHT_LOW
})
expect(result.passed).assertTrue()
})
it('实操 p_meet 在相同灯态下仍然失败', () => {
const result = runPracticalTimeout({
commandId: 'p_meet',
expectedAction: ACTION_LOW,
currentLightState: LIGHT_LOW
})
expect(result.passed).assertFalse()
})
})
这两条测试都应当描述修改前的行为。第一条防止后续修复误伤普通模式;第二条把待对齐的差异显式记录下来。
如果直接测试页面方法,计时器会让用例变慢,也容易受到异步顺序影响。更稳妥的做法是把“定时器何时触发”和“触发后怎样判定”分开。测试只调用判定入口,不需要真的等待若干秒。
特征测试还应记录终态提交次数。一个超时回调和一个点击事件可能在相邻时间片内到达,如果两个路径都能结束当前指令,就需要防止重复加分或重复写入历史。
it('点击收口后到达的超时回调不会再次提交结果', () => {
const session = createPracticalSession('p_meet', LIGHT_LOW)
session.handleAction(ACTION_LOW)
session.handleTimeout('p_meet')
expect(session.resultCommitCount).assertEqual(1)
})
这条用例与“已有近光是否通过”是两个维度。前者处理并发收口,后者处理业务结果。不要因为加入灯态判断,就删掉原有的活动状态、当前命令编号或已完成标记。
五、抽取一个只负责超时近光判定的最小函数
这次不需要重新设计整套灯光状态机。普通模式已经给出需要保留的行为,只需把那条条件变成可复用的判定器,并为调用方显式传入是否允许已有近光状态满足当前命令。
可以先定义两个很小的结果类型:
enum TimeoutVerdictCode {
SATISFIED_BY_LOW_STATE = 'SATISFIED_BY_LOW_STATE',
NOT_SATISFIED = 'NOT_SATISFIED'
}
interface TimeoutVerdict {
passed: boolean
code: TimeoutVerdictCode
}
function evaluateLowStateAtTimeout(
expectedAction: string,
currentLightState: string,
acceptExistingLow: boolean
): TimeoutVerdict {
const lowStateSatisfied =
acceptExistingLow &&
expectedAction === ACTION_LOW &&
currentLightState === LIGHT_LOW
if (lowStateSatisfied) {
return {
passed: true,
code: TimeoutVerdictCode.SATISFIED_BY_LOW_STATE
}
}
return {
passed: false,
code: TimeoutVerdictCode.NOT_SATISFIED
}
}
这个函数刻意保持狭窄:
- 它只处理超时点的近光满足条件。
- 它不启动或取消计时器。
- 它不修改页面状态。
- 它不加分,也不写历史记录。
- 它不决定所有
ACTION_LOW指令是否都允许沿用已有灯态。 - 它通过
acceptExistingLow把命令策略留给调用方。
为什么不直接写成“只要 ACTION_LOW + LIGHT_LOW 就通过”?因为这次可以明确讨论的是普通模式的既有行为和 p_meet 的文案。其他实操指令即使也使用 ACTION_LOW,其语义仍需逐条确认。把开关作为参数,可以将影响限制在已分析的入口。
为什么返回 TimeoutVerdict 而不是单个 boolean?因为业务结果写入页面时,通常还需要区分“通过一次动作完成”和“在超时点由已有状态满足”。一个稳定的原因码有助于测试和排查,又不会让判定器直接依赖提示文案。
判定关系可以写成一个很小的真值表:
acceptExistingLow | expectedAction | currentLightState | 结果 |
|---|---|---|---|
true | ACTION_LOW | LIGHT_LOW | 通过 |
true | ACTION_LOW | 非 LIGHT_LOW | 失败 |
true | 非 ACTION_LOW | LIGHT_LOW | 失败 |
false | ACTION_LOW | LIGHT_LOW | 失败 |
第四行让调用方可以继续维持其他实操命令的原行为,避免一次局部修复扩大成未经确认的业务改写。
六、普通灯光模式只替换条件,不改变收口顺序

普通灯光模式已经接受 ACTION_LOW + LIGHT_LOW。接入共享函数时,目标是重构判断位置,不能改变外部行为。
建议结构如下,这仍是尚未写入项目的接入示例:
private handleTimeout(expectedAction: string): void {
if (!this.isExamActive) {
return
}
const verdict = evaluateLowStateAtTimeout(
expectedAction,
this.currentLightState,
true
)
if (verdict.passed) {
this.completeCurrentCommand(true, verdict.code)
return
}
this.completeCurrentCommand(
false,
TimeoutVerdictCode.NOT_SATISFIED
)
}
这里的第三个参数固定为 true,因为它对应普通模式已有的近光超时行为。接入前后,以下行为都应该保持不变:
- 目标是近光,超时点已经为近光:继续通过。
- 目标是近光,超时点不是近光:继续失败。
- 目标不是近光,即使当前恰好为近光:继续失败。
- 考试已经结束:旧回调直接返回。
- 当前命令已经被点击路径收口:旧回调不能重复提交。
实际项目中的收口函数名、提示文本和分数更新方式应沿用现有实现。上面的 completeCurrentCommand 只是表示“原有普通模式收口逻辑”,不是对当前函数名的逐字摘录,也不能为了套用示例而新增一套并行状态。
普通模式接入共享函数还有一个作用:它让回归测试能直接证明抽取前后输出一致。如果只在实操函数里临时补一个 if,虽然也可能解决 p_meet,但普通模式仍保留另一份条件表达式,后续修改其中一处时差异可能再次出现。
可以用差异用例验证抽取没有改变普通模式:
const ordinaryCases: Array<TimeoutObservation> = [
{
expectedAction: ACTION_LOW,
currentLightState: LIGHT_LOW,
commandId: 'ordinary_low',
passed: true
},
{
expectedAction: ACTION_LOW,
currentLightState: LIGHT_HIGH,
commandId: 'ordinary_low',
passed: false
},
{
expectedAction: ACTION_HIGH,
currentLightState: LIGHT_LOW,
commandId: 'ordinary_high',
passed: false
}
]
ordinaryCases.forEach((item: TimeoutObservation) => {
const verdict = evaluateLowStateAtTimeout(
item.expectedAction,
item.currentLightState,
true
)
expect(verdict.passed).assertEqual(item.passed)
})
这组用例覆盖了条件中的三个变量方向:动作相同但状态不同、状态相同但动作不同,以及两者同时满足。只测成功用例会漏掉“其他动作被近光状态误放行”的风险。
七、实操模式只为 p_meet 打开已有近光分支
实操入口需要更谨慎。现有静态证据不足以支持把所有实操 ACTION_LOW 指令都改成状态型指令,因此先用命令编号明确限定 p_meet。
可以增加一个简单策略函数:
function acceptsExistingLowAtTimeout(
command: PracticalCommand
): boolean {
return command.id === 'p_meet' &&
command.action === ACTION_LOW
}
同时比较 id 和 action 有两个好处。
一是表达业务对象确实是 p_meet,而不是任何碰巧使用同一动作常量的命令。二是当题库以后误改了 p_meet 的动作字段时,函数不会悄悄继续放行,而会让回归用例暴露数据与策略不一致。
实操超时函数可以在原有保护条件之后调用共享判定器:
private handlePracticalTimeout(
command: PracticalCommand
): void {
if (!this.isPracticalExamActive) {
return
}
if (this.currentPracticalCommand?.id !== command.id) {
return
}
if (this.currentPracticalResolved) {
return
}
const verdict = evaluateLowStateAtTimeout(
command.action,
this.currentLightState,
acceptsExistingLowAtTimeout(command)
)
this.completeCurrentPracticalCommand(
verdict.passed,
verdict.code
)
}
落地时仍要适配当前 Index.ets 的真实函数与收口入口。按这个边界修改后,p_meet 在超时点已经是近光时会通过;当前不是近光时仍失败。其他实操指令继续按原有超时失败路径收口。
这里还有一个容易混淆的选择:是否在 p_meet 展示出来的瞬间立刻判正确?
这次改动不应顺手加入“展示即完成”。普通模式的现有行为是在超时回调中检查已有近光状态,本次目标是让两条超时分支对齐。若实操模式一展示命令就自动跳到下一题,会改变学员阅读时间、语音播放、倒计时和页面反馈,影响范围明显更大。
因此,建议行为是:
- 学员主动点击近光:继续由
handlePracticalAction提前完成。 - 学员没有点击,但近光始终保持到超时点:
p_meet由共享判定器判正确。 - 学员在等待期间把近光切成其他灯态:超时点不满足,判失败。
- 其他命令没有明确放开已有状态:继续按原路径处理。
这是一处局部对齐,不等于重新定义整个实操考试的交互节奏。
从命令读取到单次提交的端到端接线
把纯判断放回页面时,可以把一次超时处理拆成“读取命令、过滤旧回调、读取灯态、计算结果、单次提交”五段。这样排查时能定位到底是命令过期、灯态错误,还是结果被重复写入,而不用从最后一个布尔值向前猜。
| 阶段 | 输入 | 必须守住的条件 | 输出 |
|---|---|---|---|
| 读取命令 | 当前实操命令与回调命令编号 | 两个编号必须一致 | 当前有效命令 |
| 过滤会话 | 活动标记与已收口标记 | 会话活动且尚未收口 | 是否继续 |
| 读取灯态 | 页面统一灯态 | 在有效回调中读取最新值 | currentLightState |
| 计算结果 | 动作、灯态、命令策略 | 只有 p_meet 允许已有近光 | TimeoutVerdict |
| 单次提交 | 判定结果与原因码 | 提交前再次检查收口标记 | 一次提示、计分和切题 |
下面的 ArkTS 示例把这五段接在同一个入口中。它继续复用前文的 evaluateLowStateAtTimeout,没有另造第二套判断规则;函数名和状态字段仍需按真实页面适配。
private handlePracticalTimeout(commandId: string): void {
if (!this.isPracticalExamActive ||
this.currentPracticalResolved) {
return
}
const command: PracticalCommand | undefined =
this.currentPracticalCommand
if (command === undefined || command.id !== commandId) {
return
}
const currentLightState: string = this.currentLightState
const acceptExistingLow: boolean =
command.id === 'p_meet' && command.action === ACTION_LOW
const verdict: TimeoutVerdict = evaluateLowStateAtTimeout(
command.action,
currentLightState,
acceptExistingLow
)
if (this.currentPracticalResolved) {
return
}
this.completeCurrentPracticalCommand(
verdict.passed,
verdict.code
)
}
这段接线把旧回调过滤放在灯态读取和结果提交之前,并在提交前再次检查单次收口标记。它没有声称当前工程已经存在 currentPracticalResolved;若真实页面使用会话序号或索引变化保证幂等,应沿用原机制并用并发回归验证,而不必重复新增字段。
八、用差异回归证明只改变一个业务格子
修复后的回归不应只写一句“p_meet 通过”。更有价值的做法是列出修改前后矩阵,明确哪些结果必须变化,哪些结果必须保持。
| 场景 | 修改前 | 建议结果 | 是否允许变化 |
|---|---|---|---|
普通模式,ACTION_LOW + LIGHT_LOW,超时 | 通过 | 通过 | 不允许变化 |
普通模式,ACTION_LOW + LIGHT_HIGH,超时 | 失败 | 失败 | 不允许变化 |
普通模式,其他动作 + LIGHT_LOW,超时 | 失败 | 失败 | 不允许变化 |
实操 p_meet + LIGHT_LOW,超时 | 失败 | 通过 | 本次唯一目标变化 |
实操 p_meet + LIGHT_HIGH,超时 | 失败 | 失败 | 不允许变化 |
其他实操命令使用 ACTION_LOW,超时 | 失败 | 失败 | 暂不改变 |
| 旧命令的延迟回调到达 | 忽略 | 忽略 | 不允许变化 |
| 点击已完成后超时回调到达 | 不应重复收口 | 不应重复收口 | 不允许变化 |
这张表可以直接转换为参数化测试:
interface PracticalTimeoutCase {
name: string
command: PracticalCommand
lightState: string
expectedPassed: boolean
}
const practicalCases: PracticalTimeoutCase[] = [
{
name: 'p_meet 已经是近光',
command: {
id: 'p_meet',
text: '夜间与机动车会车:保证近光灯状态',
action: ACTION_LOW,
category: '会车'
},
lightState: LIGHT_LOW,
expectedPassed: true
},
{
name: 'p_meet 当前不是近光',
command: {
id: 'p_meet',
text: '夜间与机动车会车:保证近光灯状态',
action: ACTION_LOW,
category: '会车'
},
lightState: LIGHT_HIGH,
expectedPassed: false
},
{
name: '其他近光动作没有获得状态豁免',
command: {
id: 'p_other_low',
text: '执行近光灯操作',
action: ACTION_LOW,
category: '其他场景'
},
lightState: LIGHT_LOW,
expectedPassed: false
}
]
practicalCases.forEach((item: PracticalTimeoutCase) => {
const verdict = evaluateLowStateAtTimeout(
item.command.action,
item.lightState,
acceptsExistingLowAtTimeout(item.command)
)
expect(verdict.passed).assertEqual(item.expectedPassed)
})
第三条尤其重要。它证明本次修改依据的是 p_meet 的具体语义,而不是看到 ACTION_LOW 就批量改变所有实操命令。
还需要验证旧回调隔离。假设 p_meet 的定时器触发时,页面已经切换到下一条指令,回调中的灯态可能刚好是近光。如果先调用判定器、后比较命令编号,就可能把上一题结果写到下一题。因此顺序必须是:
- 确认实操会话仍处于活动状态。
- 确认回调携带的命令编号仍是当前编号。
- 确认当前命令尚未收口。
- 读取当前灯态并调用判定器。
- 执行一次终态提交。
可以给终态提交再加一道单次保护:
private completeCurrentPracticalCommand(
passed: boolean,
code: TimeoutVerdictCode
): void {
if (this.currentPracticalResolved) {
return
}
this.currentPracticalResolved = true
this.clearPracticalTimer()
// 以下继续调用项目现有的提示、计分和切题逻辑。
this.applyPracticalResult(passed, code)
}
如果当前项目已经通过活动标记、索引变化或计时器清理保证单次收口,应优先复用现有机制,不必为了示例重复增加字段。关键是用测试确认“动作回调与超时回调相邻到达时只提交一次”。
九、排查时不要只盯着最后一个布尔值
即使判定器只有三项输入,接入页面后仍可能遇到“逻辑看起来正确,结果却没有变化”的情况。下面列出这类修改中较常见的原因。
| 现象 | 优先检查 | 常见原因 | 处理方向 |
|---|---|---|---|
p_meet 已经是近光仍失败 | 传入判定器的 currentLightState | 读取的是旧字段或局部副本 | 在超时回调内读取当前统一灯态 |
p_meet 任何状态都通过 | 判定条件是否同时比较动作与灯态 | 使用了 `action === ACTION_LOW | |
| 其他近光实操题也通过 | acceptExistingLow 的来源 | 直接对所有 ACTION_LOW 传入 true | 先限定 command.id === 'p_meet' |
| 一题增加两次结果 | 动作与超时的收口保护 | 计时器已进入消息队列,单纯清理不足 | 在终态提交处增加幂等保护 |
| 普通模式原来通过的场景变失败 | 普通入口的第三个参数 | 抽取后误传 false | 用普通模式三组回归锁定 |
| 切到下一题后上一题回调改写结果 | 命令编号检查顺序 | 判定和写入发生在旧回调过滤之前 | 先比较活动状态与命令编号 |
页面提示与 passed 相反 | 收口函数的参数顺序 | 新增原因码后调用参数错位 | 使用具名结果对象减少位置错误 |
| 用例通过但真机操作节奏异常 | 是否加入了展示即完成 | 修复范围扩大到命令呈现阶段 | 恢复为仅在超时点读取状态 |
调试日志也应围绕这三个输入和一次提交,而不是打印整个页面对象。建议记录命令编号、期望动作、当前灯态、策略开关、结果原因和会话序号:
interface TimeoutTrace {
sessionId: string
commandId: string
expectedAction: string
currentLightState: string
acceptExistingLow: boolean
verdictCode: TimeoutVerdictCode
}
function createTimeoutTrace(
sessionId: string,
command: PracticalCommand,
lightState: string,
verdict: TimeoutVerdict
): TimeoutTrace {
return {
sessionId,
commandId: command.id,
expectedAction: command.action,
currentLightState: lightState,
acceptExistingLow: acceptsExistingLowAtTimeout(command),
verdictCode: verdict.code
}
}
这类结构化记录可以帮助判断问题出在题库数据、灯态读取、策略选择还是最终写入。正式接入时应沿用项目已有日志设施,并避免写入用户可识别信息。
如果看到 verdictCode 已经是 SATISFIED_BY_LOW_STATE,页面却仍显示失败,说明纯判断已经完成,问题应继续追到收口调用和页面状态更新,而不应反复修改 evaluateLowStateAtTimeout。
十、验证清单与交付边界
在真正修改源码后,可以按下面顺序验证。前半部分是纯判断,后半部分才涉及页面和设备。
纯判断与分支回归
-
ACTION_LOW + LIGHT_LOW + acceptExistingLow=true返回通过。 -
ACTION_LOW + 非 LIGHT_LOW + acceptExistingLow=true返回失败。 -
非 ACTION_LOW + LIGHT_LOW + acceptExistingLow=true返回失败。 -
ACTION_LOW + LIGHT_LOW + acceptExistingLow=false返回失败。 - 普通模式抽取前后的三组输出保持一致。
-
p_meet + LIGHT_LOW从实操旧结果失败变为建议结果通过。 -
p_meet + 非 LIGHT_LOW仍为失败。 - 其他实操命令没有被连带放行。
- 旧命令的延迟回调不会写入当前命令。
- 动作完成与超时到达相邻发生时只提交一次结果。
页面联调
- 先执行一个结束后保持
LIGHT_LOW的前置操作。 - 进入
p_meet时不再次点击近光按钮。 - 等待本题超时,确认本题只生成一次正确结果。
- 进入
p_meet后切换到远光,再等待超时,确认结果为失败。 - 主动点击近光时仍可沿用原动作处理路径提前完成。
- 普通灯光模式的近光超时行为没有变化。
- 提示文本、得分、进度和历史记录使用同一个最终结果。
- 快速切题后,上一题定时器不会影响下一题。
当前事实、建议方案与尚未完成事项
| 层级 | 本文结论 |
|---|---|
| 当前源码事实 | p_meet 的文案为“夜间与机动车会车:保证近光灯状态”,动作字段为 ACTION_LOW,分类为 会车 |
| 当前源码事实 | 普通 handleTimeout 对 ACTION_LOW + LIGHT_LOW 保留了超时通过分支 |
| 当前源码事实 | 实操 handlePracticalTimeout 在有效回调进入后没有对应状态分支,会按失败收口 |
| 建议方案 | 抽取最小的 evaluateLowStateAtTimeout,普通模式保持现状,实操模式只为 p_meet 打开已有近光满足条件 |
| 建议方案 | 保留会话活动、命令编号和单次收口保护,用差异回归证明只改变目标格子 |
| 尚未完成 | 本文代码没有写入 Index.ets 或其他项目源码 |
| 尚未完成 | 没有运行项目构建,没有生成新的 HAP |
| 尚未完成 | 没有在模拟器或真机上重放 p_meet 场景 |
| 尚未完成 | 没有形成安装包运行、触控过程或历史记录回读证据 |
源码阅读能确认的是两条超时分支当前存在不一致,建议代码说明的是一种局部对齐方法。只有完成实现、回归、构建和设备操作后,才能确认最终交互是否符合产品约定。因此这里只将它列为待实施方案,不把静态分支分析冒充为已经落地或通过真机验证的结果。
更多推荐



所有评论(0)