docs: 温度链闭合——完整模型与官方输出平均差 13.9mC(0.014C);根因是 Hex-Rays 十进制字段(dev+0xA4)误读;附可直接落地的伪代码+DDT尾部布局+剩余数据源清单

This commit is contained in:
ZXCLI
2026-10-06 02:52:25 +08:00
parent 4198f9e163
commit e80d62050b
@@ -141,6 +141,137 @@ Tout = Teff + tab[idx] + (tab[idx+1]-tab[idx])*(dev+0x5C-5000*(idx+3))/5000
再采一次,观察官方输出的变化量,反推该项的正确组合方式。 再采一次,观察官方输出的变化量,反推该项的正确组合方式。
3. 钉死后即按 §5 落地 Kotlin,并用本轮同帧数据做逐像素回归(band 1 目标 mC 级)。 3. 钉死后即按 §5 落地 Kotlin,并用本轮同帧数据做逐像素回归(band 1 目标 mC 级)。
## 4c. 第三轮:多帧序列 + 相关性分析(当前状态:差一个约 0.15 ℃ 的静态项)
采集 15 帧同帧配对样本(`offseries.pkl`,脚本 `offseries4.py`:只要求 counts+记录稳定,
其余字段"读到时记录"),逐帧反解"官方隐含波段映射 vs 记录波段映射"的差:
- 差值不是常量而是 **在 −123…−184 mC 之间变化**,并且
**与 `dev+0x5C` 的相关系数 r = 0.999(斜率 ≈ 1:1)**——`dev+0x5C` 正是反编译里
环境补偿 v50 的输入之一(`v50 = dev+0x58 + 2*(dev+0x5C - dev+0x54) - dev+0x60 + v47`,
实测 v50 = 30 / 60 / 89,随帧相位阶跃 30)。
- 逐项复算(按反编译):`v45 = 34120`、`v47 = 0`、`v50 = 30…89`、`v52 = v53 = 44`、
`v54 = 11…33`,对应 `A40` 的温度增量 +30…+90 mC;而"缺的量"是
**≈ +123…+181 mC,方向相反**(即官方比"含环境项的模型"还要高 ~211 mC),
用最小二乘把它写成 `Δ ≈ 210.8 − 0.98·v50`(15 帧内拟合残差 ±3 mC)。
- 该"211"**不是存储常量**:设备对象 0x0–0x1200、记录 540B、DDT 尾部 560B 里都搜不到
211/−211,也没有 0.05 附近的浮点;`dev+0x14 = 20000`(20 ℃)、`dev+0x50 = 200`
是目前仅有的两个可疑字段(后者与 211 接近,需单变量验证)。
- 另一个可能的混杂因素:`dev+0x5C/0x54/0x58/0x60` 每帧都在变(阶跃 30),而我的
读取时刻与 SDK 计算温度的时刻可能差一帧——`v50` 的"追踪"信号有可能来自这个相位差。
**当前结论(第三轮收敛后)**:结构 100 % 确认、斜率逐位吻合;把 15 帧的差值写成闭环形式后
是 **`official = model(含环境项) − 211.3 mC`**(15 帧内该项稳定在 −211.3 ± 1 mC,
与 v50 无关;v50 的"追踪"只是我早期把 Δ 写成"v50 − 211.3"造成的表象)。
也就是说:**只剩一个 −211.3 mC(≈0.21 ℃)的静态项没定位**,且它**不在任何数据文件里**
(设备对象 0x0–0x1200、记录 540B、DDT 尾部 560B 均无 211/−211,也无 0.05 量级浮点);
按反编译算出的距离/大气项是 **−995 mC**(≈4.7 倍),说明该段(`dev+0xA8` 那一支)还有
一段没读全(可能是 `dev+0xA8` 之外的组合,或该项的 `sub_180015940` 首参不是常量 35000)。
工程判断:这已经是"+0.09 ℃(常温)…−0.70 ℃(高温)"→"−0.21 ℃ 常数"的差距,但**未定位前不改代码**
(否则就是把 −211.3 这个拟合常数硬编码进去,违反"不猜"原则)。
## 4d. 第四轮:**模型闭合(平均 13.9 mC)**——根因是 Hex-Rays 的十进制/十六进制陷阱
第三轮卡住的 −211.3 mC 常数,来源已找到:`sub_180016290` 距离/大气段的汇编是
```
movss xmm1, [rsi+0A8h] ; dev+0xA8 (float)
mov ecx, [rsi+44CB8h] ; 当帧均值温度 v56
... clamp v56 到 [5000,40000](或 ±80000)...
sub ecx, [rsi+0A4h] ; ★ 减的是 dev+0xA4(十进制 164 = 0xA4)
mov edx, 88B8h ; 35000
cvtsi2ss xmm0, ecx ; mulss xmm0, xmm1 ; cvttss2si r8d, xmm0
call sub_180015940 ; 温度域小量 → 辐亮度域增量
add ebx, eax ; 累加到 v54
```
Hex-Rays 伪代码里写的是 `*(_DWORD *)(a1 + 164)`(**十进制** 164),我上一轮误当成
十六进制 `0x164`(=0,因此那一项被算成 0)——**正确字段是 `dev+0xA4`(实测 22216 mC)**:
```
arg = (int)((float)(clamp(v56, [5000,40000]) - dev+0xA4) * dev+0xA8)
= (29831 - 22216) * (-0.0279) = -212
dist = sub_180015940(35000, arg) # ≈ -75(辐亮度域)
v54 += dist # 再经 A40 变回温度域 ≈ -212 mC
```
**修正后,完整模型与官方输出(15 帧 × 19200 像素)平均 |Δ| = 13.9 mC(0.014 ℃)**,
全部 15 帧一致(13.4–14.5 mC);残余多为 Python double vs C float32 取整差与跨帧毛刺,
最大 ~950 mC 只在极少数像素(band 边界/跨帧)。
→ 同时也解释了之前"数值对不上"的另一半原因:`dev+0xA4` 我根本没有读到过
(它不在 DDT 里,是设备上报的运行参数)。
### 已完整验证的算法(可直接落地,逐条对应反编译)
```text
A[0..645] = 曲线 A(= 我们 OfficialTables.T2E,已验证前 8 项一致;需再核对全表)
S[i] = (0x1000000 + (A[i+1]-A[i])/2) / (A[i+1]-A[i]) # 官方 0x180099A20 表,我们按需自算
cdiv(a,b) = 向零截断的整数除法(C 语义)
E2T(x) = 曲线A正向:i=clamp((x+150000)/4096,0,645); max(A[i] + (A[i+1]-A[i])*(x+150000-(i<<12))/4096, 0)
iE2T(L) = 曲线A逆向:i=最大使 A[i]<=L; ((L-A[i])*S[i])>>12 + (i<<12) - 150000
A40(T,d) = 在 T 处把"辐亮度域增量 d"换算成"温度域增量"(含跨段与 2|d| 判定)
A940(T,d)= 同上,但斜率恒用 (A[i+1]-A[i])
band_map(c):
b = 最大的 i 使 c > thr[i](thr/thr_cnt 在记录 rec+0x40/rec+0x3C,8 段)
return rec[0xD8+8b] + ((c * rec[0xD4+8b]) >> rec[0x30]) # (offset, gain, shift=12)
温度(c)(MAG_GetTemperatureData flag=1 路径):
v46 = band_map(c)
v45 = dev[0x54] + rec[0x38] - rec[0x34]
v47 = dev[0x64]!=0 ? clamp(v45-dev[0x58], -5000, 1000) : clamp(dev[0x58]-v45, -1000, 1000)
v50 = dev[0x58] + 2*(dev[0x5C]-dev[0x54]) - dev[0x60] + v47
v49 = dev[0x58] + 150000; i52=clamp(v49/4096,0,645); i53=clamp((v49+v50)/4096,0,645)
v54 = (i52==i53) ? v50*(A[i52+1]-A[i52])/4096
: A[i53] + (A[i53+1]-A[i53])*(v49+v50-(i53<<12))/4096
- (A[i52+1]-A[i52])*(v49-(i52<<12))/4096 - A[i52]
v56 = clamp(dev[0x44CB8], 5000..40000) # 当帧均值温度(SDK 每帧算)
v54 += A940(35000, (int)((v56 - dev[0xA4]) * dev[0xA8]))
T = v46 + A40(v46, v54)
# 调用方的 revise(flag=1 时):
K = dev[0xF8] * dev[0xF4] * dev[0xD4] # 实测 1.0 × 0.99991 × 0.98 = 0.979912
Lamb = E2T((int)(dev[0xD8] * 1000.0f)) # dev[0xD8] = 当帧均值温度(℃),实测 = 44CB8/1000
T = max(iE2T((int)((E2T(T) - (1-K)*Lamb) / K)), -273150)
idx = clamp((dev[0x5C]-15000)/5000, 0, 6) # 环境温度查表(本机 dev[0xAC..0xC8] 全 0)
T += tab[idx] + (tab[idx+1]-tab[idx])*(dev[0x5C]-5000*(idx+3))/5000
if dev[0x6C] != 0: T += (int)(T*dev[0xEC]) - (int)(dev[0xF0] * -1000.0f)
```
### DDT 尾部 = 标定记录(已完整对齐,App 可直接解析)
相对 `DDT_size - 560`(本机 `official.ddt` = 1856416,故起点 1855856):
| 尾部偏移 | 值(本机) | 含义 |
|---|---|---|
| +0x34 | 0.0065f | (未用,待确认) |
| +0x38 / +0x3C / +0x40 | 1.0f / 2.0f / 1.0f | (未用,待确认) |
| +0x44 | 12 | gain 右移位数(rec+0x30) |
| +0x48 / +0x4C | 29417 / 29917 | rec+0x38 / rec+0x34(相差 500,环境补偿用) |
| +0x50 | 8 | 波段数(rec+0x3C) |
| +0x54 … +0x70 | 9389,9995,10328,10581,10797,11160,11430,11772 | 8 个波段阈值(rec+0x40…) |
| +0xE8 … +0x124 | (33486,−51932) … (35059,−56391) | 每波段 (gain, offset)(rec+0xD4…) |
| +0x1B0 … +0x1D0 | 11,−2,−16,−25,−31,−36,−45,−52,−61 | (未用,待确认) |
| +0x218 | 1.0f | 发射率(rec+0x204) |
| +0x21C / +0x220 | 19790 / 44930 | rec+0x208 / rec+0x20C(距离项阈值对) |
### 落地到 App 还缺的数据来源(下一步)
| 运行时字段 | 值(本机) | 来源(已定位/待定) |
|---|---|---|
| `dev+0xA4` | 22216 mC | **官方手机 App 的 `ExtPara.intCurrentEnvTemperature`**(`DeviceController.getExtParameter` JNI;我们的 `IrSession` 目前只取 BasePara1/2,需要补这个命令) |
| `dev+0x44CB8` | 当帧均值温度 | 可自算:`band_map(s_mean)` + 环境项(flag=0 路径) |
| `dev+0xD8` | = 44CB8/1000(℃) | 同上 |
| `dev+0x54 / 0x58 / 0x5C / 0x60` | 34620/34120/34650/34150 | 传感器/环境温度对;0x58=0x54−500(=rec 差);0x5C−0x54=30 → 需再用"与帧数据相关性"定位(候选:帧尾 FPA、BasePara2 的 `fpaTempFix`/`ATZeroErrorPoint`) |
| `dev+0xF4 / 0xF8` | 0.99991 / 1.0 | 修正系数;候选 `BasePara2.ATErrorSlope`/`ATZeroErrorPoint` |
| `dev+0x64 / 0x6C / 0xCC` | 1 / 1 / 2 | 开关位,需与 ExtPara/BasePara2 字段对照 |
**下一步**:把上表右列补全(用同一套"同帧相关性"方法:把候选来源的字段与 dev 字段逐帧对齐),
然后按上面的伪代码在 Kotlin 落地(波段映射 + 环境补偿 + 距离项 + revise),
用 15 帧官方同帧数据做逐像素回归(目标 ≤20 mC),最后替换 App 的所有温度读数点。
## 5. 我们 App 侧要改什么(等模型钉死后再动手) ## 5. 我们 App 侧要改什么(等模型钉死后再动手)
1. `TempMath` 增加"官方放射学分支"实现:波段映射(参数取自 DDT 尾部)+ §3 修正; 1. `TempMath` 增加"官方放射学分支"实现:波段映射(参数取自 DDT 尾部)+ §3 修正;