diff --git a/docs/guides/配置与调试指南.md b/docs/guides/配置与调试指南.md new file mode 100644 index 0000000..dc67d04 --- /dev/null +++ b/docs/guides/配置与调试指南.md @@ -0,0 +1,509 @@ +# YouleNexus 配置与调试指南 + +本指南面向在新 Cocos 前端 `YouleNexus` 上做开发联调的工程师,讲清楚两件事: + +1. **配置**——渠道身份(agentid/channelid/gameid/marketid/version)从哪来、远程配置文件怎么读、服务器地址怎么定。 +2. **调试**——如何一键切到本地/测试服、临时替换渠道身份、跑自动化测试、做真机端到端联调。 + +对应实现位于 `cocoscreator_projects/YouleNexus/assets/framework/config/`,设计依据见 `docs/superpowers/specs/2026-06-28-config-channel-design.md`。 + +> **一句话心智模型**:启动时调用 `resolveBootstrap(...)`,它把「构建期默认 + 运行期来源 + 调试 profile + URL 覆盖」合并成最终 `identity`,再决定 `servers`(调试服直连 或 远程配置解析),产出 `{ identity, servers }` 喂给 `NetClient`。 + +--- + +## 目录 + +1. [快速上手(三个最常见场景)](#1-快速上手三个最常见场景) +2. [整体数据流](#2-整体数据流) +3. [渠道身份 ChannelIdentity](#3-渠道身份-channelidentity) +4. [一键切换 debug / release 模式](#4-一键切换-debug--release-模式) +5. [调试 Profiles](#5-调试-profiles) +6. [URL Query 参数清单](#6-url-query-参数清单) +7. [服务器地址是怎么定的](#7-服务器地址是怎么定的) +8. [远程配置文件](#8-远程配置文件) +9. [启动编排 resolveBootstrap 与接入 NetClient](#9-启动编排-resolvebootstrap-与接入-netclient) +10. [原生联调(window.settings / app_*)](#10-原生联调windowsettings--app_) +11. [跑测试与类型检查](#11-跑测试与类型检查) +12. [常见调试场景配方](#12-常见调试场景配方) +13. [真实服务器端到端联调](#13-真实服务器端到端联调) +14. [发布前检查清单](#14-发布前检查清单) +15. [红线:哪些字符串绝不能改](#15-红线哪些字符串绝不能改) +16. [API 速查表](#16-api-速查表) + +--- + +## 1. 快速上手(三个最常见场景) + +> 下面假设你在 Cocos 编辑器预览(浏览器/WebView 环境)下调试,地址栏可加 query 参数。 + +| 我想…… | 怎么做 | +| --- | --- | +| **连本地服务器**(`ws://127.0.0.1:3088`) | 预览 URL 加 `?profile=local`。直接连本地,不抓远程配置。 | +| **用测试配置服**(staging 的 gameserver) | 预览 URL 加 `?profile=staging`。 | +| **临时换一个渠道身份调一个 bug** | URL 加 `?agentid=xxx&channelid=yyy&marketid=2`,覆盖最高优先级,其余不变。 | + +更多组合见 [§12 常见调试场景配方](#12-常见调试场景配方)。 + +--- + +## 2. 整体数据流 + +``` + ┌─────────────────────────────────────────────┐ +启动调用 │ resolveBootstrap(opts) │ +resolveBootstrap ─────► │ │ + │ ① resolveActiveProfile(search) │ + │ └─ ACTIVE_PROFILE 或 ?profile= 切换 │ + │ │ + │ ② 合并身份 resolveIdentity([...]) │ + │ defaults < 运行时(native|query) │ + │ < profile.identity │ + │ < 显式 URL query (最高) │ + │ │ + │ ③ 决定服务器: │ + │ profile.server ? → 直连(跳过远程) │ + │ 否则 fetcher.fetch(gameserver) │ + │ → getParam 分层取参 │ + │ → resolveServers → ws:// 候选 │ + └───────────────┬─────────────────────────────┘ + │ { identity, servers, rawConfig, profile } + ▼ + net.setIdentity(identity 相关字段) + new NetClient({ servers }) → net.start() +``` + +**文件分工**(每个文件单一职责,纯逻辑与 IO 分离,均可在 Node 下测): + +| 文件 | 职责 | +| --- | --- | +| `config/identity.ts` | `ChannelIdentity` 类型、`IdentitySource` 接口、`resolveIdentity()` 纯合并 | +| `config/sources/defaults.ts` | `BUILD_IDENTITY` 构建期默认(旧 `version.js` 等价物) | +| `config/sources/query-string.ts` | `queryStringSource()`:H5 从 URL query 读身份 | +| `config/sources/native-settings.ts` | `nativeSettingsSource()`:原生从 `window.settings`/`app_*` 同步读身份 | +| `config/profiles.ts` | `DebugProfile` / `PROFILES` / `resolveActiveProfile()` 调试档位 | +| `config/remote-config.ts` | 远程配置类型、`ConfigFetcher` 接口、`getParam()` 分层取参、`resolveServers()` | +| `config/remote-config-fetcher.ts` | 生产用 `HttpConfigFetcher`(全局 `fetch` 抓取 txt) | +| `config/runtime-mode.ts` | `resolveRuntimeMode()` 运行模式(debug/release)解析,`MODE_OVERRIDE` 手动覆盖 | +| `config/bootstrap.ts` | `resolveBootstrap()` 启动编排,串起以上全部 | + +--- + +## 3. 渠道身份 ChannelIdentity + +最终要发给服务器 `player_login` 的渠道身份字段。**属性名就是协议字段名,逐字不可改**(见 [§15](#15-红线哪些字符串绝不能改))。 + +```ts +interface ChannelIdentity { + agentid: string | number; // 代理商 ID + channelid: string | number; // 渠道 ID + gameid: string; // 游戏标识(每个子游戏固定) + marketid: string | number; // 市场/包渠道 ID + version: string; // 版本号(旧 GameData.Version) + versionCode: number; // 版本码(旧 GameData.versionCode) +} +``` + +### 各字段从哪来 + +| 字段 | 主来源 | 说明 | +| --- | --- | --- | +| `gameid` | **编译期固定**(`BUILD_IDENTITY`,每子游戏不同) | 旧工程写死在 `version.js`。一般不随环境变。 | +| `agentid` | 运行期:H5 走 URL query,原生走 `window.settings.getothername('agent')` | 默认值在 `BUILD_IDENTITY`,运行期覆盖。 | +| `channelid` | 运行期:H5 走 URL query,原生走 `window.settings.getchannelName()` | 同上。 | +| `marketid` | 运行期:H5 走 URL query,原生走 `window.settings.getmarketname()`(默认 `4`) | 同上。 | +| `version` / `versionCode` | `BUILD_IDENTITY` 默认;H5 可用 `?version=` 覆盖 | — | + +### 合并优先级(低 → 高,后者覆盖前者) + +``` +BUILD_IDENTITY(构建期默认) + < 运行时来源(原生环境=nativeSettings | H5 环境=queryString) + < profile.identity(当前调试档位的身份覆盖) + < 显式 URL query(最高,方便临时改单字段) +``` + +`resolveIdentity(sources)` 按数组顺序合并,**只覆盖"有效值"字段**——`undefined`/`null`/`空字符串` 不覆盖(所以一个来源读不到某字段时,不会把已有值清空);注意 **`0` 是有效值**会覆盖(如 `marketid=0`)。 + +> H5 模式下 `queryString` 同时担任「运行时来源」和「最高覆盖」两个角色,因此在 URL 里写 `?agentid=...` 永远生效且优先级最高。 + +--- + +## 4. 一键切换 debug / release 模式 + +运行模式是比 profile 更高一层的总开关,由 `config/runtime-mode.ts` 提供。 + +| 模式 | 怎么进入 | profile | URL 覆盖(?profile=/?agentid=…) | 身份来源 | 收发包日志 | +| --- | --- | --- | --- | --- | --- | +| **debug** | debug/预览构建自动进入;或 `MODE_OVERRIDE='debug'` | `?profile=` 可切(默认 prod) | **全部生效** | 原生或 URL(按 `isNative`) | 开(`isDebugger=true`) | +| **release** | release 构建自动进入;或 `MODE_OVERRIDE='release'` | **强制 prod** | **全部忽略** | **只走原生 `window.settings`** | 关 | + +### 三种切换姿势 + +1. **什么都不动**:发 release 包自动 release,编辑器预览自动 debug。模式由 Cocos 全局 `DEBUG` 决定。 +2. **release 包里临时调试**:把 `config/runtime-mode.ts` 的 `MODE_OVERRIDE` 设为 `'debug'`。 +3. **预览里验证正式行为**:把 `MODE_OVERRIDE` 设为 `'release'`。 + +> release 之所以能放心锁死 URL:正式包是 Cocos 原生(jsb),渠道身份走 `window.settings`/`app_*`,本就不依赖 URL。 + +### 接入 + +```ts +import { resolveRuntimeMode } from 'db://assets/framework/config/runtime-mode'; + +const isDebugBuild = typeof DEBUG !== 'undefined' ? DEBUG : true; // Cocos 全局 +const mode = resolveRuntimeMode(isDebugBuild); // 'debug' | 'release' +// 传给 resolveBootstrap({ mode, ... }),并把 result.isDebugger 传给 NetClient 的 debug 选项 +``` + +--- + +## 5. 调试 Profiles + +Profiles 取代了旧工程的 `Game_Config.Debugger`(`serverType` / `gameserver` / `isDebugger`)。一个 profile 打包了「连哪个服 + 用什么渠道身份 + 日志开关」。 + +```ts +interface DebugProfile { + name: string; + server?: string; // 显式 ws 调试服地址;给定则跳过远程抓取 + gameserver?: string; // 远程配置 txt URL(不含防缓存串) + identity?: Partial; // 调试用渠道身份覆盖 + isDebugger?: boolean; // 收发包日志开关 +} +``` + +### 内置档位(`config/profiles.ts`) + +| profile | server(直连) | gameserver(远程配置) | isDebugger | 用途 | +| --- | --- | --- | --- | --- | +| `prod` | — | `http://ylyxservice1.0791ts.cn/config/update_json.txt` | `false` | **正式发布**。走远程配置解析服务器。 | +| `staging` | — | `http://testgame.youlehdyx.com/update_json/ceshi_json.txt` | `true` | 测试配置服。 | +| `local` | `ws://127.0.0.1:3088` | — | `true` | 本地服务器直连,**不抓远程**。 | + +### 如何切换 + +- **默认档位**:`ACTIVE_PROFILE`(当前为 `'prod'`),在 `config/profiles.ts` 顶部常量。 +- **临时切换**:URL 加 `?profile=`,例如 `?profile=local`、`?profile=staging`。未知名字会回退到 `ACTIVE_PROFILE`。 + +```ts +resolveActiveProfile('?profile=local') // → PROFILES.local +resolveActiveProfile('') // → PROFILES.prod(ACTIVE_PROFILE) +resolveActiveProfile('?profile=xxx') // → PROFILES.prod(未知回退) +``` + +### 加一个自己的档位 + +直接在 `PROFILES` 里加一项即可,例如连组内某台联调机: + +```ts +export const PROFILES: Record = { + prod: { name: 'prod', gameserver: DEFAULT_GAMESERVER, isDebugger: false }, + staging: { name: 'staging', gameserver: STAGING_GAMESERVER, isDebugger: true }, + local: { name: 'local', server: 'ws://127.0.0.1:3088', isDebugger: true }, + // 新增:直连联调机 + 指定测试渠道身份 + lab: { name: 'lab', server: 'ws://192.168.1.50:3088', isDebugger: true, + identity: { agentid: 'test_agent', channelid: 'test_channel' } }, +}; +``` + +之后用 `?profile=lab` 即可。 + +--- + +## 6. URL Query 参数清单 + +预览/调试时可在地址栏拼接这些参数(H5 环境生效): + +| 参数 | 作用 | 示例 | +| --- | --- | --- | +| `profile` | 切换调试档位 | `?profile=local` | +| `agentid` | 覆盖代理商 ID(最高优先级) | `?agentid=00bA05...` | +| `channelid` | 覆盖渠道 ID | `?channelid=frdt0C...` | +| `marketid` | 覆盖市场 ID | `?marketid=2` | +| `version` | 覆盖版本号 | `?version=1.2` | + +多个参数用 `&` 连接:`?profile=staging&agentid=xxx&marketid=2`。 + +> `gameid` 不从 URL 取(按子游戏固定在 `BUILD_IDENTITY`)。 + +--- + +## 7. 服务器地址是怎么定的 + +`resolveBootstrap` 决定 `servers`(一个 `ws://` 地址数组,交给 `NetClient` 轮询)的顺序: + +1. **当前 profile 有 `server`** → 直接用它,**跳过远程配置抓取**(`local`/`lab` 这类直连档位走这条)。 +2. **否则**抓远程配置文件(profile 的 `gameserver`,没有就用 `DEFAULT_GAMESERVER`),用 `getParam` 取出连接参数,`resolveServers` 组装候选列表。 +3. 远程抓取失败或解析不出地址时:若调用方传了 `fallbackServers` 则降级使用;否则抛 `ConfigFetchError`(交由上层重试,重连本身归 `NetClient`)。 + +`resolveServers` 取的连接参数键(**逐字一致**原工程,按此优先级): + +``` +player_server_tcp → visitor_server_tcp → game_server_tcp +``` + +值形如 `ip:port`,自动加 `ws://` 前缀(已带 `ws://`/`wss://` 的不重复加),并去重。 + +> **为什么只取 `*_tcp` 不取 `*_http`**:YouleNexus 是 WebSocket-only(对应旧 `netType==0` 分支)。旧工程的 `*_server_http` 属 HTTP 传输模式(`netType==1`),本期不在范围,故有意省略。 + +--- + +## 8. 远程配置文件 + +### 是什么 + +一个放在配置服务器上的 `.txt` 文件(内容是 JSON),URL 即 profile 的 `gameserver`。抓取时自动追加防缓存时间戳:`?`。 + +生产抓取实现 `HttpConfigFetcher`(`config/remote-config-fetcher.ts`)用全局 `fetch` GET 该 URL 并 `JSON.parse`,解析失败抛 `ConfigParseError`。 + +### 结构与分层取参 `getParam` + +远程配置是**分层覆盖**结构,越具体的层级优先级越高: + +``` +data ← 顶层默认 + └─ agentlist[ {agentid} ← 按 agentid 匹配 + gamelist[ {gameid} ← 再按 gameid 匹配 + channellist[ {channelid} ← 再按 channelid + marketlist[ {marketid} ] ← 最后按 marketid + ]]] +``` + +`getParam(config, key, id)` 用当前身份(agentid/gameid/channelid/marketid)逐层下钻,命中的最具体层若带该 `key` 就更新返回值,匹配链中断则返回当前已找到的最具体值(最终可能是顶层默认或 `null`)。这与旧工程 `get_paravalue` 语义逐字一致。 + +示例: + +```jsonc +{ + "data": { + "player_server_tcp": "1.1.1.1:1000", // 顶层默认 + "agentlist": [{ + "agentid": "A", + "player_server_tcp": "2.2.2.2:2000", // A 代理商覆盖 + "gamelist": [{ + "gameid": "G", + "channellist": [{ + "channelid": "C", + "player_server_tcp": "3.3.3.3:3000" // A/G/C 这条最具体 → 命中它 + }] + }] + }] + } +} +``` + +身份 `{agentid:'A', gameid:'G', channelid:'C', ...}` → `getParam(cfg,'player_server_tcp',id)` 得 `3.3.3.3:3000`;若 channelid 换成没配的值,则回退到 `2.2.2.2:2000`;agentid 也没配则回退顶层 `1.1.1.1:1000`。 + +> 平台/UI 类参数(公告 `menunotice`、跑马灯 `scrollmsg`、客服 `service_*`、登录图 `logimage`、排行榜 `rankList` 等)**复用同一个 `getParam`**,将在后续 platform 层接入;本期只解析了连接相关的服务器地址。 + +--- + +## 9. 启动编排 resolveBootstrap 与接入 NetClient + +### 签名 + +```ts +interface BootstrapOptions { + mode: 'debug' | 'release'; // 运行模式(由 resolveRuntimeMode 决定) + win: NativeHost; // 原生注入宿主(≈ window);H5 下传 globalThis/window 即可 + search: string; // location.search(如 '?profile=local') + fetcher: ConfigFetcher; // 远程配置抓取;生产传 new HttpConfigFetcher() + isNative: boolean; // 环境判定:原生 WebView=true,H5/浏览器=false(由调用方算) + fallbackServers?: string[];// 远程失败/无地址时降级 + cacheBust?: () => string; // 防缓存串注入(默认 String(Date.now())) +} + +interface BootstrapResult { + identity: ChannelIdentity; + servers: string[]; + rawConfig: RemoteConfig | null; // 直连档位为 null + profile: DebugProfile; + mode: 'debug' | 'release'; // 透传入参 mode + isDebugger: boolean; // 是否开启收发包日志(debug 模式 + profile.isDebugger) +} + +function resolveBootstrap(opts: BootstrapOptions): Promise; +``` + +### 典型接入(生产 / H5 预览) + +```ts +import { resolveBootstrap } from 'db://assets/framework/config/bootstrap'; +import { resolveRuntimeMode } from 'db://assets/framework/config/runtime-mode'; +import { HttpConfigFetcher } from 'db://assets/framework/config/remote-config-fetcher'; +import { NetClient } from 'db://assets/framework/net/net-client'; +import { CocosWebSocketTransport } from 'db://assets/framework/net/cocos-transport'; + +async function startup() { + const href = location.href; + const isDebugBuild = typeof DEBUG !== 'undefined' ? DEBUG : true; + const mode = resolveRuntimeMode(isDebugBuild); + + const result = await resolveBootstrap({ + mode, + win: globalThis as any, + search: location.search, + fetcher: new HttpConfigFetcher(), + isNative: href.indexOf('index.html') > -1, // 等价旧 Logic.isH5Version() 反向 + fallbackServers: [], // 可选:填应急地址 + }); + + const net = new NetClient({ + servers: result.servers, + transportFactory: () => new CocosWebSocketTransport(), + debug: result.isDebugger, + }); + + // 渠道身份 + 玩家档案合并成完整 player_login 数据 + net.setIdentity({ + ...result.identity, // agentid/channelid/gameid/marketid/version... + openid, nickname, avatar, sex, province, city, unionid, // 来自登录/微信信息 + }); + + net.on('login', (data) => { /* 解析 parseLoginResponse(data) */ }); + net.start(); +} +``` + +> `NetClient.setIdentity` 需要的是完整 `player_login` 字段(`LoginRequestData`)。`resolveBootstrap` 只负责其中的**渠道身份**部分;玩家档案字段(`openid/nickname/avatar/...`)由登录/微信信息提供,二者合并后再 `setIdentity`。 + +### 环境判定 `isNative` + +沿用旧工程 `Logic.isH5Version()` 的判据:**URL 含 `index.html` → 原生 WebView**(`isNative=true`);否则 H5/浏览器(`isNative=false`)。判定逻辑放在调用方,便于测试注入。 + +--- + +## 10. 原生联调(window.settings / app_*) + +原生模式(`isNative=true`)下,渠道身份从原生注入对象同步读取,**接口名/全局名逐字一致**旧工程: + +| 字段 | 主接口 | 全局回退 | +| --- | --- | --- | +| `agentid` | `window.settings.getothername('agent')` | `window.app_agent` | +| `channelid` | `window.settings.getchannelName()` | (无) | +| `marketid` | `window.settings.getmarketname()` | `window.app_market` | + +读取做了 `try/catch`:主接口抛错(原生桥未就绪)则尝试全局回退;都拿不到则该字段不覆盖(保留 `BUILD_IDENTITY` 默认)。 + +> 原生侧需要保证在 H5 启动前注入 `window.settings` 或上述 `app_*` 全局。完整的异步 WVJB 桥(分享/视频/语音/电量等)不在本子系统范围,属后续 sdk 层;本期只做**身份相关的同步读取**。 + +--- + +## 11. 跑测试与类型检查 + +配置/渠道子系统是纯逻辑 + IO 分离设计,全部可在 Node 下自动化测试(无需 Cocos 运行时)。 + +```bash +# 工作目录:cocoscreator_projects/ +cd cocoscreator_projects + +npm run test:framework # 跑全部框架测试(node:test + tsx) +npm run typecheck:framework # 类型检查(tsc --noEmit) +``` + +要点: + +- 测试文件在 `cocoscreator_projects/framework-tests/`(**Cocos `assets/` 之外**,不被编辑器编译),实现在 `YouleNexus/assets/framework/`。 +- `test:framework` 实际跑的是 `scripts/run-framework-tests.mjs`,递归收集 `framework-tests/**/*.test.ts`。**原因**:本机 Node 20.13 < 20.14,`node --test` 还不支持 glob,故用脚本枚举。 +- 依赖 `tsx`、`typescript`、`@types/node`(devDependencies)。 +- tsconfig(`tsconfig.framework.json`)开了 `allowImportingTsExtensions`,所以**框架内 import 必须带 `.ts` 后缀**。 + +> 写测试时注意一个 TS 6.x 坑:对「只在闭包内赋值、初值为 `null`」的变量,经 `assert.ok(x)` 收窄后会被推成 `never`。规避法:把变量声明为 `any`,或在断言处把数据传给纯函数再断言(参考 `framework-tests/integration/login-flow.test.ts`)。 + +--- + +## 12. 常见调试场景配方 + +### A. 连本地服务器跑一局 + +1. 本地起服务器在 `127.0.0.1:3088`(或改 `local` 档位的 `server`)。 +2. 预览 URL 加 `?profile=local`。 +3. 这条直连、不抓远程配置;`isDebugger=true` 会输出收发包日志。 + +### B. 用测试配置服 + 默认渠道 + +预览 URL 加 `?profile=staging`。走 staging 的 gameserver 解析出服务器地址。 + +### C. 复现某渠道专属 bug + +保持当前档位,URL 追加该渠道的身份: +``` +?agentid=<该渠道agentid>&channelid=<该渠道channelid>&marketid=<该渠道marketid> +``` +这些覆盖优先级最高,远程配置会据此分层取到该渠道的服务器/参数。 + +### D. 连组内某台联调机 + +在 `PROFILES` 里加一个档位(见 [§5](#5-调试-profiles) 的 `lab` 例子),用 `?profile=lab`。 + +### E. 远程配置服挂了也要能调 + +给 `resolveBootstrap` 传 `fallbackServers: ['ws://<应急地址>']`,抓取失败时自动降级。 + +--- + +## 13. 真实服务器端到端联调 + +> 对应 Plan 2 的 Task 12 Step 4-5(自动化 mock 已全绿,真机联调需要外部输入)。 + +**前置**:① 可联调的服务器地址(`ws://ip:port` 或配置服 `gameserver` URL);② 一组后端认可的有效渠道身份(`agentid/channelid/marketid` + 目标子游戏的 `gameid`)。 + +**步骤**: + +1. 用 `cocos-creator-mcp` 的 `debug_execute_script` 注入一段临时调试入口(或挂一个调试脚本),按 [§9](#9-启动编排-resolvebootstrap-与接入-netclient) 的接入示例:`resolveBootstrap` + `HttpConfigFetcher` + `NetClient` + `CocosWebSocketTransport`。 + - 想直连:在 `PROFILES` 临时加一档带 `server` 的 profile,或直接 `new NetClient({ servers: ['ws://<联调地址>'] })`。 + - 想验证远程配置链路:把 `gameserver` 指向联调配置服,走 `resolveBootstrap` 完整流程。 +2. `net.setIdentity({...result.identity, ...玩家档案})`,监听 `open`/`login`/`message`/`slow`/`reconnecting`。 +3. `net.start()` 连真机。 +4. 断言/观察:收到 `@toconcon` 握手不报错 → 发出 `player_login` → 收到 `player_login` 响应且 `parseLoginResponse(data).ok === true` → console 打印 `playerid`/资产。 +5. 用 `debug_get_console_logs` / `debug_screenshot` 留存联调证据。 +6. 把联调结论(成功/协议偏差)回填到 `docs/protocol/` 对应章节的「⚠️待服务器确认」项。 + +--- + +## 14. 发布前检查清单 + +- [ ] `config/runtime-mode.ts` 的 `MODE_OVERRIDE` 为 `null`(发布走自动模式,由 release 构建决定)。 +- [ ] `config/profiles.ts` 的 `ACTIVE_PROFILE` 为 `'prod'`。 +- [ ] `prod` 档位 `isDebugger: false`(不输出收发包日志)。 +- [ ] 不依赖任何 `?profile=`/`?agentid=` 之类 URL 覆盖(生产 URL 应是干净的)。 +- [ ] 目标子游戏的 `BUILD_IDENTITY.gameid` 正确(每子游戏不同)。 +- [ ] `gameserver`(`DEFAULT_GAMESERVER`)指向正式配置服。 +- [ ] `npm run test:framework` 全绿、`npm run typecheck:framework` exit 0。 + +--- + +## 15. 红线:哪些字符串绝不能改 + +跨边界到「服务器 / 原生 / 配置服务」的字符串契约**必须逐字不变**(CLAUDE.md 三条红线)。内部变量/类型/函数名可以现代化命名,但下面这些字面量不能动: + +- **协议字段名**:`agentid`、`channelid`、`gameid`、`marketid`、`openid`、`version`、`route`、`rpc` 等。 +- **原生接口名/全局名**:`window.settings.getothername`、`getchannelName`、`getmarketname`;全局 `app_agent`、`app_market`。 +- **远程配置键名**:`agentlist`、`gamelist`、`channellist`、`marketlist` 及其匹配键 `agentid/gameid/channelid/marketid`;连接参数 `player_server_tcp`、`visitor_server_tcp`、`game_server_tcp`(以及未来用到的 `*_http` 等)。 +- **gameserver URL 防缓存**:`?<时间戳>` 形式。 + +改这些之前先核对旧工程源码(`12_Logic.js` 的 `get_paravalue`/`getConfig_Succ`、`05_Func.js` 的 `getothername/getchannelName/getmarketname`、`version.js`、`00_SubGame_Config.js`),不要臆测。 + +--- + +## 16. API 速查表 + +| 符号 | 来自 | 签名 / 说明 | +| --- | --- | --- | +| `resolveRuntimeMode` | `config/runtime-mode.ts` | `(isDebugBuild, override?) => 'debug' | 'release'` 运行模式解析 | +| `resolveBootstrap` | `config/bootstrap.ts` | `(opts: BootstrapOptions) => Promise` 启动编排(入参含 mode,结果含 mode/isDebugger) | +| `BUILD_IDENTITY` | `config/sources/defaults.ts` | 构建期默认渠道身份常量 | +| `resolveIdentity` | `config/identity.ts` | `(sources: IdentitySource[]) => ChannelIdentity` 优先级合并 | +| `queryStringSource` | `config/sources/query-string.ts` | `(search: string) => IdentitySource` H5 URL 身份来源 | +| `nativeSettingsSource` | `config/sources/native-settings.ts` | `(win: NativeHost) => IdentitySource` 原生身份来源 | +| `PROFILES` / `ACTIVE_PROFILE` | `config/profiles.ts` | 档位表 / 当前默认档位名 | +| `resolveActiveProfile` | `config/profiles.ts` | `(search: string) => DebugProfile`,支持 `?profile=` | +| `DEFAULT_GAMESERVER` | `config/profiles.ts` | 正式远程配置 txt URL | +| `getParam` | `config/remote-config.ts` | `(config, key, id) => unknown` 分层取参 | +| `resolveServers` | `config/remote-config.ts` | `(config, id) => string[]` ws 候选地址 | +| `ConfigFetcher` | `config/remote-config.ts` | 抓取接口 `{ fetch(url): Promise }` | +| `HttpConfigFetcher` | `config/remote-config-fetcher.ts` | 生产用 `fetch` 实现 | +| `ConfigFetchError` / `ConfigParseError` | `config/remote-config.ts` | 抓取/解析错误类型 | + +--- + +*文档对应实现已合入 master;如接口有演进,请同步更新本指南。*