fix(tools): 修正审计位游标少加形态位的一处偏移

This commit is contained in:
TermiNexus committed 2026-10-21 04:21:51 +08:00
1 parent fe83456f04
commit bdca6a8fff
2 files changed
+50 -30

No files matched your search

+47 -29
View File
@@ -1,12 +1,14 @@
# analyze_opt_trace.py - 分析客户端「全量 opt 读取」打点,按帧还原每个模型消费了几位
# analyze_opt_trace.py - 还原客户端每帧的 opt 位序列,按「模型片段」汇总
#
# 数据来源:镜像站 module_24635.js 的 Ht.t7h 补丁。每条记录:
# rid = 位读取器实例 id(同一帧共享 → 同 rid 即同帧)
# pos = 读位前的绝对位索引;bit = 读到的位
# slots = 内层读器槽位名(模型字段);elemKeys = 列表元素读器槽位名
# 槽位名全局唯一,所以能用 names/codec_fields.json 的 Slots 反查出具体模型。
# 数据来源:镜像站 module_24635.js 的 Ht.t7h 打点,每条记录:
# rid = 位读取器 id(同一帧共享)、pos = 读位前绝对位索引、bit = 读到的位、
# slots/elemKeys = 内层读器(或列表元素)的槽位名(全局唯一,可反查模型)。
#
# 用法: python tools/analyze_opt_trace.py [--names names] [--only bs,Tu,tu]
# 输出:把同一帧里「连续属于同一个模型」的读位合并成片段:
# 模型@起始位 × 位数 = 位串
# 这样可以直观看出某个模型到底消费了几位(例如 Item3DCC 是 4 位还是 5 位)。
#
# 用法: python tools/analyze_opt_trace.py [--only bs,Tu,tu] [--frames 5]
import json
import os
import sys
@@ -28,10 +30,15 @@ TracePath = os.path.join(NamesDir, 'opt_trace.json')
FieldsPath = os.path.join(NamesDir, 'codec_fields.json')
def LoadSlotIndex():
def LoadIndex():
"""槽位集合 → codec 键。用 Fields[].Slot(比 Slots 更全),再补 Slots。"""
with open(FieldsPath, 'r', encoding='utf-8') as F:
Codecs = json.load(F).get('Codecs') or {}
Index = {}
for Key, C in Codecs.items():
Slots = tuple(sorted({str(F2.get('Slot')) for F2 in (C.get('Fields') or []) if F2.get('Slot')}))
if Slots and Slots not in Index:
Index[Slots] = Key
for Key, C in Codecs.items():
Slots = tuple(sorted(S.get('Slot') for S in (C.get('Slots') or [])))
if Slots and Slots not in Index:
@@ -41,44 +48,55 @@ def LoadSlotIndex():
def Main():
if not os.path.exists(TracePath):
print('缺少 ' + TracePath)
print('请先用镜像站进一局战斗(Ht.t7h 已打全量 opt 打点)。')
print('缺少 ' + TracePath + '(先让镜像站进一局)')
return 1
with open(TracePath, 'r', encoding='utf-8') as F:
Data = json.load(F)
Rows = Data.get('trace') or []
print('opt 打点:%d 条(采集于 %s)' % (len(Rows), Data.get('at', '?')))
Index = LoadSlotIndex()
Only = [S for S in (Arg('only', '') or '').split(',') if S]
Index = LoadIndex()
Only = [S for S in (Arg('only', 'Tu,tu,bs') or '').split(',') if S]
MaxFrames = int(Arg('frames', '5') or 5)
Frames = defaultdict(list)
for R in Rows:
Frames[R.get('rid')].append(R)
print(' 帧数:%d' % len(Frames))
def NameOf(R):
Slots = tuple(sorted(R.get('slots') or []))
Key = Index.get(Slots)
if Key:
return Key.split('::')[-1]
Elem = tuple(sorted(R.get('elemKeys') or []))
if Elem:
K2 = Index.get(Elem)
return 'list<' + (K2.split('::')[-1] if K2 else '?') + '>'
return '?' + ''.join(Slots)
def Segments(Seq):
Out = []
for R in Seq:
N = NameOf(R)
if Out and Out[-1][0] == N and Out[-1][2] == R.get('pos'):
Out[-1][1] += str(R.get('bit') and 1 or 0)
Out[-1][2] = R.get('pos', 0) + 1
else:
Out.append([N, str(R.get('bit') and 1 or 0), R.get('pos', 0) + 1, R.get('pos', 0)])
return Out
Shown = 0
for Rid in sorted(Frames, key=lambda X: (X is None, X)):
Seq = Frames[Rid]
Items = []
Names = []
for R in Seq:
Slots = tuple(sorted(R.get('slots') or []))
Key = Index.get(Slots)
if not Key:
ElemKeys = tuple(sorted(R.get('elemKeys') or []))
Key = ('list<' + (Index.get(ElemKeys) or '?') + '>') if ElemKeys else ('?' + str(Slots))
Short = Key.split('::')[-1]
Names.append(Short)
Items.append('%s@%s=%s' % (Short, R.get('pos'), R.get('bit')))
if Only and not any(N in Only for N in Names):
Segs = Segments(Seq)
if Only and not any(S[0] in Only for S in Segs):
continue
Shown += 1
if Shown > 12:
if Shown > MaxFrames:
continue
print(' rid=%s(%d 位):%s' % (Rid, len(Seq), ' '.join(Items)))
if Shown > 12:
print(' …还有 %d 帧匹配' % (Shown - 12))
Line = ' '.join('%s@%d×%d=%s' % (S[0], S[3], len(S[1]), S[1]) for S in Segs[:40])
print(' rid=%s(%d 位):%s' % (Rid, len(Seq), Line))
print(' 匹配帧数:%d' % Shown)
return 0
+3 -1
View File
@@ -151,7 +151,9 @@ function AuditPt(Body, Off, File, BitStart, Label) {
const Bits = NmSize > 0 && NmSize <= Body.length ? NullmapBits(Body.slice(0, NmSize)) : null;
// 位游标由 semantic 的顺序推进给出(BitStart = 本记录形态位之后的绝对位索引),
// 不再假设「pt 是帧内第一条记录」——多记录帧里那样会整体错位。
if (Bits) Reader.AttachBits(Bits, typeof BitStart === 'number' ? BitStart : 1);
// BitStart 是「本记录形态位」的索引(semantic 在消费形态位之前记录),
// 负载里的 opt 位从它的下一位开始 —— 少加这个 1 会让整条记录的位游标整体早一位。
if (Bits) Reader.AttachBits(Bits, (typeof BitStart === 'number' ? BitStart : 0) + 1);
try {
for (let I = 0; I < D; I++) {
if (At + 8 > Buf.length) return;