Files
youle_cocos/docs/superpowers/specs/2026-06-28-runtime-mode-design.md
T
joywayerandClaude Opus 4.8 51a4ee57ac refactor(framework): 移除 resolveBootstrap 下游兜底,立第二准则
- CLAUDE.md 新增「第二准则:数据源权威、唯一,下游不兜底」
- bootstrap 删除 fallbackServers 选项与 ?? DEFAULT_GAMESERVER 猜默认
- 无 server/gameserver、远程失败、解析不出地址 → 一律抛 ConfigFetchError 显式暴露
- 同步 guide + 两篇 spec(删 fallback/降级措辞)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 18:59:15 +08:00

161 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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;
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 收发包)。