docs: document unified startup settings and browser acceptance

This commit is contained in:
2026-09-05 23:23:04 +08:00
parent facceeae9b
commit af059b40e9
3 changed files with 125 additions and 484 deletions
+81 -483
View File
@@ -1,517 +1,115 @@
# YouleNexus 配置与调试指南 # YouleNexus 配置与调试指南
本指南面向在新 Cocos 前端 `YouleNexus` 上做开发联调的工程师,讲清楚两件事: 本指南对应单文件启动配置设计:`docs/superpowers/specs/2026-09-05-unified-startup-config-design.md`。历史计划和验收报告记录当时行为,不作为当前配置步骤。
1. **配置**——渠道身份(agentid/channelid/gameid/marketid/version)从哪来、远程配置文件怎么读、服务器地址怎么定。 ## 修改启动配置
2. **调试**——如何一键切到本地/测试服、临时替换渠道身份、跑自动化测试、做真机端到端联调。
对应实现位于 `cocoscreator_projects/YouleNexus/assets/framework/config/`,设计依据见 `docs/superpowers/specs/2026-06-28-config-channel-design.md`。 只在 `cocoscreator_projects/YouleNexus/assets/framework/config/profiles.ts` 的 `STARTUP_CONFIG` 修改应用启动选项:
> **一句话心智模型**:启动时调用 `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 ```ts
interface ChannelIdentity { export const STARTUP_CONFIG = {
agentid: string | number; // 代理商 ID mode: 'debug',
channelid: string | number; // 渠道 ID gameserver: 'https://tsgames.daoqi88.cn/config/update_jsonv2.txt',
gameid: string; // 游戏标识(每个子游戏固定) useLocalServer: true,
marketid: string | number; // 市场/包渠道 ID localServers: ['ws://127.0.0.1:3088'],
version: string; // 版本号(旧 GameData.Version) } as const;
versionCode: number; // 版本码(旧 GameData.versionCode)
}
``` ```
### 各字段从哪来 - `mode`:应用运行模式,值为 `debug` 或 `release`。
- `gameserver`:远程配置 JSON 的 HTTP(S) 地址;不是 WebSocket 服务器地址。
- `useLocalServer`:true 使用本地地址,false 使用远程 JSON 按身份解析出的服务器地址。
- `localServers`:本地服务器候选列表,必须是有效回环 WebSocket 地址。
| 字段 | 主来源 | 说明 | 运行模式和服务器选择独立;release 不会偷偷改写 useLocalServer。发布时在这里明确设置 `mode: 'release'`、`useLocalServer: false`,并核对 gameserver。Creator 构建面板的 Debug 选项控制引擎编译,不能代替这里的应用配置。
| --- | --- | --- |
| `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=` 覆盖 | — |
### 合并优先级(低 → 高,后者覆盖前者) URL 的 mode/profile 参数不再参与启动选择,也不再要求它们存在。runtime-mode.ts 不自动读取 URL 或全局 DEBUG。生产调用方不再另传 mode、硬编码服务器或复制上述配置;测试可以整体注入 StartupConfig,以覆盖不同组合。
``` ## 网页本地测试
BUILD_IDENTITY(构建期默认)
< 运行时来源(原生环境=nativeSettings | H5 环境=queryString)
< profile.identity(当前调试档位的身份覆盖)
< 显式 URL query(最高,方便临时改单字段)
```
`resolveIdentity(sources)` 按数组顺序合并,**只覆盖"有效值"字段**——`undefined`/`null`/`空字符串` 不覆盖(所以一个来源读不到某字段时,不会把已有值清空);注意 **`0` 是有效值**会覆盖(如 `marketid=0`)。 1. 确认 STARTUP_CONFIG 为 debug、useLocalServer=true,且本地服务运行在配置的地址。
2. Creator 打开主 checkout 的 YouleNexus 工程和 `assets/scenes/LocalPlatformLogin.scene`。
3. 点击网页预览,使用编辑器生成的原始地址,例如 `http://127.0.0.1:7456/`。不需要追加查询参数,端口以实际预览为准。
4. 观察远程配置读取、资源加载、本地连接完成后进入可登录页面。需要验收登录时再点击游客登录。
> H5 模式下 `queryString` 同时担任「运行时来源」和「最高覆盖」两个角色,因此在 URL 里写 `?agentid=...` 永远生效且优先级最高。 LocalPlatformLogin 是 DEBUG 浏览器下的本地诊断场景,要求应用配置 debug/useLocalServer=true,保留回环目标和生命周期保护。它不是正式生产入口;旧 Login.scene 也尚未接入新 Runtime,不用它替代验收。编辑器资源操作遵循仓库 AGENTS.md,通过 funplay MCP;未经授权不改序列化资源。
--- ## 配置读取流程
## 4. 一键切换 debug / release 模式 统一配置 → 宿主身份 → 远程地址注入解析 → HTTP 读取 → JSON 根与身份层级解析 → 选择最终服务器 → Runtime 启动。
运行模式是比 profile 更高一层的总开关,由 `config/runtime-mode.ts` 提供。 无论 debug/release,也无论是否使用本地服务器,都必须先读取远程配置。`HttpConfigFetcher` 忠实使用 POST 空 body,URL 无条件拼接 `?` 和时间戳。HTTP/JSON/层级错误明确抛出,不使用旧缓存或连接兜底。
| 模式 | 怎么进入 | profile | URL 覆盖(?profile=/?agentid=…) | 身份来源 | 收发包日志 | 远程响应本身是根 JSON;原工程 `getConfig_Succ` 将它放进客户端 `GameData.serverConfig.data`,data 不是远程信封。当前 `RuntimeConfig.rawConfig` 与 `RuntimeConfig.remoteConfig.raw` 引用同一根对象;`remoteConfig.getValue(name)` 提供统一取值。只存内存,无远程文件落盘或 localStorage 快照。
| --- | --- | --- | --- | --- | --- |
| **debug** | debug/预览构建自动进入;或 `MODE_OVERRIDE='debug'` | `?profile=` 可切(默认 prod) | **全部生效** | 原生或 URL(按 `isNative`) | 开(`isDebugger=true`) |
| **release** | release 构建自动进入;或 `MODE_OVERRIDE='release'` | **强制 prod** | **全部忽略** | **只走原生 `window.settings`** | 关 |
### 三种切换姿势 层级规则沿用原 get_paravalue:全局 → agent → game → channel → market;每层第一个身份宽松相等的条目生效;只有 truthy 值覆盖,0/false/空串不覆盖;缺少匹配层即继承已取得的值;对象和数组整体替换,不自动深合并。
1. **什么都不动**:发 release 包自动 release,编辑器预览自动 debug。模式由 Cocos 全局 `DEBUG` 决定。 useLocalServer=false 时,选择 truthy `game_server_tcp`,否则 `player_server_tcp`,支持字符串和数组,保留顺序及重复项。不使用 `visitor_server_tcp` 兜底。最终候选在配置边界验证,禁止认证信息、非法协议和 URL 片段。
2. **release 包里临时调试**:把 `config/runtime-mode.ts` 的 `MODE_OVERRIDE` 设为 `'debug'`。
3. **预览里验证正式行为**:把 `MODE_OVERRIDE` 设为 `'release'`。
> release 之所以能放心锁死 URL:正式包是 Cocos 原生(jsb),渠道身份走 `window.settings`/`app_*`,本就不依赖 URL。 useLocalServer=true 时,在远程根和层级解析完成后使用 localServers;未消费的远程 game/player TCP 可以缺失。该逻辑是显式来源选择,不是读取错误后的退路。
### 接入 公告、大厅配置、登录图片等字段由相同 getValue 读取。界面显示转换和原工程 UI 副作用在各自接线批次实现。
## 身份、账号与设备
身份唯一构建来源仍是 `config/sources/defaults.ts` 的 BUILD_IDENTITY,不在 profiles.ts 再复制一份。协议字段为 agentid、channelid、gameid、marketid、version;version 是 number,不是显示版本字符串。
- H5 使用 BUILD_IDENTITY,再应用 query-string.ts 支持的 agentid/channelid/marketid/version 覆盖;gameid 不从 URL 覆盖。
- native-settings 使用构建 gameid/version 与原生提供的 agent/channel/market;不完整身份明确失败,不用 H5 身份填补。
- uAgent_3 使用 app_agent/app_channel/app_market 等原生全局,按显式 hostKind 选择,不从浏览器 URL 猜测宿主。
原生接口名和源适配器中已定义的缺省语义保持原工程契约,不在下游重复实现。
本地游客账号由 adapters/local/visitor-account.ts 读取其已有作用域缓存或生成。openid 是 testopenid_ 加随机数,unionid 是 ylgame 加同一随机数,同时产生昵称、头像、性别和地区。设备字段由 login-sources.ts 等来源提供;UI 和协议消费方不补默认值。账号/机器标识的已有存储与“远程配置只存内存”是不同职责。
不运行历史 scripts/login-live.ts,不使用其占位账号或 staging 路径作为本次验证入口。
## 原生远程地址注入
保留原工程 gameconfig 契约:非空注入字符串按 `-`→`/`、`#`→`:` 解码,再形成 `http://…txt`。native-settings 读取 getothername('gameconfig'),uAgent_3 读取 app_gameconfig,普通 H5 的对应参数在统一配置解析边界处理。未注入时只使用 STARTUP_CONFIG.gameserver。
这是宿主契约的集中解析,不是另一份生产配置常量。本地诊断入口仍拒绝非空 gameconfig 和 gameid 覆盖,避免误用该诊断环境;普通运行配置解析仍保留注入兼容。
## 代码入口
- profiles.ts:唯一可编辑 STARTUP_CONFIG 及配置类型/验证,保留远程地址注入解码。
- runtime-mode.ts:运行模式类型及纯读取逻辑,不拥有另一套默认配置。
- runtime-config.ts:最终身份、服务器和内存远程配置的统一解析入口。
- remote-config.ts:根/层级验证、getValue 与远程服务器候选解析。
- remote-config-fetcher.ts:真实 HTTP 读取。
- local-startup.ts:本地诊断边界验证,使用解析结果携带的配置来源。
- bootstrap.ts:旧调用方的兼容外观,委托同一个 runtime-config;新流程直接使用 Runtime。
普通网页配置解析示例(由启动宿主继续接入 Runtime):
```ts ```ts
import { resolveRuntimeMode } from 'db://assets/framework/config/runtime-mode'; const config = await resolveRuntimeConfig({
hostKind: 'h5',
const isDebugBuild = typeof DEBUG !== 'undefined' ? DEBUG : true; // Cocos 全局 win: window as unknown as NativeHost,
const mode = resolveRuntimeMode(isDebugBuild); // 'debug' | 'release' search: window.location.search,
// 传给 resolveBootstrap({ mode, ... }),并把 result.isDebugger 传给 NetClient 的 debug 选项 fetcher: new HttpConfigFetcher(),
});
``` ```
--- 不要在这个调用处再次写 mode 或服务器地址,也不要捕获配置错误后创建兜底连接。
## 5. 调试 Profiles ## 自动化验证
Profiles 取代了旧工程的 `Game_Config.Debugger`(`serverType` / `gameserver` / `isDebugger`)。一个 profile 打包了「连哪个服 + 用什么渠道身份 + 日志开关」。 PowerShell 工作目录为 `cocoscreator_projects`:
```ts ```powershell
interface DebugProfile { $testPaths = @(rg --files framework-tests -g '*.test.ts' | Sort-Object)
name: string; node --import tsx --test --test-concurrency=1 @testPaths
server?: string; // 显式 ws 调试服地址;给定则跳过远程抓取(纯直连) node --test framework-tests/architecture/import-boundaries.test.mjs framework-tests/architecture/presentation-boundaries.test.mjs
gameserver?: string; // 远程配置 txt URL(不含防缓存串) node node_modules/typescript/bin/tsc -p tsconfig.framework.json --noEmit
serverOverride?: string; // debug:抓远程配置走全链路后,把连接地址覆盖为此(联调本地游戏服) node scripts/check-import-boundaries.mjs
identity?: Partial<ChannelIdentity>; // 调试用渠道身份覆盖
account?: DebugAccount; // 调试用登录账号(openid 等),仅 debug 模式生效
isDebugger?: boolean; // 收发包日志开关
}
// DebugAccount:player_login 中除渠道身份外的字段(openid 必填,其余可选)
// { openid, nickname?, avatar?, sex?, province?, city?, unionid? }
// 联调账号写在这里(profile 是唯一权威来源),不要散在调试脚本里。
``` ```
### 内置档位(`config/profiles.ts`) 禁止运行 npm test、test:framework 或 scripts/run-framework-tests.mjs;历史聚合入口含资源生成器。测试在 assets 外,不能用无引擎测试通过替代真实 Creator 界面验收。遇到已知 tsx 沙箱 uv_os_get_passwd ENOMEM,在获准环境执行同一精确命令,不改业务代码绕过。
| 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` | 本地服务器直连,**不抓远程**。 |
### 如何切换 发布前修改 STARTUP_CONFIG 中的应用模式和服务器选择,并核对远程配置是否为目标身份提供有效 game/player TCP。游戏身份、原生接口和账号来源按各自权威配置验证,不复制临时值到消费端。
- **默认档位**:`ACTIVE_PROFILE`(当前为 `'prod'`),在 `config/profiles.ts` 顶部常量。 2026-09-05 的真实配置对当前 BUILD 身份匹配 agent,但没有其下匹配 game,继承得到的 game/player TCP 均缺失。因此本地诊断可以在读取后使用明确本地地址;选择远程服务器的真实启动仍会显式报错。不要据此擅自选择子游戏、更换身份或使用游客服务器地址。
- **临时切换**:URL 加 `?profile=<name>`,例如 `?profile=local`、`?profile=staging`。未知名字会回退到 `ACTIVE_PROFILE`。
```ts 远程/原生/服务器契约仍以实际原工程及 docs/protocol 对应章节核对。此前指南中的 GET、远程 data 包装、URL profile 选择、直连跳过读取、版本字符串及默认身份兜底说明已失效,本版不再沿用。
resolveActiveProfile('?profile=local') // → PROFILES.local
resolveActiveProfile('') // → PROFILES.prod(ACTIVE_PROFILE)
resolveActiveProfile('?profile=xxx') // → PROFILES.prod(未知回退)
```
### 加一个自己的档位
直接在 `PROFILES` 里加一项即可,例如连组内某台联调机:
```ts
export const PROFILES: Record<string, DebugProfile> = {
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` 远程配置文件,用 `getParam` 取出连接参数,`resolveServers` 组装候选列表。
- **debug 且 profile 有 `serverOverride`**:仍抓远程配置走完整链路(`rawConfig` 照常返回),但最终连接地址**覆盖为 `serverOverride`**(联调时把游戏服连到本地测试服)。release 一律忽略 `serverOverride`,用远程解析结果。
3. **不兜底**(CLAUDE.md 第二准则):profile 既无 `server` 又无 `gameserver`、远程抓取失败、或解析不出任何地址——一律抛 `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`。抓取时自动追加防缓存时间戳:`<gameserver>?<timestamp>`。
生产抓取实现 `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(由调用方算)
cacheBust?: () => string; // 防缓存串注入(默认 String(Date.now()))
}
interface BootstrapResult {
identity: ChannelIdentity;
account?: DebugAccount; // 调试登录账号(仅 debug 模式有值;release 恒 undefined)
servers: string[];
rawConfig: RemoteConfig | null; // 直连档位为 null
profile: DebugProfile;
mode: 'debug' | 'release'; // 透传入参 mode
isDebugger: boolean; // 是否开启收发包日志(= mode === 'debug')
}
function resolveBootstrap(opts: BootstrapOptions): Promise<BootstrapResult>;
```
### 典型接入(生产 / 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() 反向
});
const net = new NetClient({
servers: result.servers,
transportFactory: () => new CocosWebSocketTransport(),
debug: result.isDebugger,
});
// 渠道身份 + 玩家档案合并成完整 player_login 数据
// debug:玩家档案来自 result.account(profile 里配的调试账号,单一权威来源)
// release:result.account 为 undefined,玩家档案由 platform 层/原生提供(后续 Plan)
net.setIdentity({
...result.identity, // agentid/channelid/gameid/marketid/version...
...(result.account ?? {}), // openid/nickname/avatar/... 调试账号或平台层登录信息
});
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. 想绕开远程配置服直连某地址
加一个带 `server` 的 profile(同 D 的 `lab` 例子)用 `?profile=lab` 直连——这是把"应急地址"写进**权威来源**(profile),而不是让 `resolveBootstrap` 兜底。远程配置服真的挂了,就是显式 `ConfigFetchError`,去修配置服或临时切到直连 profile,不在代码里降级(CLAUDE.md 第二准则)。
---
## 13. 真实服务器端到端联调
> 对应 Plan 2 的 Task 12 Step 4-5(自动化 mock 已全绿,真机联调需要外部输入)。
**前置**:① 可联调的服务器地址(`ws://ip:port` 或配置服 `gameserver` URL);② 一组后端认可的有效渠道身份(`agentid/channelid/marketid` + 目标子游戏的 `gameid`)。
**步骤**:
1. 用 `funplay-cocos-mcp` 的 `execute_javascript`(context=editor)或 `execute_editor_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. 用 `search_project_logs` / `capture_preview_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 防缓存**:`<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<BootstrapResult>` 启动编排(入参含 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<RemoteConfig> }` |
| `HttpConfigFetcher` | `config/remote-config-fetcher.ts` | 生产用 `fetch` 实现 |
| `ConfigFetchError` / `ConfigParseError` | `config/remote-config.ts` | 抓取/解析错误类型 |
---
*文档对应实现已合入 master;如接口有演进,请同步更新本指南。*
@@ -40,5 +40,5 @@
- [ ] MCP verify actual main checkout Creator project and current dirty state; refresh only modified existing TS resources. - [ ] MCP verify actual main checkout Creator project and current dirty state; refresh only modified existing TS resources.
- [ ] Open actual LocalPlatformLogin preview without query parameters, verify remote config source, local target, login ready and no failure; stop afterward. Configuration-only browser calls may verify the other source selections without creating production sockets. - [ ] Open actual LocalPlatformLogin preview without query parameters, verify remote config source, local target, login ready and no failure; stop afterward. Configuration-only browser calls may verify the other source selections without creating production sockets.
- [ ] Write docs/superpowers/reports/2026-09-05-unified-startup-config-acceptance.md with exact results, final current configuration location, publishing instructions and source limitation. Keep prior completed reports as history. - [ ] Update docs/guides/配置与调试指南.md to remove obsolete URL profile/remote-bypass/aggregate-test instructions. Write docs/superpowers/reports/2026-09-05-unified-startup-config-acceptance.md with exact results, final current configuration location, publishing instructions and source limitation. Keep prior completed reports as history.
- [ ] Whole-change independent review; verify original dirty hashes/status unchanged; mark plan progress and commit exact documents. No merge/push. - [ ] Whole-change independent review; verify original dirty hashes/status unchanged; mark plan progress and commit exact documents. No merge/push.
@@ -0,0 +1,43 @@
# 单文件启动配置验收
日期:2026-09-05。分支 codex/local-platform-login。设计/计划提交239af0a,实现提交facceea。独立审查正在进行。
## 范围
用户批准在 profiles.ts 单一 STARTUP_CONFIG 集中配置 mode、gameserver、useLocalServer、localServers。当前 debug/true;发布时在同一文件明确配置 release/false。URL mode/profile 不再选择模式或服务器,无参数网页可测试。本批不变更账号/身份协议、服务器或任何序列化资源,不扩展 RPC/UI 接线。
## 编辑器预检
- funplay MCP `fp_5838693755a937db`:主 checkout 的 `G:/Works/YouleGamesCocosCreator/cocoscreator_projects/YouleNexus`,场景 dirty=false。上批预览窗口已关闭。
- `fp_da61681a45eaf2e1`:当前 LocalPlatformLogin 场景 UUID67e69aac-9982-4e78-a44c-1d9c42f016ae,Canvas/Host 挂 LocalPlatformLogin。未写入场景或组件。
## 实际浏览器
通过 MCP 逐一刷新七个已有 TS 文件后,run_project_preview 返回无参数 `http://localhost:7456/`(`fp_b0584ce226aea86b`)。另由 MCP 创建独立 Chromium 验证窗口,Node integration 关闭,context isolation/sandbox 开启,加载同一原始 URL(`fp_a5517175ea6d4b16`)。
- `fp_328c5953fca91a56`:真实 location.search 为空;startup-config 的 mode=debug、source=remote,唯一服务器为 ws://127.0.0.1:3088。loading-first-draw=1574.9ms,配置完成1768.8ms,资源完成1773.3ms,最短展示门2075.5ms,login 页面2077.8ms激活;ready=true,无错误提示。未点击游客登录或测试其他 RPC。
- `fp_ec454dd740d8ce07`:通过 Creator import map 导入实际编译配置模块,保留真实 fetch,在冲突且重复的 mode/profile query 下读取。请求一次,HTTP200;结果仍为代码配置 debug/useLocalServer=true,配置快照内容等于 STARTUP_CONFIG,本地目标不变。rawConfig===remoteConfig.raw,前后 localStorage 内容不变。
- `fp_7d52b3adb2c0ea9a`:独立验证窗口停止后 stopped=true、ready=false、pendingCount=0,然后关闭该窗口。编辑器启动的普通网页预览供用户自行测试。
## 工作区与使用指南
原有50个未跟踪文件 SHA256 无变化,两项历史 meta 删除仍保留;本批未修改任何序列化资源。继续使用同一主 checkout 分支,不创建 worktree。
同步更新 docs/guides/配置与调试指南.md:当前启动设置、无参数测试、原生注入、内存解析、发布修改和安全测试命令。旧指南中 URL profile、绕过远程读取和历史接口示例不再作为操作步骤。
## 自动回归
- TDD 首轮聚焦 RED:28项,4通过/24失败,包括无参数被拒绝、模式错误、旧profile影响目标及缺少新配置入口。
- 最终聚焦62/62;全量显式枚举纯TS 618/618,无跳过或取消;指定架构测试49/49;框架类型与导入边界检查通过。全量包含既有负面路径的预期观察者异常日志。
- 测试覆盖四种 mode/useLocalServer 组合、冲突与重复URL参数、无参数宿主、非法配置、原生gameconfig、读取失败、停止期间不连接,以及内存原始数据引用。没有执行聚合资源生成测试。
- 七个生产TS文件在通知浏览器验证前已稳定;之后只补充测试,实际浏览器证据对应facceea的生产代码。
## 当前使用方式和限制
设置集中在 profiles.ts 的 STARTUP_CONFIG:mode=debug、useLocalServer=true;gameserver 与 localServers 同处定义。用户可直接打开 LocalPlatformLogin 并网页预览,不加mode/profile参数。
发布时在同一配置文件明确设置 release/false,同时选择Creator适当构建选项;LocalPlatformLogin仍是本地诊断场景,不是生产UI入口。当前真实远程配置对BUILD身份缺少game/player TCP,选择远程地址时仍会显式失败;不改身份、不用游客服务器或本地地址兜底。
独立Task1规格/质量审查覆盖239af0a..facceea,通过且无可操作问题;整体审查随后记录。
用户追加纠正:原工程启动资源加载界面应为 Layer1_Logo,当前诊断场景仍使用 Layer614_Loading。上述无参数启动证据只验收配置来源和就绪流程,不代表启动界面已忠实复刻。Logo 接线按下一批资源授权与原工程核对处理。