From af059b40e9abc9b13034f8bbcfed3b20967878f1 Mon Sep 17 00:00:00 2001 From: Joywayer Date: Sat, 5 Sep 2026 23:23:04 +0800 Subject: [PATCH] docs: document unified startup settings and browser acceptance --- docs/guides/配置与调试指南.md | 564 +++--------------- .../2026-09-05-unified-startup-config.md | 2 +- ...09-05-unified-startup-config-acceptance.md | 43 ++ 3 files changed, 125 insertions(+), 484 deletions(-) create mode 100644 docs/superpowers/reports/2026-09-05-unified-startup-config-acceptance.md diff --git a/docs/guides/配置与调试指南.md b/docs/guides/配置与调试指南.md index cb10d90..d86da2c 100644 --- a/docs/guides/配置与调试指南.md +++ b/docs/guides/配置与调试指南.md @@ -1,517 +1,115 @@ # 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`。 - -> **一句话心智模型**:启动时调用 `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-红线哪些字符串绝不能改))。 +只在 `cocoscreator_projects/YouleNexus/assets/framework/config/profiles.ts` 的 `STARTUP_CONFIG` 修改应用启动选项: ```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) -} +export const STARTUP_CONFIG = { + mode: 'debug', + gameserver: 'https://tsgames.daoqi88.cn/config/update_jsonv2.txt', + useLocalServer: true, + localServers: ['ws://127.0.0.1:3088'], +} as const; ``` -### 各字段从哪来 +- `mode`:应用运行模式,值为 `debug` 或 `release`。 +- `gameserver`:远程配置 JSON 的 HTTP(S) 地址;不是 WebSocket 服务器地址。 +- `useLocalServer`:true 使用本地地址,false 使用远程 JSON 按身份解析出的服务器地址。 +- `localServers`:本地服务器候选列表,必须是有效回环 WebSocket 地址。 -| 字段 | 主来源 | 说明 | -| --- | --- | --- | -| `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=` 覆盖 | — | +运行模式和服务器选择独立;release 不会偷偷改写 useLocalServer。发布时在这里明确设置 `mode: 'release'`、`useLocalServer: false`,并核对 gameserver。Creator 构建面板的 Debug 选项控制引擎编译,不能代替这里的应用配置。 -### 合并优先级(低 → 高,后者覆盖前者) +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=…) | 身份来源 | 收发包日志 | -| --- | --- | --- | --- | --- | --- | -| **debug** | debug/预览构建自动进入;或 `MODE_OVERRIDE='debug'` | `?profile=` 可切(默认 prod) | **全部生效** | 原生或 URL(按 `isNative`) | 开(`isDebugger=true`) | -| **release** | release 构建自动进入;或 `MODE_OVERRIDE='release'` | **强制 prod** | **全部忽略** | **只走原生 `window.settings`** | 关 | +远程响应本身是根 JSON;原工程 `getConfig_Succ` 将它放进客户端 `GameData.serverConfig.data`,data 不是远程信封。当前 `RuntimeConfig.rawConfig` 与 `RuntimeConfig.remoteConfig.raw` 引用同一根对象;`remoteConfig.getValue(name)` 提供统一取值。只存内存,无远程文件落盘或 localStorage 快照。 -### 三种切换姿势 +层级规则沿用原 get_paravalue:全局 → agent → game → channel → market;每层第一个身份宽松相等的条目生效;只有 truthy 值覆盖,0/false/空串不覆盖;缺少匹配层即继承已取得的值;对象和数组整体替换,不自动深合并。 -1. **什么都不动**:发 release 包自动 release,编辑器预览自动 debug。模式由 Cocos 全局 `DEBUG` 决定。 -2. **release 包里临时调试**:把 `config/runtime-mode.ts` 的 `MODE_OVERRIDE` 设为 `'debug'`。 -3. **预览里验证正式行为**:把 `MODE_OVERRIDE` 设为 `'release'`。 +useLocalServer=false 时,选择 truthy `game_server_tcp`,否则 `player_server_tcp`,支持字符串和数组,保留顺序及重复项。不使用 `visitor_server_tcp` 兜底。最终候选在配置边界验证,禁止认证信息、非法协议和 URL 片段。 -> 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 -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 选项 +const config = await resolveRuntimeConfig({ + hostKind: 'h5', + win: window as unknown as NativeHost, + search: window.location.search, + fetcher: new HttpConfigFetcher(), +}); ``` ---- +不要在这个调用处再次写 mode 或服务器地址,也不要捕获配置错误后创建兜底连接。 -## 5. 调试 Profiles +## 自动化验证 -Profiles 取代了旧工程的 `Game_Config.Debugger`(`serverType` / `gameserver` / `isDebugger`)。一个 profile 打包了「连哪个服 + 用什么渠道身份 + 日志开关」。 +PowerShell 工作目录为 `cocoscreator_projects`: -```ts -interface DebugProfile { - name: string; - server?: string; // 显式 ws 调试服地址;给定则跳过远程抓取(纯直连) - gameserver?: string; // 远程配置 txt URL(不含防缓存串) - serverOverride?: string; // debug:抓远程配置走全链路后,把连接地址覆盖为此(联调本地游戏服) - identity?: Partial; // 调试用渠道身份覆盖 - account?: DebugAccount; // 调试用登录账号(openid 等),仅 debug 模式生效 - isDebugger?: boolean; // 收发包日志开关 -} - -// DebugAccount:player_login 中除渠道身份外的字段(openid 必填,其余可选) -// { openid, nickname?, avatar?, sex?, province?, city?, unionid? } -// 联调账号写在这里(profile 是唯一权威来源),不要散在调试脚本里。 +```powershell +$testPaths = @(rg --files framework-tests -g '*.test.ts' | Sort-Object) +node --import tsx --test --test-concurrency=1 @testPaths +node --test framework-tests/architecture/import-boundaries.test.mjs framework-tests/architecture/presentation-boundaries.test.mjs +node node_modules/typescript/bin/tsc -p tsconfig.framework.json --noEmit +node scripts/check-import-boundaries.mjs ``` -### 内置档位(`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` 顶部常量。 -- **临时切换**:URL 加 `?profile=`,例如 `?profile=local`、`?profile=staging`。未知名字会回退到 `ACTIVE_PROFILE`。 +2026-09-05 的真实配置对当前 BUILD 身份匹配 agent,但没有其下匹配 game,继承得到的 game/player TCP 均缺失。因此本地诊断可以在读取后使用明确本地地址;选择远程服务器的真实启动仍会显式报错。不要据此擅自选择子游戏、更换身份或使用游客服务器地址。 -```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` 远程配置文件,用 `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`。抓取时自动追加防缓存时间戳:`?`。 - -生产抓取实现 `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; -``` - -### 典型接入(生产 / 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 防缓存**:`?<时间戳>` 形式。 - -改这些之前先核对旧工程源码(`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;如接口有演进,请同步更新本指南。* +远程/原生/服务器契约仍以实际原工程及 docs/protocol 对应章节核对。此前指南中的 GET、远程 data 包装、URL profile 选择、直连跳过读取、版本字符串及默认身份兜底说明已失效,本版不再沿用。 diff --git a/docs/superpowers/plans/2026-09-05-unified-startup-config.md b/docs/superpowers/plans/2026-09-05-unified-startup-config.md index 494b360..2dc94ee 100644 --- a/docs/superpowers/plans/2026-09-05-unified-startup-config.md +++ b/docs/superpowers/plans/2026-09-05-unified-startup-config.md @@ -40,5 +40,5 @@ - [ ] 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. -- [ ] 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. diff --git a/docs/superpowers/reports/2026-09-05-unified-startup-config-acceptance.md b/docs/superpowers/reports/2026-09-05-unified-startup-config-acceptance.md new file mode 100644 index 0000000..640884f --- /dev/null +++ b/docs/superpowers/reports/2026-09-05-unified-startup-config-acceptance.md @@ -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 接线按下一批资源授权与原工程核对处理。