docs(spec): debug/release 一键运行模式设计(runtime-mode)

自动跟随 Cocos DEBUG + 可选手动覆盖;release 强制 prod 并完全锁死 URL 覆盖;isDebugger 接到 NetClient 收发包日志。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-28 17:51:21 +08:00
co-authored by Claude Opus 4.8
parent e4d16bf412
commit 6bfabffcfc
@@ -0,0 +1,161 @@
# debug/release 一键运行模式设计(framework/config runtime-mode)
> 状态:已与用户确认(brainstorming 通过),待转 writing-plans。
> 适用工程:`cocoscreator_projects/YouleNexus`。
> 关联:配置/渠道子系统 spec `2026-06-28-config-channel-design.md`(本设计在其 `resolveBootstrap` 上加运行模式)、`docs/guides/配置与调试指南.md`(需同步更新)。
## 0. 目标与背景
当前「调试 ↔ 正式」只能靠改 `ACTIVE_PROFILE` 常量或 URL `?profile=` 切换,且:
1. `DebugProfile.isDebugger` 字段**无人消费**(没有任何日志按它开关);
2. **没有 release 锁定**——即使正式包,URL `?profile=`/`?agentid=` 仍生效,可被篡改切到调试服或换渠道身份。
本设计引入一个**一键运行模式开关**,让整套行为随 debug/release 切换:
- **自动跟随 Cocos 构建类型**(debug/预览构建=debug,release 构建=release),并保留**可选手动覆盖常量**。
- **release 完全锁死 URL 覆盖**:强制走 `prod` profile,忽略所有 URL 参数。
- **release 安全前提**:正式包为 Cocos 原生形态(jsb),渠道身份走 `window.settings`/`app_*`,不依赖 URL,故锁 URL 不影响身份传递。
- 让 `isDebugger` 真正可观测:接到 `NetClient` 的收发包日志。
### 命名/红线
内部命名现代化(`RuntimeMode`/`resolveRuntimeMode`),不改任何外部契约字符串(协议字段、原生接口名、远程配置键)。详见 config-channel spec §5。
## 1. 开关机制(新增 `config/runtime-mode.ts`)
```ts
export type RuntimeMode = 'debug' | 'release';
/**
* 手动覆盖运行模式。
* null = 自动跟随构建类型(推荐,发布前保持 null)。
* 'debug' / 'release' = 强制该模式(用于在 release 包里临时调试,或在编辑器预览里验证正式行为)。
*/
export const MODE_OVERRIDE: RuntimeMode | null = null;
/**
* 纯函数解析运行模式:override 优先,否则由是否 debug 构建决定。
* isDebugBuild 由调用方读 Cocos 全局 DEBUG 传入(见 §4 接入)。
*/
export function resolveRuntimeMode(isDebugBuild: boolean, override: RuntimeMode | null = MODE_OVERRIDE): RuntimeMode {
if (override) return override;
return isDebugBuild ? 'debug' : 'release';
}
```
- `isDebugBuild` 来源:Cocos Creator 全局 `DEBUG`(构建期宏,release 构建为 false)。接入处用 `typeof DEBUG !== 'undefined' ? DEBUG : true`(编辑器/预览缺省视为 debug)。读全局这一薄层不写单测;纯函数 `resolveRuntimeMode` 全测。
- **一键切换三种姿势**:① 不动任何东西——发 release 包自动 release、预览自动 debug;② release 包里临时调试 → `MODE_OVERRIDE='debug'`;③ 预览里验证正式行为 → `MODE_OVERRIDE='release'`。
## 2. 模式如何重塑 resolveBootstrap
`BootstrapOptions` 新增必填 `mode: RuntimeMode`;`BootstrapResult` 新增 `mode: RuntimeMode` 与 `isDebugger: boolean`。
```ts
export interface BootstrapOptions {
mode: RuntimeMode; // 新增
win: NativeHost;
search: string;
fetcher: ConfigFetcher;
isNative: boolean;
fallbackServers?: string[];
cacheBust?: () => string;
}
export interface BootstrapResult {
mode: RuntimeMode; // 新增
isDebugger: boolean; // 新增 = (mode === 'debug')
identity: ChannelIdentity;
servers: string[];
rawConfig: RemoteConfig | null;
profile: DebugProfile;
}
```
行为分支:
- **release**:
- `profile = PROFILES.prod`(强制,**忽略 `opts.search` 的 `?profile=`**)。
- 身份来源仅 `[() => BUILD_IDENTITY, nativeSettingsSource(opts.win)]`——**不挂 `queryStringSource`,不应用 `profile.identity` 调试覆盖**。即任何 URL 参数无效。
- 服务器决策照旧(prod 无 `server` → 走远程配置)。
- **debug**:维持现状。
- `profile = resolveActiveProfile(opts.search)`。
- 身份来源 `[() => BUILD_IDENTITY, runtime(isNative?native:query), () => profile.identity ?? {}, queryStringSource(search)]`。
- `isDebugger = (mode === 'debug')`,并写入结果。(release 恒 false。`DebugProfile.isDebugger` 字段保留为档位元数据,但权威开关改由 mode 决定。)
> 设计要点:release 分支根本不构造 `queryStringSource`,从源头杜绝 URL 覆盖,而不是构造后再过滤——更不易出错。
## 3. 让 debug 模式可观测:NetClient 收发包日志
当前 `isDebugger` 无人消费。本期接上,给 `NetClient` 增可选日志开关:
```ts
export interface NetClientOptions {
servers: string | string[];
transportFactory: () => Transport;
clock?: Clock;
bus?: EventBus<NetClientEvents>;
debug?: boolean; // 新增:为真时 console 打印每帧 send/recv
logger?: (...args: unknown[]) => void; // 新增(可选):注入日志函数,默认 console.log,便于测试断言
}
```
- 发送(`send`/`sendLogin`)与接收(`onMessage` 入口)时,若 `debug` 为真,调用 `logger`(默认 `console.log`)打印方向 + 帧内容(如 `[net] → <frame>` / `[net] ← <frame>`)。
- 接入:`new NetClient({ servers, transportFactory, debug: result.isDebugger })`。release 静默,debug 输出。
- 日志只读不改流程(剥离 UI,纯 console),不影响协议行为。
## 4. 接入示例(更新到指南)
```ts
import { resolveRuntimeMode } from 'db://assets/framework/config/runtime-mode';
import { resolveBootstrap } from 'db://assets/framework/config/bootstrap';
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';
const isDebugBuild = typeof DEBUG !== 'undefined' ? DEBUG : true; // Cocos 全局
const mode = resolveRuntimeMode(isDebugBuild); // 'debug' | 'release'
const result = await resolveBootstrap({
mode,
win: globalThis as any,
search: location.search,
fetcher: new HttpConfigFetcher(),
isNative: location.href.indexOf('index.html') > -1,
});
const net = new NetClient({
servers: result.servers,
transportFactory: () => new CocosWebSocketTransport(),
debug: result.isDebugger,
});
net.setIdentity({ ...result.identity, openid, nickname, /* ...玩家档案 */ });
net.start();
```
## 5. 测试策略(tsx + node:test)
- `framework-tests/config/runtime-mode.test.ts`:
- override 为 'debug'/'release' 时优先返回 override(无视 isDebugBuild)。
- override 为 null 时:isDebugBuild=true → 'debug';false → 'release'。
- `framework-tests/config/bootstrap.test.ts`(扩充):
- **release**:`mode:'release'` + `search:'?profile=local&agentid=999'` → `profile.name==='prod'`、`servers` 走远程(非 local 直连)、`identity.agentid===BUILD_IDENTITY.agentid`(未被 999 覆盖)、`isDebugger===false`。
- **release 原生身份仍生效**:`mode:'release'`, `isNative:true`, fake `win.settings` → `identity.agentid` 来自原生。
- **debug**:`mode:'debug'` 维持原有覆盖行为(现有用例补传 `mode:'debug'`)。
- 结果含 `mode` 与 `isDebugger` 字段。
- `framework-tests/net/net-client.test.ts`(扩充):
- `debug:true` + 注入 fake `logger` → send 与 recv 各被记录一次(含方向标记)。
- `debug:false`(或不传)→ logger 不被调用。
验收:`npm run test:framework` 全绿、`npm run typecheck:framework` exit 0。
## 6. 影响面与迁移
- `resolveBootstrap` 新增**必填** `mode`——所有现有调用点(目前仅测试与未来启动场景)需补传。现有 bootstrap 测试用例补 `mode:'debug'` 保持原语义。
- `NetClient` 新增**可选** `debug`/`logger`——默认关闭,不影响现有调用与测试。
- `DebugProfile.isDebugger` 字段保留(档位元数据/可读性),但运行期权威开关改由 `mode` 决定;`prod.isDebugger:false` 与 release 自洽。
- 更新 `docs/guides/配置与调试指南.md`:新增「一键切换 debug/release」一节、`resolveRuntimeMode` 接入、发布前清单(确认 `MODE_OVERRIDE===null`)、API 速查表补项。
## 7. 范围边界(YAGNI)
- **做**:运行模式解析(自动+手动覆盖)、release URL 锁定、`isDebugger` 接到 NetClient 收发包日志、文档更新。
- **不做**:基于模式切换资源/分包;远程下发的 `isdebugger` 参数(原工程 `get_paravalue('isdebugger')`,属后续 platform 层);日志分级/上报(仅 console 收发包)。