feat: M9 枚举线形序数 enum#N 解析成成员名(category 显示 ARMOR/WEAPON/PAINT…)
数据:evidence.json 的 byModule.<模块>.enums = { 枚举短名: [{field, member, value}] },全局 348 类 / 2633 成员。查表键定为 (字段所属模块, 枚举短名, 序数):同模块内短名天然唯一;跨模块同名 11 个短名 / 9 个同名异义,键含模块号即可隔离。全表 102 条 enum<X> 引用的定义模块均由字段的 TypeCodec 唯一确定(无冲突、未解 0),导出后落盘 77 类 / 1343 成员(33879 字符,整表增量 33894)。两层分离(本任务核心约束):协议层原始值仍是 enum#N,一字不改,因此 CompareContainerDecode.mjs 的逐字段对照口径不变;EnumNames 只在 UI/日志/dump 里翻译,查不到返回 null 并由调用方标「未解」,不猜。category 实测(15 件仓库物品全部解出):ARMOR#1 / WEAPON#0 / PAINT#2 / BAZOOKA#13 / DRONE#8 / INVENTORY#3 / INVISIBLE#9,真实 5 件挂载物品类别互不相同 —— M8 结论不变。UI 与日志显示「成员名(enum#N)」。顺带修正 GarageDriver 接线:上一轮把 URL 硬编码成 9092 且日志 label 写死 store-02,改为环境变量 TANKIC_WS_URL(默认 9091)与 TANKIC_LOG_LABEL(默认 store-03),否则「配对表 ⇔ 服务端日志」会找错目录。验收:CompareContainerDecode 5 帧 / 一致叶子 26950 / 不一致 0(硬门槛);server selftest 逐字节一致 1108 / 不一致 9 / 解码中断 90;三构建全绿;浏览器实测 15/15 类别显示成员名。未解:枚举成员名未覆盖 list<enum<X>>/opt<enum<X>> 的元素级。
This commit is contained in:
1 parent
4277e0ae08
commit
356647937e
12 files changed
+268
-12
No files matched your search
@@ -19,6 +19,7 @@
|
||||
// --dry:只算不写(只打印将要发生的变更数,不碰库)。
|
||||
'use strict';
|
||||
const Db = require('./db');
|
||||
const EnumNames = require('./enum_names');
|
||||
|
||||
const Argv = process.argv.slice(2);
|
||||
const Arg = (Name, Def) => {
|
||||
@@ -126,7 +127,10 @@ async function Main() {
|
||||
'SELECT Category, COUNT(*) AS N FROM Items GROUP BY Category ORDER BY (Category IS NULL), Category',
|
||||
);
|
||||
PRINT('· Items.Category 分布(全部 992 件):');
|
||||
for (const D of Dist) PRINT(' ' + String(D.Category === null ? '<NULL>' : D.Category).padEnd(8) + D.N);
|
||||
for (const D of Dist) {
|
||||
PRINT(' ' + String(D.Category === null ? '<NULL>' : D.Category).padEnd(8) +
|
||||
' ' + EnumNames.CategoryLabel(D.Category === null ? null : D.Category) + ' ×' + D.N);
|
||||
}
|
||||
|
||||
// 仓库那 15 件的明细(M8 验收口径:ItemId / Category / Mounted)
|
||||
const Depot = await Db.Many(
|
||||
@@ -136,7 +140,7 @@ async function Main() {
|
||||
PRINT('· 仓库物品(InDepot=1)共 ' + Depot.length + ' 件:');
|
||||
for (const D of Depot) {
|
||||
PRINT(' ' + String(D.ItemId).padEnd(16) + '类别=' + String(D.Category === null ? '<NULL>' : D.Category).padEnd(8) +
|
||||
'Mounted=' + D.Mounted);
|
||||
EnumNames.CategoryLabel(D.Category === null ? null : D.Category).padEnd(16) + 'Mounted=' + D.Mounted);
|
||||
}
|
||||
return 0;
|
||||
} finally {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
// enum_names.js - 枚举线形序数 → 成员名(**仅日志/展示用**;原始值一律不改)
|
||||
//
|
||||
// 为什么单独一个模块:协议里的 enum<T> 在线上只有 4 字节 i32,解出来只有线形序数 `enum#N`
|
||||
// (口径见 tools/lib_space_codec.js)。真实客户端知道成员名,证据在 names/evidence.json:
|
||||
// byModule.<moduleId>.enums = { <枚举短名>: [ {field, member, value}, … ] }
|
||||
// 本模块按需把它读进内存,供服务端日志把 `enum#N` 翻成「成员名(enum#N)」。
|
||||
// **不参与任何解码/编码/落库**:Items.Category 等列里存的仍是原始令牌,一字不改。
|
||||
//
|
||||
// 目前只做「物品装备槽类别」这一个枚举(服务端日志只用到它):
|
||||
// 字段所在 codec = module_85196::Lo,线形 type = `enum<ja>`;ja 定义在 module_85667(TypeCodec=module_85667::pu)。
|
||||
'use strict';
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const NAMES_DIR = path.resolve(__dirname, '..', '..', 'names');
|
||||
const CATEGORY = { Module: '85667', Short: 'ja' };
|
||||
|
||||
let MemberByValue = null;
|
||||
|
||||
// 懒加载(证据文件约 1MB;只有真要打日志时才读,读不到就按「未解」回退,绝不猜)
|
||||
function Ensure() {
|
||||
if (MemberByValue) return MemberByValue;
|
||||
MemberByValue = new Map();
|
||||
try {
|
||||
const Ev = JSON.parse(fs.readFileSync(path.join(NAMES_DIR, 'evidence.json'), 'utf8'));
|
||||
const Members = ((((Ev.byModule || {})[CATEGORY.Module] || {}).enums) || {})[CATEGORY.Short] || [];
|
||||
for (const M of Members) if (!MemberByValue.has(String(M.value))) MemberByValue.set(String(M.value), String(M.member));
|
||||
} catch (Err) {
|
||||
// 证据不可用:保持空表,CategoryLabel 会如实写「未解」
|
||||
}
|
||||
return MemberByValue;
|
||||
}
|
||||
|
||||
// 'enum#1' → 'ARMOR(enum#1)';空/NULL → '未知';查不到 → 'enum#1(未解)'
|
||||
function CategoryLabel(Raw) {
|
||||
if (Raw === null || Raw === undefined || Raw === '') return '未知';
|
||||
const S = String(Raw);
|
||||
const M = /^enum#(-?\d+)$/.exec(S);
|
||||
const Name = M ? Ensure().get(M[1]) : null;
|
||||
return Name ? Name + '(' + S + ')' : S + '(未解)';
|
||||
}
|
||||
|
||||
module.exports = { CategoryLabel, CATEGORY };
|
||||
@@ -36,6 +36,7 @@ const path = require('path');
|
||||
const Db = require('./db');
|
||||
const Link = require('./link');
|
||||
const SemanticModule = require('./semantic');
|
||||
const EnumNames = require('./enum_names');
|
||||
|
||||
const PROJECT = path.resolve(__dirname, '..', '..');
|
||||
const NAMES_DIR = path.join(PROJECT, 'names');
|
||||
@@ -203,7 +204,7 @@ async function SetMounted(ItemId) {
|
||||
const [Res] = await Pool.query('UPDATE Items SET Mounted = 1 WHERE ItemId = ? AND Mounted <> 1', [Id]);
|
||||
const Changed = Res.affectedRows > 0;
|
||||
Log(
|
||||
'→ 落库:Items.Mounted=1(ItemId=' + Id + ',类别=' + (Category === null ? '未知' : Category) +
|
||||
'→ 落库:Items.Mounted=1(ItemId=' + Id + ',类别=' + EnumNames.CategoryLabel(Category) +
|
||||
',同时卸下 ' + Unmounted + ' 件同类)' + (Changed ? '' : '(该件已是 1,幂等跳过)'),
|
||||
);
|
||||
if (Changed || Unmounted > 0) {
|
||||
|
||||
@@ -70,8 +70,8 @@ private val App = FC<Props> {
|
||||
for (Id in Session.DepotItems) {
|
||||
val IsMounted = Mounted.contains(Id)
|
||||
li {
|
||||
// 类别一并渲染:M8 的同类互斥就是按它判定的(来源见 ClientState.ItemCategory 头注)
|
||||
+("$Id 类别 ${Session.CategoryOf(Id) ?: "?"} " + (if (IsMounted) "已挂载" else "未挂载") + " ")
|
||||
// 类别一并渲染:显示**枚举成员名**(附原始 enum#N);M8 的同类互斥就是按原始类别判定的
|
||||
+("$Id 类别 ${Session.CategoryLabelOf(Id)} " + (if (IsMounted) "已挂载" else "未挂载") + " ")
|
||||
// 「装备」按钮:点击发 M_.fcn(ItemId);已挂载的置灰(防重复点,落库本身也幂等)
|
||||
button {
|
||||
disabled = IsMounted
|
||||
|
||||
@@ -93,6 +93,12 @@ private const val SLOT_MARK_MODIFICATION_COUNT = 7
|
||||
// 物品「装备槽类别」所在的模型项 modelId(module_85196::Lo)—— 依据见 ItemCategory 的头注
|
||||
private const val CATEGORY_MODEL = "6117306895090150422"
|
||||
|
||||
// 类别枚举的查名上下文(**仅展示层**):codec module_85196::Lo 的 category 线型是 `enum<ja>`,
|
||||
// 而 ja 的成员表定义在 module_85667(TypeCodec=module_85667::pu)。导出 EnumMembers 时已按
|
||||
// 「字段所在模块 → TypeCodec 模块」回退解析并把结果挂在字段所在模块(85196)键下,故这里传 85196。
|
||||
private const val CATEGORY_ENUM_MODULE = "85196"
|
||||
private const val CATEGORY_ENUM_SHORT = "ja"
|
||||
|
||||
internal class ClientState {
|
||||
|
||||
// ---- 物品三旗标(每个只记最新一次,附来源帧)
|
||||
@@ -136,6 +142,18 @@ internal class ClientState {
|
||||
return null
|
||||
}
|
||||
|
||||
// 类别的**成员名**(显示层):查不到返回 null(原始 enum#N 仍在,不猜)。
|
||||
fun ItemCategoryName(ItemId: String): String? {
|
||||
val Raw = ItemCategory(ItemId) ?: return null
|
||||
return EnumNames.OfRaw(CATEGORY_ENUM_MODULE, CATEGORY_ENUM_SHORT, Raw)
|
||||
}
|
||||
|
||||
// 类别展示文本:查到「成员名(enum#N)」,查不到「enum#N(未解)」,无 pt 数据「?」。
|
||||
fun ItemCategoryLabel(ItemId: String): String {
|
||||
val Raw = ItemCategory(ItemId) ?: return "?"
|
||||
return EnumNames.Label(CATEGORY_ENUM_MODULE, CATEGORY_ENUM_SHORT, Raw)
|
||||
}
|
||||
|
||||
// ---- 挂载位 / 设备
|
||||
// devicesLoaded 在服务端是**覆盖写设备行**(见 3DTank/tools/server/responder.js 头注),
|
||||
// 所以按挂载位 id 收敛、后到者覆盖;同时保留收到过的记录条数以便追溯。
|
||||
@@ -318,6 +336,19 @@ internal class ClientState {
|
||||
Doc["itemsOnMarket"] = linkedMapOf("来源帧" to ItemsOnMarketFrame, "条数" to ItemsOnMarket.size, "值" to ItemsOnMarket)
|
||||
Doc["mountedItems"] = linkedMapOf("来源帧" to MountedItemsFrame, "条数" to MountedItems.size, "值" to MountedItems)
|
||||
|
||||
// 仓库物品类别:枚举字段的**成员名**与原始序数并列(展示层,原始值不丢)
|
||||
val Cats = ArrayList<Any?>(ItemsOnDepot.size)
|
||||
for (Id in ItemsOnDepot) {
|
||||
Cats.add(
|
||||
linkedMapOf(
|
||||
"物品Id" to Id,
|
||||
"类别原始" to ItemCategory(Id),
|
||||
"类别名" to ItemCategoryName(Id),
|
||||
)
|
||||
)
|
||||
}
|
||||
Doc["仓库物品类别"] = linkedMapOf("件数" to ItemsOnDepot.size, "清单" to Cats)
|
||||
|
||||
val Devs = ArrayList<Any?>()
|
||||
for (D in DevicesRecords) {
|
||||
Devs.add(
|
||||
@@ -355,7 +386,14 @@ internal class ClientState {
|
||||
val Table = LinkedHashMap<String, Any?>()
|
||||
for ((Id, O) in PtByObjectId) {
|
||||
val Models = ArrayList<Any?>(O.Models.size)
|
||||
for (M in O.Models) Models.add(linkedMapOf("模型Id" to M.ModelId, "值" to M.Value))
|
||||
for (M in O.Models) {
|
||||
val Entry = LinkedHashMap<String, Any?>()
|
||||
Entry["模型Id"] = M.ModelId
|
||||
Entry["值"] = M.Value
|
||||
// 枚举字段的成员名(显示层):原始值仍在「值」里,这里另附一份便于对照
|
||||
EnumDisplay(M.ModelId, M.Value)?.let { Entry["枚举"] = it }
|
||||
Models.add(Entry)
|
||||
}
|
||||
Table[Id] = linkedMapOf("GameTypeId" to O.GameTypeId, "模型项数" to O.Models.size, "模型项" to Models)
|
||||
}
|
||||
Doc["pt对象索引"] = linkedMapOf(
|
||||
@@ -373,6 +411,25 @@ internal class ClientState {
|
||||
|
||||
// ---------------------------------------------------------------- 小工具
|
||||
|
||||
// 模型项里的枚举字段:modelId → codec 键(含模块号)→ 字段类型串 enum<X> → EnumNames 查名。
|
||||
// 只为**显示**:Value 原样不动(原始 enum#N 仍在「值」里),这里另附 { 字段名: {原始, 名} }。
|
||||
// 只处理裸 `enum<X>`;list<enum<X>> / opt<enum<X>> 的元素级展示不在此列(如实不展示,不猜)。
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun EnumDisplay(ModelId: String, Value: Any?): Map<String, Any?>? {
|
||||
val CodecKey = ProtocolTables.CodecByModelId(ModelId) ?: return null
|
||||
val Fields = ProtocolTables.Codec(CodecKey) ?: return null
|
||||
val V = Value as? Map<*, *> ?: return null
|
||||
val Module = CodecKey.substringBefore("::").removePrefix("module_")
|
||||
val Out = LinkedHashMap<String, Any?>()
|
||||
for (F in Fields) {
|
||||
if (!F.Type.startsWith("enum<") || !F.Type.endsWith(">")) continue
|
||||
val Raw = V[F.Name]?.toString() ?: continue
|
||||
val Short = F.Type.substring(5, F.Type.length - 1)
|
||||
Out[F.Name] = linkedMapOf("原始" to Raw, "名" to EnumNames.OfRaw(Module, Short, Raw))
|
||||
}
|
||||
return if (Out.isEmpty()) null else Out
|
||||
}
|
||||
|
||||
private fun Field(R: SpaceRecord, Name: String): Any? =
|
||||
R.Fields.firstOrNull { it.first == Name }?.second
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// EnumNames.kt —— 枚举线形序数 → 成员名(**只用于展示层**)
|
||||
//
|
||||
// 为什么单独一层:协议里的 enum<T> 在线上只有 4 字节 i32,解出来只有线形序数 `enum#N`
|
||||
// (口径见 FieldReader.kt:68-73 与 3DTank/tools/lib_space_codec.js)。真实客户端还知道成员名
|
||||
// (枚举类 `new Cls("Member", 值)`,证据见 3DTank/names/evidence.json 的 byModule.<模块>.enums)。
|
||||
//
|
||||
// 【两层分离(本任务的核心约束)】
|
||||
// · 协议层原始值 = `enum#N`,**一字不改**(FieldReader / Container / 上下行构造都不碰它),
|
||||
// 这样 client/tools/CompareContainerDecode.mjs 与 container.js 的逐字段字节对照口径不变;
|
||||
// · 本对象只在 UI / 日志 / dump 里把 `enum#N` 翻译成成员名,查不到就返回 null(调用方如实标「未解」,
|
||||
// 绝不猜、也不改原始值)。
|
||||
//
|
||||
// 数据来源:ProtocolTables.EnumMembers(键 `<moduleId>::<枚举短名>`,由 ExportProtocolTables.mjs 从
|
||||
// evidence.json 生成,只含导出表 enum<T> 类型串真正引用到的枚举)。
|
||||
package Protocol
|
||||
|
||||
internal object EnumNames {
|
||||
|
||||
// 按 (模块号, 枚举短名, 线形序数) 查成员名;查不到(模块/短名/数值任一不在表里)返回 null。
|
||||
// Module 一律用**字段所在 codec 的模块号**(如 "85196"),不是枚举类真正定义的模块号 ——
|
||||
// 导出时已按「本模块 → TypeCodec 模块」回退解析并挂在本模块键下(见导出脚本注释)。
|
||||
fun Of(Module: String, EnumTypeShortName: String, Value: Int): String? =
|
||||
ProtocolTables.EnumMember(Module, EnumTypeShortName, Value)
|
||||
|
||||
// 便捷入口:直接吃协议层原始字符串(形如 "enum#1")。不是该形状或查不到返回 null。
|
||||
fun OfRaw(Module: String, EnumTypeShortName: String, Raw: String): String? {
|
||||
val N = Raw.removePrefix("enum#").toIntOrNull() ?: return null
|
||||
return Of(Module, EnumTypeShortName, N)
|
||||
}
|
||||
|
||||
// 展示文本:查到写「成员名(enum#N)」,查不到写「enum#N(未解)」。UI/日志/dump 共用同一口径。
|
||||
fun Label(Module: String, EnumTypeShortName: String, Raw: String): String {
|
||||
val Name = OfRaw(Module, EnumTypeShortName, Raw)
|
||||
return if (Name != null) "$Name($Raw)" else "$Raw(未解)"
|
||||
}
|
||||
}
|
||||
@@ -56,7 +56,10 @@ internal class PendingUplink(
|
||||
|
||||
internal object GarageDriver {
|
||||
|
||||
private const val Url = "ws://localhost:9091/"
|
||||
// 目标服务器:环境变量 TANKIC_WS_URL 优先,默认连主用的 9091
|
||||
private val Url: String = Env("TANKIC_WS_URL").ifEmpty { "ws://localhost:9091/" }
|
||||
// 服务端日志 label(对应 index.js --label):随实例走,否则「配对表 ⇔ 服务端日志」对照会找错目录
|
||||
private val LogLabel: String = Env("TANKIC_LOG_LABEL").ifEmpty { "store-03" }
|
||||
private const val SpaceId = 100073070L
|
||||
private const val TickMs = 120
|
||||
private const val EnterLabel = "进空间(纯推送)"
|
||||
@@ -387,7 +390,7 @@ internal object GarageDriver {
|
||||
Log("--- (a) 配对表 ⇔ 服务端日志「← 重放:发 …」逐条对照 ---")
|
||||
val Root = LogsRoot()
|
||||
if (Root == null) {
|
||||
Log("✗ 找不到 3DTank/server-logs/store-02")
|
||||
Log("✗ 找不到 3DTank/server-logs/$LogLabel")
|
||||
return
|
||||
}
|
||||
val New = ListDir(Root)
|
||||
@@ -541,7 +544,7 @@ internal object GarageDriver {
|
||||
private fun LogsRoot(): String? {
|
||||
val Cwd = js("process.cwd()") as String
|
||||
for (Up in 0..6) {
|
||||
val P = Cwd + "/" + "../".repeat(Up) + "3DTank/server-logs/store-02"
|
||||
val P = Cwd + "/" + "../".repeat(Up) + "3DTank/server-logs/$LogLabel"
|
||||
if (Exists(P)) return P
|
||||
}
|
||||
return null
|
||||
|
||||
@@ -59,6 +59,9 @@ internal class GarageSession(private val OnChanged: () -> Unit) {
|
||||
// 某件物品的「装备槽类别」(pt 模型 6117306895090150422 的 category);没有 pt 数据时返回 null
|
||||
fun CategoryOf(ItemId: String): String? = StateObj.ItemCategory(ItemId)
|
||||
|
||||
// 类别的展示文本(成员名 + 原始序数):UI/日志用;查不到写「enum#N(未解)」,无 pt 数据写「?」
|
||||
fun CategoryLabelOf(ItemId: String): String = StateObj.ItemCategoryLabel(ItemId)
|
||||
|
||||
// ---------------------------------------------------------------- 连接
|
||||
|
||||
fun Connect() {
|
||||
@@ -245,7 +248,7 @@ internal class GarageSession(private val OnChanged: () -> Unit) {
|
||||
EnqueueManual(Uplink.M_FCN, SpaceId.toString(), Bytes)
|
||||
StateObj.MarkMounted(ItemId)
|
||||
AddLog(
|
||||
"⟳ 装备 $ItemId(类别=${Cat ?: "未知"})→ 已入队 M_.fcn(靶=$SpaceId,${Bytes.size} 字节" +
|
||||
"⟳ 装备 $ItemId(类别=${StateObj.ItemCategoryLabel(ItemId)})→ 已入队 M_.fcn(靶=$SpaceId,${Bytes.size} 字节" +
|
||||
(if (Already) ",本地已是已挂载" else ",本地乐观标为已挂载") +
|
||||
(if (SameCat.isNotEmpty()) ",同时把同类 ${SameCat.joinToString(",")} 标为未挂载" else "")
|
||||
)
|
||||
|
||||
@@ -64,6 +64,9 @@ internal class ProtocolTablesData(
|
||||
val CodecByMethod: Map<String, String> = emptyMap(),
|
||||
// 模型真名 → codec 键(字段 TypeCodec 为空时的第二解析路径,口径同 lib_space_codec.js 的 ReadValue)
|
||||
val CodecByModelName: Map<String, String> = emptyMap(),
|
||||
// 枚举成员名**旁路表**(仅显示层用):<moduleId>::<枚举短名> → { <线形序数> → <成员名> }。
|
||||
// 协议层的 enum 原始值仍是 `enum#N`,这张表不参与任何解码/编码,只供 EnumNames 查名。
|
||||
val EnumMembers: Map<String, Map<String, String>> = emptyMap(),
|
||||
// 协议核心类 → 线上基础类型(codec_fields.WireClasses)
|
||||
val WireClasses: Map<String, String> = emptyMap(),
|
||||
// 裸类名 → 线上基础类型(ResourceClasses ∪ 手写表,手写优先;口径同 container.js 的 Alias)
|
||||
@@ -105,6 +108,10 @@ internal object ProtocolTables {
|
||||
|
||||
fun CodecByModelName(ModelName: String): String? = Data.CodecByModelName[ModelName]
|
||||
|
||||
// 枚举成员名(显示层旁路查表):<moduleId>::<枚举短名> → 线形序数 → 成员名。查不到返回 null。
|
||||
fun EnumMember(Module: String, EnumTypeShortName: String, Value: Int): String? =
|
||||
Data.EnumMembers[Module + "::" + EnumTypeShortName]?.get(Value.toString())
|
||||
|
||||
// 裸类名 → 线上基础类型:先查协议核心类(WireClasses),再查直译表(TypeAliases)。
|
||||
// 顺序与 lib_space_codec.js 的 ReadValue 一致(WireClasses 在前,TypeAlias 在后)。
|
||||
fun WireAlias(Type: String): String? = Data.WireClasses[Type] ?: Data.TypeAliases[Type]
|
||||
|
||||
@@ -5,4 +5,4 @@ package Protocol
|
||||
|
||||
// 协议表原始 JSON 文本(紧凑格式,仅此一处副本进入编译产物)
|
||||
internal const val ProtocolTablesJsonText: String =
|
||||
"{\"Note\":\"客户端协议表,由 client/tools/ExportProtocolTables.mjs 从 3DTank/names/ 生成,请勿手改。UplinkByMethod 源 uplink_table.json,DownlinkByMethod 源 downlink_table.json(含 Containers 段)。Codecs/CodecByModelId/CodecByMethod/CodecByModelName 源 codec_fields.json,按「pt.Items 的 modelId + downlink_table 方法」取闭包裁剪(只留字段名/线类型串/TypeCodec,丢掉全部元数据)。TypeAliases = codec_fields.ResourceClasses[].Wire 与手写 RESOURCE_CLASS_WIRE 合并(手写优先),WireClasses 为 codec_fields.WireClasses。只挑选 M1/M2 目标空间(names/space-init-*.json、space-session-*.json)里出现过的 methodId 与全部容器方法。线格式口径:bool/i8=1、i16=2、i32=4、i64=8、f32=4、f64=8(全大端);string/bytes/array = varint 长度 + 原始字节;list<T> = varint 元素数 + 元素;map<K,V> = varint 条数 + 条数 × (K,V);enum<T> = i32(4 字节大端);opt<T> 的「空/非空」是 nullmap 比特流里的 1 个 bit(空值负载里 0 字节),不是 1 字节标志。帧格式见 Protocol/Frame.kt 与 Protocol/Nullmap.kt(口径与 3DTank/tools/server/link.js 一致)。\",\"GeneratedAt\":\"2026-09-18T01:58:44.437Z\",\"Source\":\"3DTank/names/{uplink_table.json,downlink_table.json,codec_fields.json,space-*.json}\",\"PrimitiveWidths\":{\"i8\":1,\"i16\":2,\"i32\":4,\"i64\":8,\"f32\":4,\"f64\":8,\"bool\":1,\"enum\":4},\"VarintPrefixed\":[\"string\",\"bytes\",\"array\"],\"Selection\":{\"DownlinkMethodIds\":31,\"UplinkMethodIds\":7,\"CodecCount\":559,\"CodecByModelIdCount\":356,\"CodecByMethodCount\":432},\"WireClasses\":{\"R\":\"i64\",\"xr\":\"i64\"},\"TypeAliases\":{\"B_\":\"i64\",\"Da\":\"i64\",\"Ea\":\"i64\",\"Gf\":\"i64\",\"Pc\":\"i64\",\"W_\":\"i64\",\"kv\":\"i64\",\"nh\":\"i64\",\"no\":\"i64\",\"pv\":\"i64\",\"qs\":\"i64\",\"uh\":\"i64\",\"uo\":\"i64\",\"zv\":\"i64\"},\"CodecByModelId\":{\"104562115846147942\":\"module_89803::Wt\",\"120961863156462946\":\"module_85196::Du\",\"149643551432886459\":\"module_85196::m_\",\"171896995500198357\":\"module_85183::Sm\",\"213661725715747381\":\"module_85196::gs\",\"216902005816818019\":\"module_31908::Jn\",\"258682513334072065\":\"module_25377::St\",\"260111248825415414\":\"module_85183::Mb\",\"313670832122918648\":\"module_85183::Qw\",\"433350986715773690\":\"module_88693::it\",\"462434031489766566\":\"module_47243::o_\",\"466296741210741928\":\"module_10283::rt\",\"481504601146841174\":\"module_85196::mo\",\"510571369984788727\":\"module_85183::Go\",\"513176302374590130\":\"module_10283::wt\",\"573691036321759779\":\"module_85183::wp\",\"649690707811173020\":\"module_37504::\$t\",\"658797348514407988\":\"module_85196::Fs\",\"693025797100292228\":\"module_47243::zh\",\"701801920209410257\":\"module_85196::Zo\",\"712090483864327130\":\"module_85196::wo\",\"732152133982227177\":\"module_85183::ip\",\"770654516236272028\":\"module_85183::jm\",\"787936358514072261\":\"module_31908::Ei\",\"806128205466208462\":\"module_47243::Wa\",\"821863604528384663\":\"module_88693::lt\",\"834829861153964627\":\"module_85196::q_\",\"844040023906032853\":\"module_85183::fl\",\"868364325972433765\":\"module_54199::mt\",\"871785774630866042\":\"module_37504::\$t\",\"881620222948704326\":\"module_37504::\$t\",\"887431150717814637\":\"module_85183::Hy\",\"889485403566576664\":\"module_47243::\$a\",\"891137447706578619\":\"module_54199::ft\",\"905970599512839002\":\"module_85196::q_\",\"920233600808088339\":\"module_85183::Ry\",\"965113061940997187\":\"module_47243::Oe\",\"973781565169577734\":\"module_85183::pk\",\"994751080759166914\":\"module_89803::ht\",\"1009501302658881534\":\"module_47243::Tu\",\"1036461397335537760\":\"module_47243::Jh\",\"1090112775751802907\":\"module_85183::ol\",\"1092307613749405757\":\"module_85183::Np\",\"1115275516484037832\":\"module_37504::Yt\",\"1130027771466355435\":\"module_85183::Mp\",\"1139677084511624062\":\"module_85196::Ps\",\"1164458258646872337\":\"module_85196::j_\",\"1237976996871886061\":\"module_85183::Rv\",\"1259328549938381181\":\"module_85196::wu\",\"1298753286651761171\":\"module_25377::jt\",\"1417398836682569292\":\"module_47243::Su\",\"1456475022890566348\":\"module_47243::O_\",\"1472449384337994019\":\"module_99527::N\",\"1475210252818583689\":\"module_85183::c_\",\"1500120709087433677\":\"module_85183::Wm\",\"1534251830977989099\":\"module_85196::rs\",\"1535852183188388854\":\"module_47243::pa\",\"1661364280719338177\":\"module_85183::a_\",\"1663344340563853103\":\"module_85183::yw\",\"1688856467034284073\":\"module_85183::yh\",\"1748249573394295263\":\"module_85183::Df\",\"1767155986856291742\":\"module_85183::zb\",\"1782954502314814229\":\"module_85183::Mv\",\"1838248562944554574\":\"module_85183::mb\",\"1850509145998626828\":\"module_25377::wt\",\"1851605205665890036\":\"module_47243::Mc\",\"1865478986390468300\":\"module_85196::_s\",\"1893408944113965505\":\"module_47243::Us\",\"1986821874929333245\":\"module_Line truncated
|
||||
"{\"Note\":\"客户端协议表,由 client/tools/ExportProtocolTables.mjs 从 3DTank/names/ 生成,请勿手改。UplinkByMethod 源 uplink_table.json,DownlinkByMethod 源 downlink_table.json(含 Containers 段)。Codecs/CodecByModelId/CodecByMethod/CodecByModelName 源 codec_fields.json,按「pt.Items 的 modelId + downlink_table 方法」取闭包裁剪(只留字段名/线类型串/TypeCodec,丢掉全部元数据)。TypeAliases = codec_fields.ResourceClasses[].Wire 与手写 RESOURCE_CLASS_WIRE 合并(手写优先),WireClasses 为 codec_fields.WireClasses。EnumMembers 源 evidence.json,是**显示层旁路表**:键 <moduleId>::<枚举短名> → { <线形序数> → <成员名> }。它只供 UI/日志/dump 查名(见 Protocol/EnumNames.kt),协议层的 enum 原始值仍是 `enum#N`,两者互不影响;只导出「导出表的 enum<T> 类型串真正引用到的」枚举(codec 闭包 + 上下行字段表),未命中的键不导出。只挑选 M1/M2 目标空间(names/space-init-*.json、space-session-*.json)里出现过的 methodId 与全部容器方法。线格式口径:bool/i8=1、i16=2、i32=4、i64=8、f32=4、f64=8(全大端);string/bytes/array = varint 长度 + 原始字节;list<T> = varint 元素数 + 元素;map<K,V> = varint 条数 + 条数 × (K,V);enum<T> = i32(4 字节大端);opt<T> 的「空/非空」是 nullmap 比特流里的 1 个 bit(空值负载里 0 字节),不是 1 字节标志。帧格式见 Protocol/Frame.kt 与 Protocol/Nullmap.kt(口径与 3DTank/tools/server/link.js 一致)。\",\"GeneratedAt\":\"2026-09-18T04:55:54.326Z\",\"Source\":\"3DTank/names/{uplink_table.json,downlink_table.json,codec_fields.json,space-*.json}\",\"PrimitiveWidths\":{\"i8\":1,\"i16\":2,\"i32\":4,\"i64\":8,\"f32\":4,\"f64\":8,\"bool\":1,\"enum\":4},\"VarintPrefixed\":[\"string\",\"bytes\",\"array\"],\"Selection\":{\"DownlinkMethodIds\":31,\"UplinkMethodIds\":7,\"CodecCount\":559,\"CodecByModelIdCount\":356,\"CodecByMethodCount\":432,\"EnumClassCount\":77,\"EnumMemberCount\":1343,\"EnumMissCount\":0},\"WireClasses\":{\"R\":\"i64\",\"xr\":\"i64\"},\"TypeAliases\":{\"B_\":\"i64\",\"Da\":\"i64\",\"Ea\":\"i64\",\"Gf\":\"i64\",\"Pc\":\"i64\",\"W_\":\"i64\",\"kv\":\"i64\",\"nh\":\"i64\",\"no\":\"i64\",\"pv\":\"i64\",\"qs\":\"i64\",\"uh\":\"i64\",\"uo\":\"i64\",\"zv\":\"i64\"},\"CodecByModelId\":{\"104562115846147942\":\"module_89803::Wt\",\"120961863156462946\":\"module_85196::Du\",\"149643551432886459\":\"module_85196::m_\",\"171896995500198357\":\"module_85183::Sm\",\"213661725715747381\":\"module_85196::gs\",\"216902005816818019\":\"module_31908::Jn\",\"258682513334072065\":\"module_25377::St\",\"260111248825415414\":\"module_85183::Mb\",\"313670832122918648\":\"module_85183::Qw\",\"433350986715773690\":\"module_88693::it\",\"462434031489766566\":\"module_47243::o_\",\"466296741210741928\":\"module_10283::rt\",\"481504601146841174\":\"module_85196::mo\",\"510571369984788727\":\"module_85183::Go\",\"513176302374590130\":\"module_10283::wt\",\"573691036321759779\":\"module_85183::wp\",\"649690707811173020\":\"module_37504::\$t\",\"658797348514407988\":\"module_85196::Fs\",\"693025797100292228\":\"module_47243::zh\",\"701801920209410257\":\"module_85196::Zo\",\"712090483864327130\":\"module_85196::wo\",\"732152133982227177\":\"module_85183::ip\",\"770654516236272028\":\"module_85183::jm\",\"787936358514072261\":\"module_31908::Ei\",\"806128205466208462\":\"module_47243::Wa\",\"821863604528384663\":\"module_88693::lt\",\"834829861153964627\":\"module_85196::q_\",\"844040023906032853\":\"module_85183::fl\",\"868364325972433765\":\"module_54199::mt\",\"871785774630866042\":\"module_37504::\$t\",\"881620222948704326\":\"module_37504::\$t\",\"887431150717814637\":\"module_85183::Hy\",\"889485403566576664\":\"module_47243::\$a\",\"891137447706578619\":\"module_54199::ft\",\"905970599512839002\":\"module_85196::q_\",\"920233600808088339\":\"module_85183::Ry\",\"965113061940997187\":\"module_47243::Oe\",\"973781565169577734\":\"module_85183::pk\",\"994751080759166914\":\"module_89803::ht\",\"1009501302658881534\":\"module_47243::Tu\",\"1036461397335537760\":\"module_47243::Jh\",\"1090112775751802907\":\"module_85183::ol\",\"1092307613749405757\":\"module_85183::Np\",\"1115275516484037832\":\"module_37504::Yt\",\"1130027771466355435\":\"module_85183::Mp\",\"1139677084511624062\":\"module_85196::Ps\",\"1164458258646872337\":\"module_85196::j_\",\"1237976996871886061\":\"module_85183::Rv\",\"1259328549938381181\":\"module_85196::wu\",\"1298753286651761171\":\"module_25377::jt\",\"1417398836682569292\":\"module_47243::Su\",\"1456475022890566348\":\"module_47243::O_\",\"1472449384337994019\":\"module_99527::N\",\"1475210252818583689\":\"module_85183::c_\",\"1500120709087433677\":\"module_85183::Wm\",\"1534251830977989099\":\"module_85196::rs\",\"1535852183188388854\":\"module_47243::pa\",\"1661364280719338177\":\"module_85183::a_\",\"1663344340563Line truncated
|
||||
@@ -1 +1 @@
|
||||
{"Note":"客户端协议表,由 client/tools/ExportProtocolTables.mjs 从 3DTank/names/ 生成,请勿手改。UplinkByMethod 源 uplink_table.json,DownlinkByMethod 源 downlink_table.json(含 Containers 段)。Codecs/CodecByModelId/CodecByMethod/CodecByModelName 源 codec_fields.json,按「pt.Items 的 modelId + downlink_table 方法」取闭包裁剪(只留字段名/线类型串/TypeCodec,丢掉全部元数据)。TypeAliases = codec_fields.ResourceClasses[].Wire 与手写 RESOURCE_CLASS_WIRE 合并(手写优先),WireClasses 为 codec_fields.WireClasses。只挑选 M1/M2 目标空间(names/space-init-*.json、space-session-*.json)里出现过的 methodId 与全部容器方法。线格式口径:bool/i8=1、i16=2、i32=4、i64=8、f32=4、f64=8(全大端);string/bytes/array = varint 长度 + 原始字节;list<T> = varint 元素数 + 元素;map<K,V> = varint 条数 + 条数 × (K,V);enum<T> = i32(4 字节大端);opt<T> 的「空/非空」是 nullmap 比特流里的 1 个 bit(空值负载里 0 字节),不是 1 字节标志。帧格式见 Protocol/Frame.kt 与 Protocol/Nullmap.kt(口径与 3DTank/tools/server/link.js 一致)。","GeneratedAt":"2026-09-18T01:58:44.437Z","Source":"3DTank/names/{uplink_table.json,downlink_table.json,codec_fields.json,space-*.json}","PrimitiveWidths":{"i8":1,"i16":2,"i32":4,"i64":8,"f32":4,"f64":8,"bool":1,"enum":4},"VarintPrefixed":["string","bytes","array"],"Selection":{"DownlinkMethodIds":31,"UplinkMethodIds":7,"CodecCount":559,"CodecByModelIdCount":356,"CodecByMethodCount":432},"WireClasses":{"R":"i64","xr":"i64"},"TypeAliases":{"B_":"i64","Da":"i64","Ea":"i64","Gf":"i64","Pc":"i64","W_":"i64","kv":"i64","nh":"i64","no":"i64","pv":"i64","qs":"i64","uh":"i64","uo":"i64","zv":"i64"},"CodecByModelId":{"104562115846147942":"module_89803::Wt","120961863156462946":"module_85196::Du","149643551432886459":"module_85196::m_","171896995500198357":"module_85183::Sm","213661725715747381":"module_85196::gs","216902005816818019":"module_31908::Jn","258682513334072065":"module_25377::St","260111248825415414":"module_85183::Mb","313670832122918648":"module_85183::Qw","433350986715773690":"module_88693::it","462434031489766566":"module_47243::o_","466296741210741928":"module_10283::rt","481504601146841174":"module_85196::mo","510571369984788727":"module_85183::Go","513176302374590130":"module_10283::wt","573691036321759779":"module_85183::wp","649690707811173020":"module_37504::$t","658797348514407988":"module_85196::Fs","693025797100292228":"module_47243::zh","701801920209410257":"module_85196::Zo","712090483864327130":"module_85196::wo","732152133982227177":"module_85183::ip","770654516236272028":"module_85183::jm","787936358514072261":"module_31908::Ei","806128205466208462":"module_47243::Wa","821863604528384663":"module_88693::lt","834829861153964627":"module_85196::q_","844040023906032853":"module_85183::fl","868364325972433765":"module_54199::mt","871785774630866042":"module_37504::$t","881620222948704326":"module_37504::$t","887431150717814637":"module_85183::Hy","889485403566576664":"module_47243::$a","891137447706578619":"module_54199::ft","905970599512839002":"module_85196::q_","920233600808088339":"module_85183::Ry","965113061940997187":"module_47243::Oe","973781565169577734":"module_85183::pk","994751080759166914":"module_89803::ht","1009501302658881534":"module_47243::Tu","1036461397335537760":"module_47243::Jh","1090112775751802907":"module_85183::ol","1092307613749405757":"module_85183::Np","1115275516484037832":"module_37504::Yt","1130027771466355435":"module_85183::Mp","1139677084511624062":"module_85196::Ps","1164458258646872337":"module_85196::j_","1237976996871886061":"module_85183::Rv","1259328549938381181":"module_85196::wu","1298753286651761171":"module_25377::jt","1417398836682569292":"module_47243::Su","1456475022890566348":"module_47243::O_","1472449384337994019":"module_99527::N","1475210252818583689":"module_85183::c_","1500120709087433677":"module_85183::Wm","1534251830977989099":"module_85196::rs","1535852183188388854":"module_47243::pa","1661364280719338177":"module_85183::a_","1663344340563853103":"module_85183::yw","1688856467034284073":"module_85183::yh","1748249573394295263":"module_85183::Df","1767155986856291742":"module_85183::zb","1782954502314814229":"module_85183::Mv","1838248562944554574":"module_85183::mb","1850509145998626828":"module_25377::wt","1851605205665890036":"module_47243::Mc","1865478986390468300":"module_85196::_s","1893408944113965505":"module_47243::Us","1986821874929333245":"module_37504::Xt","1987602940683756146":"module_47243::ke","1997576349992148053":"module_85183::rk","2023935033691193766":"module_37504::$t","2046257084008991040":"module_85183::ya","2057320715890332864":"module_85183::ue","2057487026153364495":"module_85183::op","2078544146946392301":"module_85183::Y_","2080556892932195426":"module_47243::Sc","2085529474881905981":"module_31908::li","2101100305743734614":"mLine truncated
|
||||
{"Note":"客户端协议表,由 client/tools/ExportProtocolTables.mjs 从 3DTank/names/ 生成,请勿手改。UplinkByMethod 源 uplink_table.json,DownlinkByMethod 源 downlink_table.json(含 Containers 段)。Codecs/CodecByModelId/CodecByMethod/CodecByModelName 源 codec_fields.json,按「pt.Items 的 modelId + downlink_table 方法」取闭包裁剪(只留字段名/线类型串/TypeCodec,丢掉全部元数据)。TypeAliases = codec_fields.ResourceClasses[].Wire 与手写 RESOURCE_CLASS_WIRE 合并(手写优先),WireClasses 为 codec_fields.WireClasses。EnumMembers 源 evidence.json,是**显示层旁路表**:键 <moduleId>::<枚举短名> → { <线形序数> → <成员名> }。它只供 UI/日志/dump 查名(见 Protocol/EnumNames.kt),协议层的 enum 原始值仍是 `enum#N`,两者互不影响;只导出「导出表的 enum<T> 类型串真正引用到的」枚举(codec 闭包 + 上下行字段表),未命中的键不导出。只挑选 M1/M2 目标空间(names/space-init-*.json、space-session-*.json)里出现过的 methodId 与全部容器方法。线格式口径:bool/i8=1、i16=2、i32=4、i64=8、f32=4、f64=8(全大端);string/bytes/array = varint 长度 + 原始字节;list<T> = varint 元素数 + 元素;map<K,V> = varint 条数 + 条数 × (K,V);enum<T> = i32(4 字节大端);opt<T> 的「空/非空」是 nullmap 比特流里的 1 个 bit(空值负载里 0 字节),不是 1 字节标志。帧格式见 Protocol/Frame.kt 与 Protocol/Nullmap.kt(口径与 3DTank/tools/server/link.js 一致)。","GeneratedAt":"2026-09-18T04:55:54.326Z","Source":"3DTank/names/{uplink_table.json,downlink_table.json,codec_fields.json,space-*.json}","PrimitiveWidths":{"i8":1,"i16":2,"i32":4,"i64":8,"f32":4,"f64":8,"bool":1,"enum":4},"VarintPrefixed":["string","bytes","array"],"Selection":{"DownlinkMethodIds":31,"UplinkMethodIds":7,"CodecCount":559,"CodecByModelIdCount":356,"CodecByMethodCount":432,"EnumClassCount":77,"EnumMemberCount":1343,"EnumMissCount":0},"WireClasses":{"R":"i64","xr":"i64"},"TypeAliases":{"B_":"i64","Da":"i64","Ea":"i64","Gf":"i64","Pc":"i64","W_":"i64","kv":"i64","nh":"i64","no":"i64","pv":"i64","qs":"i64","uh":"i64","uo":"i64","zv":"i64"},"CodecByModelId":{"104562115846147942":"module_89803::Wt","120961863156462946":"module_85196::Du","149643551432886459":"module_85196::m_","171896995500198357":"module_85183::Sm","213661725715747381":"module_85196::gs","216902005816818019":"module_31908::Jn","258682513334072065":"module_25377::St","260111248825415414":"module_85183::Mb","313670832122918648":"module_85183::Qw","433350986715773690":"module_88693::it","462434031489766566":"module_47243::o_","466296741210741928":"module_10283::rt","481504601146841174":"module_85196::mo","510571369984788727":"module_85183::Go","513176302374590130":"module_10283::wt","573691036321759779":"module_85183::wp","649690707811173020":"module_37504::$t","658797348514407988":"module_85196::Fs","693025797100292228":"module_47243::zh","701801920209410257":"module_85196::Zo","712090483864327130":"module_85196::wo","732152133982227177":"module_85183::ip","770654516236272028":"module_85183::jm","787936358514072261":"module_31908::Ei","806128205466208462":"module_47243::Wa","821863604528384663":"module_88693::lt","834829861153964627":"module_85196::q_","844040023906032853":"module_85183::fl","868364325972433765":"module_54199::mt","871785774630866042":"module_37504::$t","881620222948704326":"module_37504::$t","887431150717814637":"module_85183::Hy","889485403566576664":"module_47243::$a","891137447706578619":"module_54199::ft","905970599512839002":"module_85196::q_","920233600808088339":"module_85183::Ry","965113061940997187":"module_47243::Oe","973781565169577734":"module_85183::pk","994751080759166914":"module_89803::ht","1009501302658881534":"module_47243::Tu","1036461397335537760":"module_47243::Jh","1090112775751802907":"module_85183::ol","1092307613749405757":"module_85183::Np","1115275516484037832":"module_37504::Yt","1130027771466355435":"module_85183::Mp","1139677084511624062":"module_85196::Ps","1164458258646872337":"module_85196::j_","1237976996871886061":"module_85183::Rv","1259328549938381181":"module_85196::wu","1298753286651761171":"module_25377::jt","1417398836682569292":"module_47243::Su","1456475022890566348":"module_47243::O_","1472449384337994019":"module_99527::N","1475210252818583689":"module_85183::c_","1500120709087433677":"module_85183::Wm","1534251830977989099":"module_85196::rs","1535852183188388854":"module_47243::pa","1661364280719338177":"module_85183::a_","1663344340563853103":"module_85183::yw","1688856467034284073":"module_85183::yh","1748249573394295263":"module_85183::Df","1767155986856291742":"module_85183::zb","1782954502314814229":"module_85183::Mv","1838248562944554574":"module_85183::mb","1850509145998626828":"module_25377::wt","1851605205665890036":"module_47243::Mc","1865478986390468300":"module_85196::_s","189340894411Line truncated
|
||||
@@ -259,6 +259,88 @@ function SortByMethodId(Obj) {
|
||||
return Out;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- 枚举成员名(显示层旁路表)
|
||||
//
|
||||
// 协议里的 enum<T> 解出来只有**线形序数** `enum#N`(见 FieldReader.kt / lib_space_codec.js),
|
||||
// 真实客户端却知道成员名。证据在 names/evidence.json:
|
||||
// byModule.<moduleId>.enums = { <枚举构造器短名>: [ {field, member, value}, … ] }
|
||||
// 其中 value 就是线形序数(与线上 i32 同口径),member 是成员名。
|
||||
//
|
||||
// 【两层分离】本表只是**旁路**:导出的 EnumMembers 供 UI/日志/dump 查名,
|
||||
// 协议层的原始值仍是 `enum#N`(FieldReader / Container 一字不改),
|
||||
// 所以 client/tools/CompareContainerDecode.mjs 的逐字段字节对照口径不受任何影响。
|
||||
//
|
||||
// 取哪些枚举:只取「导出表里真正会出现的 enum<T>」——即 codec 闭包 + 上下行字段表里的 enum 类型串。
|
||||
// 枚举短名是**模块内局部别名**,真正定义它的模块可能是字段 TypeCodec 指向的模块
|
||||
// (例:module_85196::Lo 的 category 是 `enum<ja>`,但 ja 定义在 module_85667::pu)。
|
||||
// 因此解析顺序:① 字段所属模块的 evidence → ② TypeCodec 模块的 evidence;两条都不中记「未解」。
|
||||
// 键仍是字段所属模块(`<moduleId>::<短名>`),这样调用方拿「字段所在 codec 的模块号」就能查到。
|
||||
const EvidenceSrc = ReadJson(path.join(NamesDir, 'evidence.json'));
|
||||
const EnumEvidenceByModule = {};
|
||||
for (const [M, V] of Object.entries(EvidenceSrc.byModule || {})) {
|
||||
EnumEvidenceByModule[String(Number(M))] = V.enums || {};
|
||||
}
|
||||
const ModuleIdOf = (M) => {
|
||||
const R = /^module_(\d+)$/.exec(String(M || ''));
|
||||
return R ? R[1] : String(M || '');
|
||||
};
|
||||
const EnumMembersOf = (M, S) => (EnumEvidenceByModule[ModuleIdOf(M)] || {})[S];
|
||||
|
||||
// 取类型串里所有 enum<X> 的 X(覆盖 enum<X> / list<enum<X>> / opt<enum<X>> / map<K, enum<X>>)
|
||||
function CollectEnumShorts(Type, Out) {
|
||||
const Re = /enum<([^<>]+)>/g;
|
||||
let M;
|
||||
while ((M = Re.exec(String(Type || ''))) !== null) Out.push(M[1]);
|
||||
}
|
||||
|
||||
const EnumRefs = []; // { Module, Short, TypeCodec, Where }
|
||||
function AddEnumRefs(Type, Module, TypeCodec, Where) {
|
||||
const Shorts = [];
|
||||
CollectEnumShorts(Type, Shorts);
|
||||
for (const S of Shorts) EnumRefs.push({ Module: ModuleIdOf(Module), Short: S, TypeCodec: String(TypeCodec || ''), Where });
|
||||
}
|
||||
for (const Key of CodecClosure) {
|
||||
const C = CodecAll[Key];
|
||||
for (const F of C.Fields || []) AddEnumRefs(F.Type, C.ModuleId || C.Module, F.TypeCodec, 'codec');
|
||||
}
|
||||
for (const Spec of Object.values(DownlinkByMethod)) for (const F of Spec.Fields) AddEnumRefs(F.Type, Spec.Module, F.TypeCodec, 'downlink');
|
||||
for (const Spec of Object.values(UplinkByMethod)) for (const F of Spec.Fields) AddEnumRefs(F.Type, Spec.Module, F.TypeCodec, 'uplink');
|
||||
|
||||
const EnumMembers = {};
|
||||
const EnumSourceModule = {};
|
||||
const EnumMisses = [];
|
||||
for (const R of EnumRefs) {
|
||||
const Key = R.Module + '::' + R.Short;
|
||||
if (Object.prototype.hasOwnProperty.call(EnumMembers, Key)) continue;
|
||||
let Members = EnumMembersOf(R.Module, R.Short);
|
||||
let From = R.Module;
|
||||
if (!Members || !Members.length) {
|
||||
const Tm = /^module_(\d+)::/.exec(R.TypeCodec);
|
||||
if (Tm) {
|
||||
const M2 = EnumMembersOf(Tm[1], R.Short);
|
||||
if (M2 && M2.length) {
|
||||
Members = M2;
|
||||
From = Tm[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!Members || !Members.length) {
|
||||
if (!EnumMisses.some((X) => X.Key === Key)) EnumMisses.push({ Key, Where: R.Where, TypeCodec: R.TypeCodec });
|
||||
continue;
|
||||
}
|
||||
const Map = {};
|
||||
for (const E of Members) {
|
||||
const V = String(E.value);
|
||||
if (!(V in Map)) Map[V] = String(E.member);
|
||||
}
|
||||
EnumMembers[Key] = Map;
|
||||
EnumSourceModule[Key] = From;
|
||||
}
|
||||
// 键排序,保证产物顺序稳定(幂等)
|
||||
const EnumMembersSorted = {};
|
||||
for (const K of Object.keys(EnumMembers).sort()) EnumMembersSorted[K] = EnumMembers[K];
|
||||
const EnumMemberTotal = Object.values(EnumMembersSorted).reduce((A, M) => A + Object.keys(M).length, 0);
|
||||
|
||||
const Tables = {
|
||||
Note:
|
||||
'客户端协议表,由 client/tools/ExportProtocolTables.mjs 从 3DTank/names/ 生成,请勿手改。' +
|
||||
@@ -267,6 +349,9 @@ const Tables = {
|
||||
'downlink_table 方法」取闭包裁剪(只留字段名/线类型串/TypeCodec,丢掉全部元数据)。' +
|
||||
'TypeAliases = codec_fields.ResourceClasses[].Wire 与手写 RESOURCE_CLASS_WIRE 合并(手写优先),' +
|
||||
'WireClasses 为 codec_fields.WireClasses。' +
|
||||
'EnumMembers 源 evidence.json,是**显示层旁路表**:键 <moduleId>::<枚举短名> → { <线形序数> → <成员名> }。' +
|
||||
'它只供 UI/日志/dump 查名(见 Protocol/EnumNames.kt),协议层的 enum 原始值仍是 `enum#N`,两者互不影响;' +
|
||||
'只导出「导出表的 enum<T> 类型串真正引用到的」枚举(codec 闭包 + 上下行字段表),未命中的键不导出。' +
|
||||
'只挑选 M1/M2 目标空间(names/space-init-*.json、space-session-*.json)里出现过的 methodId 与全部容器方法。' +
|
||||
'线格式口径:bool/i8=1、i16=2、i32=4、i64=8、f32=4、f64=8(全大端);string/bytes/array = varint 长度 + 原始字节;' +
|
||||
'list<T> = varint 元素数 + 元素;map<K,V> = varint 条数 + 条数 × (K,V);enum<T> = i32(4 字节大端);' +
|
||||
@@ -282,6 +367,9 @@ const Tables = {
|
||||
CodecCount: Object.keys(Codecs).length,
|
||||
CodecByModelIdCount: Object.keys(CodecByModelId).length,
|
||||
CodecByMethodCount: Object.keys(CodecByMethod).length,
|
||||
EnumClassCount: Object.keys(EnumMembersSorted).length,
|
||||
EnumMemberCount: EnumMemberTotal,
|
||||
EnumMissCount: EnumMisses.length,
|
||||
},
|
||||
WireClasses,
|
||||
TypeAliases: SortKeys(TypeAliases),
|
||||
@@ -291,6 +379,7 @@ const Tables = {
|
||||
Codecs,
|
||||
UplinkByMethod: SortByMethodId(UplinkByMethod),
|
||||
DownlinkByMethod: SortByMethodId(DownlinkByMethod),
|
||||
EnumMembers: EnumMembersSorted,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------- 落盘
|
||||
@@ -333,3 +422,16 @@ console.log(
|
||||
' / modelId ' + Tables.Selection.CodecByModelIdCount + ' 个 / methodId ' + Tables.Selection.CodecByMethodCount + ' 个',
|
||||
);
|
||||
console.log(' 目标空间模板 ' + SpaceFiles.length + ' 个:' + SpaceFiles.join(', '));
|
||||
// EnumMembers 的体积增量:把该键从同一份对象里摘掉再序列化一次做差(其余字段逐字节相同)
|
||||
const EnumJsonLength = JSON.stringify(EnumMembersSorted).length;
|
||||
const NoEnum = {};
|
||||
for (const K of Object.keys(Tables)) if (K !== 'EnumMembers') NoEnum[K] = Tables[K];
|
||||
const EnumDelta = Json.length - JSON.stringify(NoEnum).length;
|
||||
console.log(
|
||||
' 枚举成员名 ' + Tables.Selection.EnumClassCount + ' 个枚举类 / ' + Tables.Selection.EnumMemberCount + ' 个成员' +
|
||||
'(enum 引用 ' + EnumRefs.length + ' 处 / 未解 ' + Tables.Selection.EnumMissCount + ' 处)' +
|
||||
',EnumMembers 占 ' + EnumJsonLength + ' 字符,整表增量 ' + EnumDelta + ' 字符',
|
||||
);
|
||||
if (EnumMisses.length) {
|
||||
console.log(' 未解枚举(前 10):' + EnumMisses.slice(0, 10).map((X) => X.Key + '(' + X.Where + ')').join(', '));
|
||||
}
|
||||
Reference in new issue
Block a user