feat(icon): 轨迹时序指纹修复 —— 1ms 图标 4/8→7/8=0,errorCode 语义与轨迹对拍收口

- 真因:dt 分布与真机严重不符(旧 50% 落 0~16ms,真机 79% 落 13~20ms)+ 点数
  随机 57~65(真机 6/6 恒 61)。按 6 条真机轨迹重拟合 _sample_dt,新增 _fit_points
  钉死 61 点(补齐用 [0,0,dt] 驻留点,不能复制位移点——净位移会被放大)。
- A/B:1ms 图标 0×4→0×7(前 7 连 0);残余偶发 12 为连发频控形态,低频使用即可。
- errorCode 语义标定:50=答案被否(容差实测 25~40px,我们点偏仅 2~3px),
  12=包级拒答;判点位只看 50/0。
- 顺带修 build_plain:轨迹落末槽(k=37)时 chunk2 恒补前导逗号 → 非法 JSON。
- 新增 scripts/verify/send_campaign.py(批量真发+落图+统计)与 judge_icons.py
  (提示目标 vs 点击点并排判读);账本 #64~#74 + 专文 ICON2408.md。

复验:selftest 全绿;判读图 24/24 点位全对;mliev 图标 9/10=0。
This commit is contained in:
yml2213
2026-10-12 14:32:14 +08:00
parent 6a8b3b3ce4
commit 74a863c8b3
9 changed files with 752 additions and 35 deletions
+61 -1
View File
@@ -132,14 +132,74 @@ def test_offline_generate_and_roundtrip() -> None:
len(plain["chunk2"]), len(plain["sd"]))
def test_build_plain_trajectory_first() -> None:
"""轨迹就在第 0 槽时,chunk1 里没有任何槽 —— 不能再补分隔逗号。
实测触发场景:某些构建的清单里轨迹模块排在第 0 位,那样会拼出 `{"cd":[,`
—— 非法 JSON,服务端返回 errorCode=12。
"""
import json as _json
from tcaptcha.telemetry.values import build_plain, trajectory_index
rows = [{"name": "trajectory"}, {"name": "platform"}, {"name": "constant_98k"}]
assert trajectory_index(rows) == 0
cd = [[[1, 1, 12]], "MacIntel", "98k"]
p = build_plain(cd, rows)
assert p["chunk1"] == '{"cd":[', repr(p["chunk1"])
assert _json.loads(p["chunk1"] + p["trajectory"] + p["chunk2"] + p["sd"])["cd"] == cd
# 轨迹在中间时,chunk1 仍必须以 ',' 结尾(真机实测口径)
rows2 = [{"name": "platform"}, {"name": "trajectory"}, {"name": "constant_98k"}]
p2 = build_plain(["MacIntel", [[1, 1, 12]], "98k"], rows2)
assert p2["chunk1"].endswith(",") and p2["chunk2"].startswith(",")
def test_build_plain_trajectory_last() -> None:
"""轨迹就在**最后 1 槽**时,chunk2 里没有任何槽 —— 不能再补前导逗号。
实测触发场景(2026-10-12):38 槽里有构建把轨迹排在 k=37(最后),
旧写法恒拼 `,],` -> 整段变成 `{"cd":[...,],` 非法 JSON。
本题同时钉住「切分器必须接受 `],` 这种对称边界」。
"""
import base64 as _b64
import json as _json
from tcaptcha.telemetry.crypto import DEFAULT_KEY
from tcaptcha.telemetry.values import build_plain, trajectory_index
rows = [{"name": "platform"}, {"name": "constant_98k"}, {"name": "trajectory"}]
assert trajectory_index(rows) == 2
cd = ["MacIntel", "98k", [[1, 1, 12]]]
p = build_plain(cd, rows)
assert p["chunk2"] == "],", repr(p["chunk2"])
whole = p["chunk1"] + p["trajectory"] + p["chunk2"] + p["sd"]
assert _json.loads(whole)["cd"] == cd
# 旧写法(恒补前导逗号)拼出来的确实是非法 JSON —— 这正是当年 12 的成因之一
bad = p["chunk1"] + p["trajectory"] + "," + p["chunk2"] + p["sd"]
try:
_json.loads(bad)
raise AssertionError("旧写法应当拼出非法 JSON")
except ValueError:
pass
# 走完整加密 -> 解密 -> 切分:切分器要吃下 `],` 这种边界
collect = build_collect(p["chunk1"], p["trajectory"], p["chunk2"], p["sd"], DEFAULT_KEY)
dec = xtea_decrypt_bytes(_b64.b64decode(collect), DEFAULT_KEY).decode("latin1").rstrip("\x00")
assert split_collect_segments(dec) == (p["chunk1"], p["trajectory"], p["chunk2"], p["sd"])
def main() -> int:
selfcheck()
test_split_single_space_pad()
test_split_ignores_space_inside_string()
test_build_collect_roundtrip_single_space_pad()
test_offline_generate_and_roundtrip()
test_build_plain_trajectory_first()
test_build_plain_trajectory_last()
print("✅ crypto 纯算法自检通过 (XTEA 往返 + encrypt_string 一致性"
" + 四段切分回归 + 纯离线闭环)")
" + 四段切分回归 + 纯离线闭环 + 轨迹首/末槽边界)")
return 0