Files
youle_cocos/docs/superpowers/plans/2026-09-05-local-platform-login.md
T

14 KiB
Raw Blame History

Local Platform Login Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 在主 checkout 的联调分支中完成启动、真实资源加载、游客登录与本地服务器响应展示的验收。 Architecture: 复用 Runtime/Controller/WireClient 与 local sources,开发目录内组合严格 Transport、范围包装、诊断 GameEntry 和 Cocos 端口。无新 Store/Router。 Tech Stack: TypeScript ES2020、node:test/tsx、Cocos Creator 3.8.8、funplay MCP。 Spec: docs/superpowers/specs/2026-09-05-local-platform-login-design.md(用户已确认全文及第8节九项资源授权)。

Global Constraints

  • 服务器、远程配置服务、settings/WVJB 契约零改动。默认 prod、BUILD_IDENTITY 及原生配置优先级保持不变。
  • 不修改生产 GameEntry 契约,不选择真实子游戏,不接其他业务 RPC,不替换现有 Login/Launch/MainMenu 场景,不复做 3A。
  • 代码新增只在 YouleNexus/assets/scripts/local-platform-login/,测试在 framework-tests/local-platform-login/;下文路径均相对 cocoscreator_projects/。
  • .scene/.prefab/.anim/.meta 只能由 Creator/MCP 生成或保存;写入/提交仅限 spec 第8节,其他导入副产物保留且报告。
  • 按用户最新授权,在 G:/Works/YouleGamesCocosCreator 使用 codex/local-platform-login;Creator projectPath 必须为该目录下 cocoscreator_projects/YouleNexus。原有50项改动按路径/状态/哈希保护,精确暂存,不自动 stash。
  • 显式枚举纯 TS 测试及两项指定 architecture 测试;禁止 npm test、run-framework-tests.mjs、test:framework。tsx ENOMEM 精确升级运行,不改业务代码。
  • TDD→任务规格/质量审查→修复复验→scoped commit;最后整分支审查,代码测试不能替代引擎/真实服务验收。

Task 1: 严格本地 WebSocket Transport

Files: Create YouleNexus/assets/scripts/local-platform-login/local-login-transport.ts; Test framework-tests/local-platform-login/transport.test.ts。 Interfaces: export LocalLoginEvidence = {kind:string; data:unknown}; export EvidenceSink=(event:LocalLoginEvidence)=>void。export LocalLoginTransport implements Transport;constructor options {context:LocalStartupContext; createSocket:(url:string)=>WebSocket; onFault:(error:unknown)=>void; record:EvidenceSink}。注入真实浏览器构造器仅在组件根完成。

  • 写 RED 测试,FakeSocket 实现标准 open/message/close/error 回调与 readyState/send/close。导入缺失模块必须是语义 RED。
assert.throws(() => transport.send('{}'), /open/i);
transport.connect(context.config.servers[0]!);
socket.readyState = 1; socket.onopen?.({} as Event);
transport.send('frame');
assert.deepEqual(socket.sent, ['frame']);
  • 实现每次 connect 前 assertLocalConnectionTarget;构造器/校验失败先 onFault 再抛出。close 显式停止后回调失效;回调捕获 socket 实例,只交付当前连接。
const socket = options.createSocket(url);
this.socket = socket;
socket.onmessage = event => {
  if (this.socket !== socket) return;
  if (typeof event.data !== 'string') { /* report fault and stop current transport */ }
  else { /* record raw frame, then deliver message callback */ }
};

以上注释分支必须实现:非文本构造 TypeError→关闭/失效→报告,文本记录成功后交付;record 抛错按显式故障清理,不吞掉。

  • RED/GREEN:远程 URL 不构造;构造错误 cause/原对象保留;OPEN 前 send 失败;非文本失败不交付;error/close 单次 close 通知;自然 close 允许 Wire 重连;显式 close 无重连回调;旧实例事件无效;关闭抛错可定位。入站握手在 codec 前被 record 捕获,发送证据只记录成功 send。
  • node --import tsx --test framework-tests/local-platform-login/transport.test.ts、node node_modules/typescript/bin/tsc -p tsconfig.framework.json --noEmit、node scripts/check-import-boundaries.mjs、git diff --check。审查后只提交 source/test,无 meta。

Task 2: 诊断 GameEntry 与 Wire 范围边界

Files: Create local-login-game-entry.ts、local-login-wire.ts 于上述脚本目录;Test framework-tests/local-platform-login/game-entry.test.ts、wire.test.ts。 Interfaces: createDiagnosticGameEntry(gameId:string|number,record:EvidenceSink):GameEntry;createLocalLoginWire({context,transportFactory,onFault,record}):PlatformWireClient,transportFactory:()=>Transport。只包装一个 new WireClient({servers:context.config.servers,transportFactory})。

  • RED:真实 new GameSessionHost(entry) 会创建/释放诊断对象;entry.resolveSeatCount([]) 必须抛错,module.attach/restore/events 均抛出操作名。dispose 状态转换记录一次,重复释放安全;key/route 为 local-platform-login,不是协议平台 route。
const host = new GameSessionHost(createDiagnosticGameEntry(context.config.identity.gameid, record));
assert.equal(host.state, 'idle');
assert.throws(() => entry.resolveSeatCount([]), /resolveSeatCount/);
  • RED/GREEN 工厂实现:模块实例编号递增,created→disposed,所有游戏行为 record violation 后 throw。不得返回座位数、空处理结果或真实子游戏模块。
  • Wire RED:真实 WireClient 配合 fake Transport 输入完整 login fixture;成功大厅原 event 引用交付;合法房间恢复在 Runtime 前终止;损坏响应保留解析错误;拒绝非 agent/player_login 出站且无 send。
const parsed = parseLoginResponse(event.message.data);
if (parsed.state === 0 && parsed.room !== null) failScope('login room recovery');
  • 实现 subscribe 返回注销函数,单个内部订阅转发;所有切服、room/子游戏 message 终止;agent/platform 普通推送原样交付。kick/非零登录回包先交付现有处理链,再通知宿主停止本次验收,以保留原始失败显示。故障单次锁存,先释放底层订阅与 Wire,后通知宿主,清理错误不能丢弃。实现 stop 幂等;生命周期 start/stop 不假装已经连接。
  • node --import tsx --test framework-tests/local-platform-login/game-entry.test.ts framework-tests/local-platform-login/wire.test.ts,覆盖范围禁止后零继续交付/零重连、正常重连同一信封。类型/导入/diff 检查,审查与 scoped commit。

Task 3: 纯 TS 宿主组合与启动操作

Files: Create local-login-host.ts;Test framework-tests/local-platform-login/host.test.ts。 Interfaces: createLocalLoginHost(options): {start():Promise;login():Promise;stop():void;getSnapshot():{state:PlatformState|null;ready:boolean;busy:boolean}}。 options: configOptions:ResolveRuntimeConfigOptions、storage:LocalStoragePort、random:()=>number、now:()=>number、sampleHost:()=>LocalHostSnapshot、view:PlatformViewPort、scheduler:FrameScheduler、loadResources:()=>Promise、waitForMinimumDisplay:()=>Promise、createSocket:(url:string)=>WebSocket、record:EvidenceSink、onBusy:(busy:boolean)=>void、onFailure:(error:unknown)=>void;所有依赖必需。

  • 写真实 Runtime+Controller 联合 RED:控制 resources/minimum/socket 三个未完成输入,任何一个未就绪禁止 login;configOptions fetcher 计数为零,配置解析仅一次(从 record/身份解析输入探针确认)。首个 config 解析在 Runtime 构造前完成以提供诊断 gameId,场景 shell 显示此阶段。
  • 构造一次 local startup Promise,context→entry、sources、wire 工厂。Controller.connect(runtime)→runtime.start();Runtime 选项 resolveRuntimeConfig 返回已解析同一 config。
const context = await resolveLocalStartup(options.configOptions);
const entry = createDiagnosticGameEntry(context.config.identity.gameid, options.record);
// createLocalLoginSources uses live closure for active login state.
// Runtime creation and Controller.connect occur only once after context validation.
  • login 合并忙碌调用,prepare 成功才 controller.login;响应前保持 busy,onBusy 与 View 的登录遮罩一致。销毁期间异步 prepare/config 完成不能创建连接或发送;通过 generation/terminal 标记拒绝迟到结果,不新增业务状态机。失败先禁用输入再停止各所有者,onFailure 最后呈现错误;stop 保留已经绘制的成功/失败结果。
  • RED/GREEN:正常 player_login fixture→Store logged-in/outside→lobby model;state非零/kick 原错误;房间恢复无写入 Store;重复点击单 send;缓存损坏无 send;stop during prepare/config/resources 无后续渲染/连接;observer/清理错误保留;invalid query/native 零账号写入与连接。
  • node --import tsx --test framework-tests/local-platform-login/host.test.ts、类型/导入/diff 检查、任务审查提交。此步不真实联网,不实现空资源端口的生产入口。

Task 4: Cocos View、组件与授权场景

Files: Create local-login-view.ts、LocalPlatformLogin.ts;Test framework-tests/local-platform-login/view.test.ts(纯投影/绑定端口行为,可控 cc 模块测试注入只在测试环境);Create spec 第8节九个资源文件,由 MCP/Creator 完成。 Interfaces: CocosLocalLoginView implements PlatformViewPort,持有明确 roots/status/result Labels;createCocosFrameScheduler():FrameScheduler。View 提供 bindLogin(instance:Node,onLogin:()=>void)、setBusy(boolean)、showFailure(unknown)、dispose();所有必需节点缺失 throw。组件持有 Loading/Login Prefab、页面 roots、status/result Labels、StopButton 引用。

  • RED 先覆盖 view 的失败绑定、正确 PageModel→Label 显示、遮罩忙碌/终止、解绑行为;使用可控引擎端口而非在消费代码填默认 Node。必要的测试模块加载器只能位于 framework-tests,不能伪装 cc 生产实现。
  • 实现触摸绑定 group-2/游客登录;隐藏微信/手机/QQ/快速/隐藏快速入口;版本 Label 取有效 identity.version。加载节点 group-40/载入动画 用 Cocos update/tween 旋转,生命周期清理。不得持久改源 prefab。
  • 组件 DEBUG 且 sys.isNative=false 限制;500ms 仅一处定义。start 调用宿主,configOptions 使用实际 location.search 与 window,禁止 query 默认;Storage 绑定 localStorage,random/now 用 Math.random/Date.now,sampleHost 显式 location=null 及存在时的 returnCitySN。
  • loadResources 检查实际序列化 Prefab 与 SpriteFrame 依赖,实例化 Login、绑定节点与事件,等待真实 draw;minimum-display 自 Loading 首个 draw 计时剩余500ms,不能以 setTimeout 代替资源加载。帧回调使用 Cocos director 事件与真实取消。
  • 编辑器操作前用 MCP 核实固定主工程 projectPath。用户已取消 worktree 切换;在当前联调分支继续,不请求打开已清理的隔离路径,不保存未知场景。
  • MCP 创建空 LocalPlatformLogin.scene,1600×720 Canvas/Camera/Host/roots/status/result/stop,实例化 Loading Prefab,挂新组件及序列化引用;Login Prefab 由运行实例加载。保存→重新打开→核实组件与引用真实持久,检查九个资源范围,不使用 edit_prefab_json。
  • Cocos noEmit/诊断需使用实际引擎声明;不能把 cc 文件纳入无引擎 framework tsconfig。记录 engine diagnostics 与纯 TS 各自结果。MCP 预览新的场景,显式 profile=local;手工模拟点击前确认无旧 LoginFlow 活跃。
  • 任务规格/质量审查覆盖代码及 MCP 绑定证据,再提交授权文件;若工具不能持久化挂载,报告具体阻塞而非手改序列化。

Task 5: 真实服务验收与整批回归

Files: Create docs/superpowers/reports/2026-09-05-local-platform-login-acceptance.md;Modify 本计划完成记录;失败时修复只能通过同任务 TDD 子代理与审查,且不得越资源授权。 Interfaces: 只消费已实现组件、只读证据与 MCP;不新增 RPC 或服务器接口。

  • 核实本地 gateway 进程与路由对应服务;只读定位版本/身份配置,脱敏记录来源和未证实项。不能依据历史 version=10000/41 改请求。
  • MCP 预览明确 profile=local,观察 Loading 实际旋转与≥500ms、ready四门、游客可操作;从 Transport 证据确认唯一 WS、本地 URL、握手,点击后唯一 agent/player_login。
  • 记录成功回包 state=0、Store logged-in/outside、PageModel 与结果页一致。若拒绝/无回包/房间恢复,记录事实并定位来源,不能宣告通过或去测其他 RPC。再独立预览同 origin 验证账号/机器缓存复用与登录成功;停止后无重连/监听。
  • 执行以下精确回归,所有 exit 非零即停止结果认定:
$loginTests = @(rg --files framework-tests -g '*.test.ts')
if ($loginTests.Count -eq 0) { throw 'No TypeScript tests selected' }
node --import tsx --test --test-concurrency=1 @loginTests
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
git diff --check
  • 审计基线到 HEAD 和所有未提交资源路径,主 checkout原有50项状态与哈希不变;本批真实成功与纯测试成功分开记录。报告仅提交脱敏摘要与必要截图引用,认证密钥和原始账号设备标识不提交。
  • 任务规格/质量审查、完整分支审查、完成记录;不自动合并或推送。

计划自审与执行

spec 1–6 对应 Tasks1–3;spec7–9 对应Task4;spec10–11对应Task5。诊断生命周期先创建再释放,不把工厂写成直接throw;最短展示从真实draw开始;View不能直接读原包。代码接口按上文导出,任务之间按依赖串行;测试存根只在测试目录。 用户已指定本会话 subagent-driven-development,无需再次选择执行方式。第8节资源授权已确认;额外资源不得暗中增加。