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

124 lines
14 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.
# 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。
```ts
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 实例,只交付当前连接。
```ts
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。
```ts
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。
```ts
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<void>;login():Promise<void>;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<void>、waitForMinimumDisplay:()=>Promise<void>、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。
```ts
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 非零即停止结果认定:
```powershell
$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节资源授权已确认;额外资源不得暗中增加。