From fafdb91a04ca00b67e432bb3053603129cd9a9e6 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Wed, 21 Oct 2026 02:48:17 +0800 Subject: [PATCH] =?UTF-8?q?feat(tools):=20=E8=BE=B9=E7=95=8C=E5=AE=A1?= =?UTF-8?q?=E8=AE=A1=E6=94=AF=E6=8C=81=E9=80=90=E5=AD=97=E6=AE=B5=E4=BD=8D?= =?UTF-8?q?=E6=B8=B8=E6=A0=87=E8=BF=BD=E8=B8=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 3DTank/tools/audit_pt_boundaries.js | 18 ++++++++++++++++++ 3DTank/tools/lib_space_codec.js | 14 ++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/3DTank/tools/audit_pt_boundaries.js b/3DTank/tools/audit_pt_boundaries.js index 695b9cc..f6a0ccd 100644 --- a/3DTank/tools/audit_pt_boundaries.js +++ b/3DTank/tools/audit_pt_boundaries.js @@ -45,11 +45,22 @@ const Alias = {}; for (const [N, R] of Object.entries(Tables.ResourceClasses || {})) Alias[N] = R.Wire; for (const [N, W] of Object.entries(RESOURCE_CLASS_WIRE)) Alias[N] = W; +// --trace <文件名>:<项序号>:只对这一项打开逐字段追踪,打印「字段 / 字节区间 / 位游标前后」 +const TraceArg = Arg('trace', ''); +const TraceDepth = Number(Arg('trace-depth', '6')) || 6; +let TraceOn = false; + const Reader = MakeReader(Tables, { EnumBytes: Number(Arg('enum-bytes', '4')) || 4, OptBits: true, TypeAlias: Alias, StringLiteralOnly: true, + OnField: (I) => { + if (!TraceOn || I.Depth > TraceDepth) return; + console.log(' ' + ' '.repeat(I.Depth) + I.Name + ' <' + I.Type + '> ' + + I.Start + '→' + I.Next + ' bit ' + I.BitBefore + '→' + I.BitAfter + + (I.Bad ? ' BAD ' + I.Why : '')); + }, }); const PT_METHOD = '7640916300855664666'; @@ -152,7 +163,14 @@ function AuditPt(Body, Off, File) { return; } const BitBefore = Bits ? Reader.BitCursor().Index : 0; + const TraceNow = TraceArg && path.basename(File) === TraceArg.split(':')[0] && String(I + 1) === TraceArg.split(':')[1]; + if (TraceNow) { + console.log(' [trace] ' + path.basename(File) + ' 第 ' + (I + 1) + ' 项 ' + Id + '(' + Key + ')负载@' + At + + ' 起始 bit=' + BitBefore); + TraceOn = true; + } const R = Reader.ReadFields(Buf, At, Codec, 0); + TraceOn = false; Stats.Items++; // 「解不动」的判据要包含位游标:整条模型字段全是 opt 且都为空时,字节不推进但位在推进, // 这也是合法的(例如 DescriptionMediaCC 只有两个 opt)。 diff --git a/3DTank/tools/lib_space_codec.js b/3DTank/tools/lib_space_codec.js index 1371171..102255a 100644 --- a/3DTank/tools/lib_space_codec.js +++ b/3DTank/tools/lib_space_codec.js @@ -109,6 +109,9 @@ function SplitTypeArgs(Text) { function MakeReader(Tables, Options) { const Opts = Options || {}; const EnumBytes = Opts.EnumBytes === undefined ? 4 : Opts.EnumBytes; + // OnField:逐字段回调(诊断用)。参数 {Depth, Name, Type, Start, Next, BitBefore, BitAfter, Bad, Why} + // —— 只读观测,不参与解码判定;审计器用它打印「每个字段消费了哪些字节/位」。 + const OnField = Opts.OnField || null; // OptBits:opt 的「空/非空」改从 nullmap 比特流读 1 个 bit(见文件头注释)。 // 默认 false = 老口径(1 字节标志),也就是「字段表」那条路径的既有行为,不动它。 const OptBits = !!Opts.OptBits; @@ -315,10 +318,21 @@ function MakeReader(Tables, Options) { let Bad = false; let Why = ''; for (const F of (Codec && Codec.Fields) || []) { + const BitBefore = BitIndex; const R = ReadValue(Buf, At, F.Type, F.TypeCodec, Depth); // 匿名字段(表里没有名字,只有线宽)用序号区分,否则会同名互相覆盖 const Key = F.Name === '(匿名)' || F.Name === '?' ? '(匿名#' + F.Order + ')' : F.Name; Obj[Key] = R.Value; + if (OnField) { + try { + OnField({ + Depth: Depth || 0, Name: Key, Type: F.Type, Start: At, Next: R.Next, + BitBefore, BitAfter: BitIndex, Bad: !!R.Bad, Why: R.Why || '', + }); + } catch (E) { + // 诊断回调不允许影响解码 + } + } if (R.Bad) { Bad = true; // 诊断用的原因串:哪个字段、什么类型、卡在负载内第几字节、内层如果直接说得出原因也带上