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

8.4 KiB
Raw Blame History

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)

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。

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 增可选日志开关:

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. 接入示例(更新到指南)

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 收发包)。