事故:平均 29fps 掩盖了十分钟后的塌陷
《潮汐消消》夏季版本上线后,案例设定中有玩家反馈「挂在家园二十分钟,手机烫手,画面越来越卡」。性能看板显示该会话平均 29fps,距离 30fps 只差一点,看起来不像故障。
把会话按分钟拆开后,问题很清楚:前 10 分钟稳定在 30fps,第 15 分钟开始掉到 26fps,最后 5 分钟只有 22–24fps。平均值被前半段拉高,玩家的体感却集中在后半段。
另一个团队更典型:在模拟环境里跑 10 分钟温控曲线,曲线平稳,就签了验收;真机连续挂机 30 分钟才进入持续降频。10 分钟只覆盖了升温阶段,没有覆盖热平衡后的稳态。
本节要解决的不是「怎么让手机不热」,而是怎么验收才不会被平均值和短时曲线骗过去。结论先说:至少跑挂机与连续对局两种场景,用保持率、降频起点与温控状态判定;机内传感器只能用来建模与写脚本,签字必须来自真机证据。
两种场景:长时间挂机与连续对局
单个场景无法覆盖发热问题。挂机看持续热平衡,对局看峰值与交互负载,两者的失败模式不同。
| 场景 | 案例设定时长 | 负载特征 | 能暴露的问题 | 常见误判 |
|---|---|---|---|---|
| 长时间挂机 | 30 分钟 | 家园动画、低频输入、屏幕常亮 | 热平衡后的持续降频、电池温升 | 只看前 5 分钟,曲线还没起来 |
| 连续对局 | 10 分钟 | 连续 90 秒关卡、结算、特效 | 峰值温升、帧率保持、输入延迟 | 只看平均帧率,忽略后半段 |
消消单局约 90 秒,连续对局用多局拼接,覆盖关卡开始、结算与家园切换;方舟单场 8–12 分钟,对局场景本身就是长时负载,但仍要补挂机场景,因为玩家在主界面停留的时间往往更长。
两个场景都要跑,不是二选一。挂机合格不代表对局合格,对局合格也不代表挂机半小时后不降频。验收报告必须分别给出结论,不能合并成一个总分。
温控档位与降频曲线
温控策略把设备状态映射为帧率上限、渲染比例上限与特效开关。下表是案例模型的比较参数,不是厂商标准;项目要按目标机型与外壳结构重新标定。
| 状态 | 皮肤温度阈值 | 帧率上限 | 渲染比例上限 | 非必要特效 | 动作 |
|---|---|---|---|---|---|
nominal | <40°C | 60fps | 1.00 | 保留 | 正常 |
warm | ≥40°C | 60fps | 0.85 | 保留 | 降分辨率,继续观察 |
hot | ≥44°C | 30fps | 0.75 | 关闭 | 降帧率与特效 |
critical | ≥48°C | 30fps | 0.65 | 关闭 | 最低画质,提示或限制入口 |
状态升级要立即执行,降温要带回滞:温度降到阈值以下 2°C,并连续 3 个采样保持,才允许降一级。没有回滞,温度在 44°C 附近抖动时,帧率会在 30/60 之间反复跳。
平台给出的热状态与温度模型可能不一致,策略取两者中更严重的一个。温度是连续值,平台状态是离散值;只信温度会漏掉厂商的保守限频,只信状态会丢掉细粒度趋势。
对消消这类默认 30fps 的产品,hot 状态并不会继续降帧率,因为已经在 30fps;它的温控手段是降分辨率与关闭特效。这意味着不能用帧率保持率单独判断消消的温控是否生效,还要看渲染比例、特效状态与温控状态时长。
状态机:升级、回滞与冷却
温控不是一个查表函数,而是一个带记忆的状态机。同一温度下,从 nominal 升到 warm 与从 hot 降到 warm 的条件不同。
| 事件 | 条件 | 动作 | 记录 |
|---|---|---|---|
| 升温跨阈值 | 温度达到下一档,或平台状态更严重 | 立即升级,可跨多档 | 温度、平台状态、目标档 |
| 降温 | 温度低于当前档阈值 2°C,连续 3 个采样 | 只降一档 | 连续样本数、退出温度 |
| 平台状态恶化 | 平台声明比温度模型更严重 | 立即升级 | 平台状态与温度 |
| 温度抖动 | 在阈值附近来回,未满足退出条件 | 保持当前档 | 抖动区间与停留时长 |
降温一次只降一档,不允许从 critical 直接跳回 nominal。跨档跳回会让帧率瞬间翻倍,温度很快再次升高,形成更大的振荡;逐档退出给系统一个观察窗口。
状态机还要记录「为什么升级」。只有当前温度,无法区分是环境温度升高、充电发热还是玩法负载变化;把平台状态、充电状态与场景 id 一起记下来,才能定位热源。
充电与握持:先控制变量
温控验收最容易失效的地方不是脚本,而是条件不统一。同一台设备,充电时与不充电时的温度曲线可能差出一个档位。
| 变量 | 影响 | 验收时的处理 |
|---|---|---|
| 是否充电 | 充电本身发热,叠加游戏负载 | 充电与不充电各跑一轮,分别出结论 |
| 是否戴壳 | 外壳隔热,皮肤温度与内部温度背离 | 记录壳型,至少覆盖官方壳与裸机 |
| 环境温度 | 夏季与空调房差异明显 | 记录室温,目标区间内对比 |
| 屏幕亮度 | 屏幕与背光发热 | 固定亮度或固定百分比 |
| 网络状态 | 弱网重试增加 CPU 与射频负载 | 固定网络类型与信号强度 |
这些变量不固定,报告之间不可比。验收记录里至少要写清设备、系统、构建号、场景、室温、充电状态与壳型;缺一项,结论就只能当参考,不能签字。
降频曲线怎么标定
阈值不是拍脑袋定的,也不是从别的项目复制的。标定要先用固定负载找到「温度—帧率」的拐点,再给拐点留安全余量。
| 步骤 | 做法 | 产出 |
|---|---|---|
| 1. 固定负载 | 用固定场景、固定时长、固定画质跑多轮 | 温度与帧率原始曲线 |
| 2. 找拐点 | 找帧率开始持续下降的温度区间 | 初步阈值 |
| 3. 留余量 | 在拐点前 2–3°C 设降档线 | 正式阈值 |
| 4. 多机验证 | 至少 3 台同类机型复跑 | 阈值是否稳定 |
| 5. 周期复核 | 系统升级、外壳变化、季节变化后重跑 | 阈值版本 |
拐点之前的降档是「预防」,拐点之后的降档是「补救」。预防降档能保住帧率稳定性,但会提前牺牲画质;补救降档画质保得久,但玩家已经先看到卡顿。案例模型选择在拐点前降档,并用回滞避免频繁切换。
阈值要版本化。系统升级可能改变调度策略,新外壳可能改变散热;没有版本号的阈值,半年后没人说得清当前值是怎么来的。
指标:不要用平均值签字
验收指标要能定位「什么时候开始变差」和「差了多少」,而不只是「整体平均是多少」。
| 指标 | 口径 | 作用 | 案例阈值 |
|---|---|---|---|
| 最高皮肤温度 | 全程采样最大值 | 判断是否烫手 | 43°C |
| 最高电池温度 | 全程采样最大值 | 判断电池安全与充电策略 | 45°C |
| 帧率保持率 | 最后 20% 窗口中位数 ÷ 最前 20% 窗口中位数 | 判断长时衰减 | 0.8 |
| 降频起点 | 首次进入非 nominal 的时间 | 判断是否过早降频 | 不早于总时长 50% |
| 温控状态时长 | 各状态累计毫秒 | 判断是否长期停留在 hot | hot 不超过 30% |
| 帧时间 p95 | 按窗口统计 | 判断卡顿尾部 | 不超过预算 1.2 倍 |
平均帧率不在表里。平均 29fps 可能是 30fps 跑了 25 分钟、22fps 跑了 5 分钟,也可能是全程 29fps;前者是热降频,后者是性能不足,处理方式完全不同。
保持率也要看窗口。用最后 5 分钟对比最前 5 分钟,比全程平均更接近玩家体感;窗口太短会被单次结算污染,窗口太长又会掩盖中期塌陷。案例模型取首尾各 20%,项目可以按场景时长调整。
传感器只能建模,验收必须真机
机内温度、平台热状态、帧率与渲染比例都通过 Host 接口读取。模型 Host 用一条合成升温曲线驱动状态机,source 固定为 model;真机 Host 的 source 为 device,并携带设备 id、系统版本与构建号。
模型能做什么:
- 验证验收脚本会不会在 30 分钟内正确采样、分窗口与计算保持率。
- 验证温控状态机的升级、回滞、降级顺序与边界。
- 验证报告字段、失败原因与签字规则。
- 在没有真机排队时做回归,防止脚本改坏。
模型不能做什么:
- 不能代表某台设备的真实发热曲线。外壳材质、握持方式、环境温度、是否充电、是否戴壳,都会改变结果。
- 不能代表厂商的调度与限频策略。同样的温度,不同厂商可能给出不同的帧率。
- 不能作为验收证据。模型跑出的
pass只能说明脚本正确,不能说明设备合格。
因此验收函数把 model 证据直接拒绝:即使报告结论是 pass,签字也会返回 model-source-cannot-signoff。真机证据还必须满足设备 id 匹配、系统版本与构建号非空、报告结论为 pass。
验收流程
一次可复现的温控验收至少包含下面几步:
- 选目标机型:消消优先覆盖 35% 的低端机,方舟覆盖默认 60fps 的中高端机与低档 30fps 的边界机。
- 固定环境:室温、是否充电、是否戴壳、屏幕亮度、音量与网络状态都要记录;这些条件不固定,结果不可比。
- 跑挂机 30 分钟与连续对局 10 分钟,采样间隔 1 秒,保留原始样本而不是只留汇总。
- 计算首尾窗口保持率、最高温度、降频起点与各状态时长,逐项对照阈值。
- 每类机型至少 3 台,覆盖充电与不充电两种条件;只跑一台不能代表批次。
- 签字时附设备 id、系统版本、构建号、场景 id 与原始样本哈希;模型报告单独存放,不进入验收结论。
第 6 步是流程里最容易被省略的一步。没有构建号与设备 id,报告无法复现;没有原始样本,无法解释「为什么这次 pass、上次 fail」。
可运行模型:thermal-demo.ts
完整实现在 src/runtime/thermal.ts,包含温控状态机、模型 Host、两套场景、验收报告与签字规则。下面的 demo 用连续对局场景跑一遍模型,再尝试签字。
// file: fixtures/thermal-demo.ts
// tier: L1
// verify: node fixtures/thermal-demo.ts
import {
BATTLE_PROFILE,
BATTLE_SCENARIO,
modelThermalHost,
runAcceptance,
signOff,
} from "../src/runtime/thermal.ts";
const host = modelThermalHost(BATTLE_PROFILE, BATTLE_SCENARIO.durationMs, BATTLE_SCENARIO.sampleMs);
const report = runAcceptance(host, BATTLE_SCENARIO);
console.log(JSON.stringify({
scenario: report.scenarioLabel,
source: report.source,
samples: report.samples,
maxSkinC: report.maxSkinC,
maxBatteryTempC: report.maxBatteryTempC,
fpsRetention: report.fpsRetention,
degradeOnsetMs: report.degradeOnsetMs,
finalState: report.finalState,
minRenderScale: report.minRenderScale,
verdict: report.verdict,
reasons: report.reasons,
}, null, 2));
const sign = signOff({
report,
deviceId: report.deviceId,
osVersion: "Android 15",
buildId: "1.2.3",
});
console.log(JSON.stringify({ accepted: sign.accepted, reasons: sign.reasons }));
运行 node fixtures/thermal-demo.ts,输出可以读成一张验收失败记录:
| 字段 | 输出 | 含义 |
|---|---|---|
source | model | 这是模型,不是设备证据 |
samples | 600 | 10 分钟、每秒一个样本 |
maxSkinC / maxBatteryTempC | 44 / 46 | 超过案例阈值 43 / 45 |
fpsRetention | 1 | 消消锁 30fps,降频不体现在帧率上 |
degradeOnsetMs | 117000 | 约第 2 分钟进入 warm,早于总时长 50% |
finalState / minRenderScale | hot / 0.75 | 已降分辨率并关闭特效 |
verdict | fail | 温度与降频起点均不达标 |
最后一行签字结果是 accepted: false,原因首先是 model-source-cannot-signoff,其次才是报告里的温度与降频起点。这个顺序很重要:模型报告即使各项都合格,也不能签字;模型的作用是证明验收脚本能跑,不是证明设备能过关。
配套测试 test/runtime/thermal.test.ts 有 12 条路径,覆盖阈值映射、平台状态升级、回滞降温、两套场景、模型升温、自定义阈值、模型证据拒绝、设备信息匹配与失败报告透传。运行命令是 node --test test/runtime/thermal.test.ts。
模型里的温度、时长与阈值都是比较参数,不是任何一台真机的测量值。这里是模型,不是真机测量结果。
双案例:方舟的 60fps 与更长战斗
方舟默认 60fps,hot 状态会把帧率上限压到 30fps,保持率会直接体现为 0.5;它的验收指标比消消更敏感,也更容易被平均帧率掩盖。
方舟单场 8–12 分钟,连续对局场景本身就更长;它的温控验收要覆盖完整战斗循环与赛季结算,不能照搬消消的 10 分钟窗口与 43°C 阈值。
与相邻章节的关系
9.1 的帧策略决定运行期档位,9.2 的阶段切片决定一帧内谁先让路,本节决定长时间运行后如何降频与验收。机型矩阵与兼容性测试可以参考兼容性设备矩阵 ,埋点字段与指标口径可以参考埋点规范 ,低端机整体降级取舍可以参考低端机降级 。
温控状态也要进入崩溃与卡顿的联合分析:长时间 hot 状态下的长帧,可能同时是热降频与主线程任务堆积,单看一个指标无法归因。
小结
- 平均帧率会掩盖后段塌陷;验收用首尾窗口保持率、最高温度、降频起点与状态时长。
- 挂机与连续对局两种场景都要跑,前者看热平衡,后者看峰值与交互负载。
- 温控状态升级立即执行,降温带回滞;平台状态与温度模型取更严重者。
- 传感器与模型只能验证脚本与状态机,不能作为验收证据;签字必须来自真机。
- 验收报告要能复现:设备 id、系统版本、构建号、场景 id 与原始样本缺一不可。
阅读导航:上一节:9.2 每帧预算与主线程任务切片 · 下一节:10.1 存档格式与迁移 。
常见错误与边界
- 用平均帧率签字。平均 29fps 可能由前 25 分钟 30fps 与后 5 分钟 22fps 拼成;必须看首尾窗口保持率与降频起点。
- 只跑短时曲线。10 分钟只能覆盖升温阶段,热平衡可能在 20 分钟后才出现;挂机场景至少 30 分钟。
- 只跑一个场景。挂机合格不代表对局合格,对局合格也不代表长时间停留后不降频;两套场景分别出结论。
- 把模型输出当成设备数据。模型 Host 的
source是model,签字函数必须拒绝;模型只验证脚本与状态机。 - 温控阈值照搬其他项目。外壳、散热、充电策略不同,43°C 与 30% 状态时长只是案例参数,必须按目标机型重新标定。
- 报告只留汇总不留原始样本。没有逐秒温度与帧率,事后无法判断是降频、卡顿还是采样错误;原始样本要随报告归档。
- 用一台设备代表一个批次。同型号设备的散热与电池状态也有差异;每类至少 3 台,并覆盖充电与不充电两种条件。
- 只看最高温度不看状态时长。短时冲到 45°C 与长期停在
hot是两种问题;状态时长和降频起点要一起看。 - 验收环境随季节漂移。夏季室温与空调房差异会改变曲线;记录室温并固定测试时段,否则历史报告不可比。
- 边界:本节不测量真实功耗与发热,也不替代厂商热管理。所有温度与时长均为模型比较参数,真机验收要固定环境、设备与构建号,并保留原始样本。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。