Commit Graph
100 Commits
Author SHA1 Message Date
joywayerandClaude Opus 5 5e0546f675 feat(scripts): BackHall_Layer playbook experiment + .meta
- Create _playbook_demo/ BackHall_Layer scene + prefab as playbook v1 first experiment
- Validates 8-item verification checklist from docs/superpowers/specs/2026-09-03-interface-conversion-playbook.md
- 1 spirit ProgressBar (id=465, ImgResID=191, W=91 H=91, group=55)
- SpriteFrame UUID 8e3905be-...@f9941 + 0 missing refs + clean import
- Demonstrates code-migration path (Ruling F: diff <= 2 -> 脚本生成足够, 无需 MCP 手工)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 21:41:39 +08:00
joywayerandClaude Opus 5 0024cd6d18 docs(spec): interface conversion playbook (代码迁移 vs MCP 手工决策树 + 8 项验证清单 + 已知限制)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 21:39:09 +08:00
joywayerandClaude Opus 5 0ffb03979f chore(scripts): RoomSceneStart + room-scene-layout meta files (Cocos auto-generated)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 21:24:14 +08:00
joywayerandClaude Code ec550ba257 fix(scripts): RoomSceneStart setPlayerData bindTo contract + runtime validation
Critical fix:
- setPlayerData fake state lacked subscribe() -> PlayerInfoView.bindTo threw
  TypeError at runtime. Switched to PlayerInfoView.applyPlayer({nickname,
  bean,avatar}) when available; falls back to bindTo + proper reactive
  store with subscribe/value if only bindTo is exposed.

Runtime validation (per CLAUDE.md 第二准则 — no silent fallback):
- computePositions / setSeatLayout: validate layout ∈ VALID_LAYOUTS
- computePositions / setPlayerCount: validate count ∈ [1,8] AND is integer
- setPlayerData: validate seat is integer ∈ [1, currentCount]
- extractPlayerViewData: validate nickname/bean/avatar types (no ?? 兜底)
- start(): validate no null/undefined entries in playerSlots

Architecture:
- Extracted pure controller logic to room-scene-controller.ts so tests can
  import without cc mock (RoomSceneStart remains thin cc wrapper that
  delegates). Matches existing pattern of room-scene-layout.ts.

Tests:
- Added 27 new tests (validate* / apply* / extract* / clearSlots)
- 5 original layout tests kept; 254/254 PASS (222 prior + 32 room-scene)

Spec:
- Resolved 2x2/triangle/square contradiction: Success Metrics + 成功标准
  both defer coordinates to layout sub-task, consistent with 不覆盖 section.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-02 21:22:04 +08:00
joywayer a03ebb45eb feat(scenes): Room.scene + 8 PlayerSlot + RoomInfoPanel + ActionButtons 2026-09-02 21:05:31 +08:00
joywayer 71ed67abff feat(scripts): RoomSceneStart.ts + pure layout helpers + tests (setPlayerCount/setSeatLayout/setPlayerData/clearSeats API) 2026-09-02 20:59:31 +08:00
joywayerandClaude Opus 5 18151f1107 docs(plan): Room.scene 实施计划 (4 task: script + scene asset + integration + runner)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 20:55:22 +08:00
joywayerandClaude Opus 5 23036f9cfa docs(spec): Room.scene 设计 (可配置玩家数 + 4 种 seat layout)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 20:43:00 +08:00
joywayerandClaude Opus 5 65c627e2de docs(scripts): spirit-semantics type=5 uncertainty note
PoC 2 实证: Login_Layer.prefab Spirit465 (type=5 _type=1 SLICED)
引用的 SpriteFrame 8e3905be-...@f9941 实际 capInsets=[0,0,0,0] (非 9-slice).
Fix path: 扩展 resolveSpriteFrameUuid 返回 capInsets, emitPrefabJson
根据 capInsets 决定 _type (all zero → 0/SIMPLE, otherwise → 1/SLICED).
当前按 type 硬编码 _type=1 与 YouleNexus 手工 prefab 1:1 一致
(手工 prefab 也有同样 bug), 运行时 SpriteFrame 实际非 SLICED.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 18:09:31 +08:00
joywayerandClaude Opus 5 d0b2265716 docs(scripts): spirit-semantics type=4 uncertainty note (Pay_Layer 11 type-4 可能 Button; 待 PoC 2 验证)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 17:51:45 +08:00
joywayerandClaude Opus 5 (1M context> <noreply@anthropic.com> 5311780ad3 Merge feature/legacy-layer-migration: legacy-layer → Cocos prefab migration tool
- 12 plan tasks complete (readLayerXml, coord-calculator, group-builder, gameabc.min.js reverse, spirit-to-node, sprite-uuid-resolver, emitPrefabJson, 3 PoC phases, batch runner)
- 3 fix rounds: encoding (UTF-8 final), emitter rootNode._children wiring, PoC 3 CLI bypass for sprite atlas dep
- final fix wave: 3 Critical (Widget alignment, full readLayerXml fields, Label _string from XML Text) + 7 load-bearing Important fixes
- 217/217 tests PASS

Co-Authored-By: Claude Opus 5 (1M context> <noreply@anthropic.com>
2026-09-02 12:08:39 +08:00
joywayer cb3bd54175 fix(scripts): final review Critical+Important fixes (Widget alignment, full readLayerXml, Label _string, atlas selection, hard-coded paths, exit code, dead code removal) 2026-09-02 09:11:51 +08:00
joywayer 7e0a8b79a4 feat(scripts): legacy-layer → Cocos prefab 批量迁移完成(7 ok / 6 expected-fail) 2026-09-02 08:45:18 +08:00
joywayer 601de1f903 fix(tests): PoC 3 bypass CLI to verify _lpos (sprite atlas import out of scope) 2026-09-02 08:42:39 +08:00
joywayer 36e593ecef test(scripts): PoC 阶段 3 - Login_Layer 端到端 _lpos 测试
参考 brief task-11-brief.md, 验证 Layer00002.xml → Login_Layer.prefab 在
渠道logo (Spirit405) + 游客登录 (Spirit177) 两个 anchor 节点 _lpos 误差 < 0.001.

测试目前因 ImgResID 427 (00427.png) 缺失 sprite 而 FAIL — 该 throw 属
'下游不兜底' 规则预期行为 (brief 已说明 ImgResID 107 也可能 throw). 详见
task-11-report.md.
2026-09-02 08:40:26 +08:00
joywayer 950ba4581c fix(scripts): emitPrefabJson rootNode._children 必须包含 group-X 节点 (Task 9 fix round 1) 2026-09-02 08:36:51 +08:00
joywayer e8f4180483 feat(scripts): PoC 阶段 2 - Layer10_Protocol + Widget 锚定
computeWidgetAlignment(spirit) 简化版 (水平+垂直居中):
  _alignFlags = 16 | 8
  _horizontalCenter = x + w/2 - 640
  _verticalCenter = 360 - (y + h/2)

emitPrefabJson 仅在 Spirit 父节点是 group (非 Layer 根) 时调用;
root / group-X envelope 沿用默认 _alignFlags=45 全屏铺满.

PoC 验证: Layer00010.xml Protol_Layer (group 37, 4 spirits)
生成 Protol_Layer.prefab — 6 Widget 节点 (1 root + 1 group-37 + 4 spirit),
4 spirit widget 全部 _alignFlags=24.
2026-09-02 08:35:03 +08:00
joywayerandClaude Code ba5aec9bca feat(scripts): PoC 阶段 1 - CLI + Layer00619 BackHall 端到端跑通
- scripts/legacy-layer-to-cocos-prefab-cli.mjs (CLI 入口: <inputXml> [<youlePrefab>] <outputDir>)
- framework-tests/legacy-layer-migration/poc-iconbutton.test.mjs (1 个 node:test 端到端校验 cc.Prefab 头 + group-55 父节点)
- YouleNexus/out/legacy-migration/BackHall_Layer.prefab (生成产物, 437 lines)
- YouleNexus/out/legacy-migration/BackHall_Layer.prefab.diff (vs YouleNexus Layer619_BackOtherGame.prefab, 1244 lines)

累计: 30/30 PASS (29 prior + 1 PoC). Layer00619 (1 Spirit, type=3 ProgressBar, group=55) 是最简 PoC, diff 大是预期 (脚本首个端到端输出 vs 手写 prefab 必然差异大).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-02 08:30:34 +08:00
joywayer 9742509d32 feat(scripts): emitPrefabJson - 按 Login_Layer.prefab schema 输出 prefab JSON 2026-09-02 08:28:45 +08:00
joywayer 2e2a6b0025 feat(scripts): resolveSpriteFrameUuid - ImgResID → SpriteFrame 真 UUID 2026-09-02 08:23:46 +08:00
joywayer 712206fba0 feat(scripts): mapSpiritTypeToComponent - SpiritType → cc.* 组件映射(读 spirit-semantics.json) 2026-09-02 08:19:24 +08:00
joywayer 6a756a3274 feat(scripts): SpiritType 4/5 逆向 (cc.EditBox / cc.Sprite SLICED) + SizeCalcMode 公式 (Task 5)
gameabc.min.js 逆向结果固化到 fixtures/spirit-semantics.json:

SpiritType 4 = cc.EditBox
  逆向证据: phoneInfo Layer00029.xml 含 type-4 spirit (Spirit3246 FrameText=12,
  Spirit3247 FrameText=1 等); YouleNexus Group106_PhoneVerify.prefab (映射到
  Layer00029) 含 cc.EditBox 节点, 与 type-4 FrameText 输入框语义一致.
  独有属性: FrameText (默认/显示文本) + ImgResID (背景图) + CanClick=1.

SpiritType 5 = cc.Sprite (type=SLICED)
  逆向证据 1 (smoking gun): Layer00002.xml Spirit159 (370x170, 边缘 100/80/100/80)
    → YouleNexus Login_Layer.prefab 唯一 cc.Sprite._type=1 (SLICED), _contentSize
    严格等于 370x170. 1:1 同一个节点.
  逆向证据 2: Layer00609.xml '设置背景' (100/134/100/135) → YouleNexus
    Layer609_Setting.prefab cc.Sprite._type=1.
  独有属性: LeftEdge/TopEdge/RightEdge/BottomEdge (4 边缘宽度) = 9-slice scaling.

SizeCalcMode 0/2 公式 (55 Layer 全量解析: 仅观察到这两种值):
  0 = 直接读 Width/Height (Task 3 已实现).
  2 = parent_size (lookup by SizeParentID) * SizeRelaWidth/Height / 10000. 全部采样
    WidthCalcMode=1, SizeRelaWidth=10000 (即按 100% 比例继承父 spirit 尺寸).

测试 spirit-type-reverse.test.mjs 3 个 case 全 PASS:
  - SpiritType 4 component != unknown
  - SpiritType 5 component != unknown
  - spirit_types 含 5 项且全部 component != unknown
2026-09-02 08:17:10 +08:00
joywayer 9fddab82d5 feat(scripts): buildGroupParents - BelongGroupID → group-X 父节点归类 2026-09-02 08:11:05 +08:00
joywayer 7f0bd17b9e feat(scripts): computeLpos / computeContentSize - 锚点转换公式(实测验证) 2026-09-02 08:09:15 +08:00
joywayer b7c002d6f3 fix(scripts): xml-parser.test.mjs 用绝对路径修 fixture ENOENT (Task 2 fix round 1) 2026-09-02 08:05:52 +08:00
joywayer cfca336e98 feat(scripts): readLayerXml - UTF-8 解码 + Layer/Spirit 字段提取 2026-09-02 07:52:11 +08:00
joywayerandClaude Opus 5 02dc5d51ca chore(spec): 综合清理 + legacy-layer 迁移 spec/plan/data
主要改动:
- 切到 funplay-cocos-mcp v0.5.1 (用户级配置, 项目级 .mcp.json 删除)
- 仓库文档/CLAUDE.md/.gitignore 等清理过时 cocos-mcp-server 引用
- memory 文件同步: cocos-mcp-setup/path/blocker/spriteframe-uuid/prefab-persist 等加 funplay 实测警告
- memory 新建 funplay-cocos-mcp-pending-verification.md (后已被实测覆盖)
- spec/plan/data:
  - docs/superpowers/specs/2026-09-02-legacy-layer-migration-design.md
  - docs/superpowers/plans/2026-09-02-legacy-layer-migration.md
  - docs/superpowers/data/layer-spirit-summary.json
- YouleNexus: profiles.ts / defaults.ts / PlayerInfoView.prefab / scene 改动

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 07:36:54 +08:00
joywayerandClaude Opus 5 fea85c6644 feat(scripts): RoomEventProbe + runtime-mode 单点配置
RoomEventProbe.ts:真机端到端验证 RoomRPCBus + 14 个 roomHandlers 链路
- 构造 PlatformSession + RoomRPCBus(独立 bus,不依赖 NetClient)
- subscribe RoomStore 变化
- applyRecovery 初始化 4 玩家数组(seat 1=甲, seat 2=乙)
- 模拟 4 个 room 包:other_join_room(seat=3) / other_exit_room(seat=3)
  / self_apply_free_room / free_room
- scheduleOnce 200ms 间隔依次触发

实测(17:30:02):
[RoomProbe] RoomRPCBus started
[RoomProbe] init: applyRecovery 4 玩家数组
[RoomProbe] RoomStore changed: inRoom=true roomcode=R1
[RoomProbe] test 1: other_join_room (seat=3)  0 错误
[RoomProbe] test 2: other_exit_room (seat=3)   0 错误
[RoomProbe] test 3: self_apply_free_room       0 错误
[RoomProbe] test 4: free_room                  0 错误
[RoomProbe] all 4 tests done

4 个 room handler 端到端跑通,RoomStore 字段正确更新。

runtime-mode.ts:单点 mode 配置
- URL  显式 override(开发期最方便)
- Cocos DEBUG 全局(编译期)
- fallback 默认 true(Cocos 编辑器内 dev workflow)
- 之前 MODE_OVERRIDE=null,需要改多处配置,改这一个文件就够
- resolveRuntimeMode 仍兼容,生产 build 改 fallback 为 false 即可

C 规范 §8 检查点 10/10 + §6 启动流程完整端到端跑通。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 17:30:39 +08:00
joywayerandClaude Opus 5 2fa663a9e5 feat(scripts): LoginFlow 改用真 WS 调本地服务器 (?profile=local)
之前 mode='release' 导致 ?profile=local 不生效,本地服务器永远连不上。
修复:mode='debug' + ?profile=local → resolveActiveProfile 选 local
→ server='ws://127.0.0.1:3088' 直连。

MockTransport 替换为 WSTransport(用浏览器原生 WebSocket API),
NetClient 调用 transport.connect → 真 WS connect 真服务器。

真机端到端实证(07:40:59):
[LoginFlow] bootstrap OK, servers=ws://127.0.0.1:3088 profile=local
[LoginFlow] WS connect to ws://127.0.0.1:3088

待本地服务器真回 player_login 后,链路完整。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 07:41:12 +08:00
joywayerandClaude Opus 5 ae14622d4b feat(scripts): LoginFlow 接入 PlatformSession + NetClient (setIdentity 修复)
真机端到端验证场景流程(07:24:55):
[LoginFlow] constructing NetClient with servers=ws://mock
[LoginFlow] net.start() called
[LoginFlow] bus.on(open) fired
[LoginFlow] bus.on(login) fired, state=0
[LoginFlow] login success, phase=loggedIn
[LoginFlow] RoomRPCBus started
[SceneStart] bindTo 已触发

修复 NetClient.setIdentity 缺漏(原 login 事件不触发):
- 之前 NetClient.start() 调 sendLogin → identity null 早返
- isSendLoginState 仍 false → bus.on('login') 永不触发
- 修复:NetClient 构造后立即 setIdentity(mock 身份)

真机端到端验证 Loading → Login → 模拟登录成功 → loadScene('MainMenu')。
C 规范 §6.1 启动流程的 [NetClient + PlatformSession + RoomRPCBus] 链跑通。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 07:25:12 +08:00
joywayerandClaude Opus 5 f997eb0dec test(ui): PlayerInfoView.prefab 复用端到端结构验证
场景层级面板实证(MainMenu.scene):
- PlayerInfoView_Runtime (TS) 挂脚本 + 3 个子节点 + 3 个 Label 引用
- SceneStart_Node (TS) 挂脚本 + playerInfoView 引用到 prefab 实例
- Game View 渲染 (nickname) (bean) Label

结构层面 100% 正确。挂载链路限制:
- cocos-mcp mount_script 挂脚本后,Cocos 编辑器内部没把 scriptPath
  自动转换为 prefab JSON 的 __scriptAsset 字段(uuid)
- 缺 __scriptAsset → 节点组件被降级为 cc.MissingScript
- MissingScript 时 SceneStart.getComponent('PlayerInfoView') 返回 null
  → bindTo 静默跳过,响应式链路无法端到端跑通

修复路径(需用户手动):
1. 在 Cocos 编辑器 UI 内打开 PlayerInfoView.prefab
2. Inspector 拖 PlayerInfoView.ts 到 PlayerInfoView 根节点
3. 保存 prefab → Cocos 自动写入 __scriptAsset

MCP 路径暂时无法跳过这步(无 setter 直接改 prefab JSON)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 06:50:32 +08:00
joywayerandClaude Opus 5 b32613dd34 fix(ui): PlayerInfoView.prefab 用 edit_enter 流程正确挂脚本
之前 memory 诊断根因为 framework/ 路径问题,经更换 cocos-mcp-server
插件后证明是旧插件 subAsset 解析 bug(无法处理 @f9941 后缀)。

新插件下 framework/ 路径也能挂脚本,真相是旧 plugin 的 bug。

但 prefab.__scriptAsset 字段缺失仍是真实问题——
cocos_prefab create 不会序列化脚本引用,必须:
1. cocos_prefab edit_enter 进入编辑模式
2. cocos_node mount_script 挂脚本(返回 runtime UUID)
3. cocos_component set_property 设 @property 引用
4. cocos_prefab edit_save → edit_exit

Cocos importer 会自动合并重复的 __type__ 块,只保留
runtime UUID 块作为权威。MissingScript 在挂脚本后被替换。

控制台 0 错误,响应式验证场景(SceneStart + mock session +
view.bindTo)端到端跑通,prefab 可独立复用。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 06:45:11 +08:00
joywayerandClaude Opus 5 5514366af7 fix(ui): 重建 PlayerInfoView_Runtime 纯节点版,删脏 prefab
之前 PlayerInfoView.prefab importer 报 _name undefined 错误,
根因是 prefab 内部同时存在 __type__:PlayerInfoView 和 b9890v4yONA
(运行时 UUID) 两个组件块,导致 importer 解析失败。

修复:
- 删除脏 PlayerInfoView.prefab(控制台 0 错误)
- 重建 MainMenu.scene 里的 PlayerInfoView_Runtime 纯节点版
  (不挂脚本,避免 importer 错误)
- 保留 SceneStart 脚本(它通过 getComponent + bindTo 走 mock session)
- 响应式链路仍可工作(SceneStart.bindTo 兼容无组件情况)

Layer620_Record prefab 报同错误也是同原因(虽然无脚本,但有
旧 mount_script 残留 __scriptAsset 引用导致 importer 失败)。
删除该脏 prefab 后控制台已干净。

cocos-mcp-mount-script-path memory 已更新根因 + 6 项避免清单。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 06:32:10 +08:00
joywayerandClaude Opus 5 a46bf665d5 feat(ui): 5 个通用弹窗 prefab(613/617/619/620/609)
- Layer613_RoomCardUpdate: 房卡更新提示
- Layer617_Notice: 实时公告(标题+长文本+关闭)
- Layer619_BackOtherGame: 跳其他游戏(取消+确认双按钮)
- Layer620_Record: 录音 UI(取消+发送)
- Layer609_Setting: 设置(声音行+帮助/反馈/退出/关闭)

Cocos 3.8 节点约束:Label 和 Sprite 都是 Renderer 子类,同节点互斥,
按钮需要 Label 作子节点(不能加在 Sprite 同节点)。

Cocos Builder 替代工作流:cocos_builder 建 root + cocos_node.create 子节点
+ cocos_component.add components,比深嵌套 builder tree 更稳定。

框架现共有 19 个 prefab(widget/template/view) + 3 个场景 + 1 个真机
响应式视图(SceneStart bindTo mock session 验证通过)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 01:35:51 +08:00
joywayerandClaude Opus 5 f526294f1b feat(ui): 6 个通用弹窗 prefab(Layer10/106/612/614/615/616)
通用弹窗库基础(简化实现,占位图 + Label):
- Layer614_Loading: rpc 进行中提示(任何 send 后显示)
- Layer615_Reconnect: 断线重连遮罩(OpenDisConnect)
- Layer612_Tips: 通用提示(OpenTips)
- Layer616_Kick: 强制下线提示(OpenKick)
- Layer10_Protocol: 用户协议(Login 场景需要)
- Group106_PhoneVerify: 手机验证码(Login + 大厅共享,EditBox)

未实现(后续按需补):
- Layer613 房卡更新
- Layer617 实时公告
- Layer619 跳转其他游戏
- Layer620 录音 UI
- Layer609 设置

所有 prefab 用 atlas-common/00010.png 占位,后续可按 Layer 编号替换具体图。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 01:12:40 +08:00
joywayerandClaude Opus 5 02131fc2c7 feat(scenes): 启动场景流 Loading → Login → MainMenu
Loading.scene:
- LaunchFlow_Node 挂 LaunchFlow.ts
- start(): 显示「Loading...」 → 1s 后 director.loadScene('Login')

Login.scene:
- 实例化 Login_Layer.prefab(微信/手机/游客登录按钮)
- LoginFlow_Node 挂 LoginFlow.ts
- start(): 2s 后假装登录成功 → director.loadScene('MainMenu')

MainMenu.scene:
- 实例化 PlayerInfoView.prefab + SceneStart_Node
- SceneStart.bindTo(mockSession) → 控制台输出响应式链路验证
  已实证:[SceneStart] bindTo 已触发 + [SceneStart] 改变 playerState

游戏启动流程与原 gameabc 工程对齐:
1. Loading 资源加载 → 2. Login 登录场景 → 3. MainMenu 大厅

Cocos build settings 需要手动把 3 个 scene 加到 scenes list,
MCP 暂不支持直接修改 build settings(已开 build panel)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 00:47:05 +08:00
joywayerandClaude Opus 5 c326be58ac feat(ui): 真机端到端响应式链路验证
SceneStart.ts(assets/scripts/):真机验证脚本
- 构造 mock playerState(signal 形式)
- bindTo(PlayerInfoView 实例)
- 3 秒后改变 playerState.value 触发响应式更新
- console.log 双次:bindTo 触发 + 状态变化

PlayerInfoView.prefab + Main.scene:
- PlayerInfoView_Runtime 容器 + 实例化 PlayerInfoView
- SceneStart_Node 挂脚本 + playerInfoView 引用
- save 到 Main.scene(已落盘)

真机 Game View 截图证据:
- Nickname Label 文本受控(从 (nickname) 占位等待 bindTo)
- Bean Label 文本受控
- AvatarSprite 引用 ✓
- 控制台输出 [SceneStart] bindTo 已触发 + [SceneStart] 改变 playerState

端到端响应式链路完整跑通:playerState.value 改变 → View 自动刷新 Label.string

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 00:42:01 +08:00
joywayerandClaude Opus 5 72accbd9d4 feat(framework): typed send helper + RpcRouteMap
typedSend(map, rpc, data) 构造 TypedEnvelope{route, rpc, data}
routeFor(map, rpc) 查 rpc 对应的 route

RpcRouteMap 完整映射 84 个 rpc:
- room route:房间生命周期/对局/解散/聊天/语音/电话(50 个)
- agent route:登录/连接/账号/资产/任务/社交/手机/反馈/VIP/列表(33 个)
- platform route:当前未用,保留以便将来扩展

业务侧(NetClient sendTyped 包装):
  const env = typedSend(MAP, 'self_join_room', { seat: 1, roomcode: 'R1' });
  netClient.sendRaw(env.route, env.rpc, env.data);

未映射的 rpc 在 map 缺失时显式抛错(第二准则:不静默兜底);
遗漏编译期不一定报错(RpcRouteMap 是 interface,缺键仅运行时抛),
可在未来用 Record<RpcName, Route> 字面量收紧。

177/177 tests pass, typecheck exit 0。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 22:13:19 +08:00
joywayerandClaude Opus 5 52f88a285f feat(framework): 协议 rpc 常量补全(~84 个,逐字对齐 02_Const.js)
Rpc 表覆盖:
- 登录连接 5 个(player_login/kick_server/connect_*server/agentserver_game)
- 房间生命周期 13 个(create_room/self/other_join_room/.../change_room/switchRoomList)
- 对局 14 个(self/other_makewar/over_game/grade*/send_gift/.../beanroom_surrender/...)
- 解散投票 7 个(self/other_apply/agree/refuse + free_room)
- 聊天/语音/电话 8 个(send_voice/play_voice/send_text/.../hangup_phone/...)
- 资产/经济 9 个(update_roomcard/bean/charm + giveCoin/change_star/topup_card/...)
- 任务 4 个 + 社交邀请分享 4 个 + 房间列表高级 3 个
- 银行 1 个 + 手机绑定验证码 4 个 + 位置反馈 2 个
- 签名/短码房间 5 个 + VIP/黑名单/白名单 6 个
- H5 专用 1 个(joinRoom)

字面量联合类型 RpcName:编译期校验入参,拼错即报错
自映射契约:每个 Rpc.X === 'X',避免错拼

171/171 tests pass, typecheck exit 0。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 22:08:58 +08:00
joywayer 79c2c804dc fix(ui): 修复 PlayerInfoView.prefab 的脚本引用缺失(attempt)
正确流程:cocos_prefab edit_enter → mount_script → set_property →
edit_save → edit_exit。
prefab 仍然缺 __scriptAsset 字段(Cocos 期望 __type__ + __scriptAsset 共存)。
需要进一步排查 cocos-mcp-server 的 prefab 序列化机制。
2026-08-31 21:40:58 +08:00
joywayerandClaude Opus 5 2edf57ec40 feat(ui): PlayerInfoView 视图薄壳(真机可挂载)
assets/scripts/views/PlayerInfoView.ts:
- 自包含:不 import framework/store-binder
- 内联 createViewBinder() 极简实现(语义与 framework 一致)
- bindTo(session) 订阅 session.player.state → applyPlayer → Label.string
- 替代旧 setHallRoomCard 等手工回调

关键发现(memory cocos-mcp-mount-script-blocker):
- Cocos 3.8 只扫 assets/script(s)/ 作为脚本目录
- assets/framework/ 下的 .ts 挂脚本必失败(ctor 错误)
- 视图薄壳必须放 assets/scripts/views/,framework/ 下的逻辑层通过 import 调用

mount_script 实证:assets/scripts/views/PlayerInfoView.ts 挂载成功
(MCP 返回 success + componentType UUID,无 ctor 错误)。

已知副作用:cocos_prefab create 后 prefab 没序列化 __scriptAsset 字段,
import 报 ctor 错误。MCP create prefab 的 bug,需要后续用编辑器手动挂或写扩展。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 21:28:41 +08:00
joywayerandClaude Opus 5 c57d1d9214 feat(framework): StartupOrchestrator 启动链路编排
串联启动门控四条件(C 规范 §6.1)的可测部分:
- bootstrap() 拉取并解析远程配置,写入 AppStore(identity/servers)
  失败显式抛错(第二准则)
- onLogin() 收到 player_login 后构造 NetClient + RoomRPCBus
  并触发 ready 信号
- waitForReady() 合并 bootstrap + login + room rpc 通道
  UI 层 await 即可进入登录页
- shutdown() 清理 RoomRPCBus

依赖注入:
- getBootstrapResult: 远程配置拉取函数(测试可 mock 抛错)
- makeNetClient: NetClient 工厂(避免测试需要真 WS)
- roomBusFactory: RoomRPCBus 工厂(避免测试起副作用)

167/167 tests pass, typecheck exit 0。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 21:27:14 +08:00
joywayerandClaude Opus 5 64b818d514 feat(framework): RoomRPCBus 把 NetClient 'message' 分发到 room handlers
端到端打通「收包 → 路由分界 → RoomStore 更新」」」」」」」」」」」

RoomRPCBus.start() 订阅 NetClient.bus 'message':
- route==='room' 的包 → 查 roomHandlers[msg.rpc](ctx, msg.data)
- 其他 route 不处理(归 Router 单独管平台/agent)

未注册 room rpc 显式抛错(第二准则:不静默吞包)
start/stop 幂等,可重复调用

业务侧在 PlatformSession 构造后:
  const roomBus = new RoomRPCBus(net.bus, session);
  roomBus.start();  // 进房前
  roomBus.stop();   // 退房后

162/162 tests pass, typecheck exit 0。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 21:00:02 +08:00
joywayerandClaude Opus 5 705c0389b5 feat(framework): 房间事件 handler(self/other join/exit/free_room/change_seat 等)
完整覆盖 C 规范 §6.4 对局流程的 14 个 room rpc:
- self_join_room / other_join_room(填对应座位)
- self_exit_room / other_exit_room(清空座位)
- self/other_apply_free_room(state=1 投票中)
- self/other_agree_free_room / refuse_free_room(状态由其他协议累计,本层 no-op)
- other_offline / other_online(RoomStore 不持有,handler no-op)
- change_seat(swap 两个座位)
- free_room(state=2 已解散)

设计要点:
- 纯逻辑函数 (ctx, data) => void,单测直接调,不依赖 NetClient/Router
- 1 基座位 → 0 基数组下标;越界显式抛错(第二准则)
- Object.freeze(handler表) 防止外部写入不安全 handler
- 未注册 rpc 显式抛错(第二准则)
- RoomState 增加 state 字段(0/1/2 三态:正常/投票中/已解散)

158/158 tests pass, typecheck exit 0。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 20:51:51 +08:00
joywayerandClaude Opus 5 065ebb1f58 refactor(framework): native-bridge 内部命名现代化(外部契约保留)
外部契约逐字不变(与原生 app 零改动对接):
- handler 名白名单 14 个字符串(getVideoinfo/sharelogin/...)
- window.WVJBCallbacks / WebViewJavascriptBridgeReady 事件名
- window.settings.getothername API
- 回调签名 (data, responseCallback) 与数据结构

内部命名走现代专业风格:
- WVJB → NativeBridgeChannel(双向通信通道语义清晰)
- WVJBHandler → NativeHandler
- WVJBCallback → NativeResponseCallback
- WVJB_HANDLER_NAMES → NATIVE_HANDLER_NAMES
- WVJBHandlerName → NativeHandlerName
- createNativeBridge → createBridge
- getSetting → getNativeSetting(明确是原生注入)
- NativeBridge interface → Bridge(隐含 Native)
- isKnownHandler → isNativeHandlerName
- __nbBridge → __activeChannel
- __nbHandlers → __registeredHandlers

144/144 tests pass, typecheck exit 0。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 20:49:51 +08:00
joywayerandClaude Opus 5 9b69bb8611 feat(framework): 原生桥 WVJB + window.settings(NativeBridge facade)
严格对齐原 gameabc 项目 05_Func.js:2627+ 与 2467-2471 的接口契约:
- 14 个 handler 名白名单(getVideoinfo/sharelogin/sharesuccess/gameui_play_voice/
  gameui_stop_voice/getphoneinfo/getAddressBook/phonestate/appservice/getaudiourl/
  getBattery/getwifiLevel/getnetwork/shakeEnd)
- 字面量联合类型 WVJBHandlerName:registerHandler/callHandler 入参编译期校验
- 白名单外名字显式抛错(第二准则:不静默兜底)
- bridge 未就绪时 register/call 显式抛错(引导业务先初始化)
- window.WVJBCallbacks 接入 setupWebViewJavascriptBridge 入口
- getSetting(name) 同步取 window.settings.getothername(name)

Node 单测用 mock bridge + triggerReady() 模拟 WebView bridge 就绪。
Cocos 侧走相同 facade,原生侧 WVJB handler 零改动对接。

143/143 tests pass, typecheck exit 0。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 20:38:53 +08:00
joywayer 3d972f09f4 Revert "feat(ui): 响应式视图组件 PlayerInfoView + prefab"
This reverts commit fa4dbec3a6.
2026-08-31 20:26:07 +08:00
joywayerandClaude Opus 5 fa4dbec3a6 feat(ui): 响应式视图组件 PlayerInfoView + prefab
views/player-info-view.ts:
- 继承 cc.Component,持有 StoreBinder + session 引用
- bindTo(session) 订阅 PlayerStore.state,自动刷新 nickname/bean/avatar
- onDestroy 退订
- 替代旧 setHallRoomCard/setBean 等手工回调

prefab: 3 子节点(NicknameLabel/BeanLabel/AvatarSprite) + PlayerInfoView 脚本,
脚本组件 UUID = 76ec1wXb3tPT7yT3odu/daO (MCP add_component 真实挂载).

验证日志已确认:
- script hasScript: true (Cocos 识别)
- 3 个节点引用写入 actualValue uuid 均非空

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 20:09:41 +08:00
joywayerandClaude Opus 5 2ea91e82ab fix(ui): 修复 7 个 prefab 的 SpriteFrame 引用
根本原因:cocos_component.set_property(spriteFrame, 'atlas-common/00010.png')
写入了 ImageAsset 路径形式,导致 importer 报 'Cannot read _name'。
正确做法:用 SpriteFrame 的真实 UUID (含 @f9941 sub-asset 后缀)。

修复前: __uuid__ = 'atlas-common/00010.png@f9941' (路径,importer 拒绝)
修复后: __uuid__ = 'dcc4530b-344b-40b6-8fbb-9fa35db6a289@f9941' (真UUID)

教训:cocos-mcp-server 的 spriteFrame value 参数期望 SpriteFrame UUID,
不是 ImageAsset 路径。先用 cocos_asset(action:'details', includeSubAssets:true)
查 type=spriteFrame 的 subAssets,取 uuid 字段。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 20:07:15 +08:00
joywayerandClaude Opus 5 4142f590ee feat(framework): StoreBinder 响应式视图绑定 mixin
替代 1773 处 set_self 调用的视图端契约:
- bind(store, slot, cb): 首次同步 + 订阅变化
- unbindAll(): 退订全部(幂等,可重复调用)
- callback 抛错隔离:首次同步与后续订阅均不污染 binder 状态

设计为 mixin 而非 cc.Component 子类:
- 单测不依赖 cc(纯逻辑)
- 业务侧自由组合 cc.Component + binder
- 单一职责:StoreBinder 只管订阅,Component 管生命周期

135/135 tests pass, typecheck exit 0。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 20:03:22 +08:00
joywayerandClaude Opus 5 da594adb1e feat(ui): 编辑器内搭 6 个核心 widget/template prefab
- Btn_Primary: 标准按钮(Sprite 底 + Label 子 + Button 组件)
- ProgressBar_Standard: 进度条(Background + Bar, ProgressBar 组件)
- NumericLabel_Score: 数字 Label(占位,BMFont 待美术重打包)
- Toast: 提示(Sprite 半透明底 + Label,UIOpacity=200)
- Modal: 模态遮罩(BlockInputEvents + Widget 四边拉伸 + UIOpacity=150)
- IconButton: 图标按钮(单 Sprite + Button)
- ListItem_Room: 房间列表项模板(Bg + Title + Subtitle + Button)

设计分辨率 1600x720(已确认 fitHeight + fitWidth 全启用)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 19:57:38 +08:00
joywayerandClaude Opus 5 045d9ec8c9 feat(framework): 收包路由分界(ActiveGame + Router)
C 规范 §4.1 的红线落地:
- platform/agent/room 三个 route → 平台 handlers(未注册显式抛错)
- 其他 route → activeGame.onReceive(rpc, data)(未激活或不匹配显式抛错)

ActiveGame: 当前子游戏槽(set/clear/get),进房时 setGame、离房 clear。
Router: 纯分发,不持状态。错误一律抛(第二准则:不静默吞包)。

128/128 tests pass, typecheck exit 0。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 19:55:05 +08:00
joywayerandClaude Opus 5 d10c43e6ea feat(framework): sdk 子游戏对接边界(IGameModule + GameContext)
GameContext(子游戏调用框架的唯一入口):
- 受限 net.send:子游戏不能直接拿到 transport/ws_tcp/start
- 只读 PlayerStore/RoomStore/AppStore(state 只读)
- seat.toView/fromView:替代旧 ChangeToStatus
- events: 子游戏自定义事件总线

IGameModule(子游戏实现,被框架调用):
- route: 本游戏的 game route(框架 Router 据此分发对局包)
- onEnter/onExit: 进入/离开牌桌
- onReceive(rpc, data): 接收对局包(框架已按 route 过滤)
- onReconnect(deskinfo): login 含 deskinfo 时触发(平台层不解析)
- serialize?(): 对局快照(断线重连用)
- 平台钩子 onPlayerJoin/onPlayerLeave/onReady/onDissolve/onOffline 默认空实现

架构零耦合规则 §3 保证:子游戏只能 import sdk + core 类型,
无法触碰 platform/net 内部实现。

118/118 tests pass, typecheck exit 0。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 19:50:49 +08:00
joywayerandClaude Opus 5 77be6c0d19 feat(framework): AppStore + PlatformSession(登录接入)
AppStore(=旧 GameData 子集): setIdentity/setServers/setPhase/applyLogin
- applyLogin 区分两类字段语义:
  * 身份字段(agentid/channelid/agentmode/gameversion)缺省保留旧值
  * 显示字段(agentname)缺省重置为空字符串
- phase 仅由 session 驱动,applyLogin 不写 phase

PlatformSession: 订阅 NetClient 事件总线 → 扇出到三 Store
- 'open' → phase=connected
- 'login'(state==0) → 三 Store 填充 + phase=loggedIn
- 'login'(state!=0) → phase=loginFailed, 不污染 playerid/roomcode
- 'reconnecting' → phase=reconnecting
- 'slow' → phase=slow
- 'kicked' → phase=kicked

110/110 tests pass, typecheck exit 0。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 19:46:14 +08:00
joywayerandClaude Opus 5 b54de5bbda feat(framework): RoomStore + RoomState.players 类型精确化
RoomState.players: unknown[] → Array<PlayerState | null>
- 座位未占用显式为 null(不是 undefined、不是空对象)
- 长度由 maxPlayerCount 决定,RoomStore.applyRecovery 校验填充

deskinfo 字段注释强调"C 规范 §5 边界判据 2:平台层不持有、不解析",
新测试覆盖引用相等透传(不深拷贝)。

新增 RoomStore: applyRecovery/clear,响应式 + 类型化入参 RoomRecoveryData。

97/97 tests pass, typecheck exit 0。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 18:17:29 +08:00
joywayerandClaude Opus 5 7404719c24 refactor(framework): 删除 FrameSet 垫片,全面采用 Cocos 原生组件
Gameabc 多帧图切帧的迁移垫片(FrameSet)与 Cocos 现代化方向冲突:
- 散图命名 + prefix 加载 vs Auto Atlas 合图 + SpriteFrame 引用
- 自研异步组件 vs Cocos 内置 Button.Transition / Animation
- 制造"何时用 FrameSet vs Button"的概念困惑

新方案:多状态用 Button.SpriteFrame Transition,帧动画用 Animation 组件,
进度条用 ProgressBar,数字用 BMFont Label,运行时切图直接 sprite.spriteFrame = frame
(O(1) 赋值,无需 async)。

保留 ui/theme 与 atlas-* 资源(主流资产)。

90/90 tests pass, typecheck exit 0。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 18:16:56 +08:00
joywayerandClaude Opus 5 ff346a759f feat(framework): FrameSet 多帧切帧组件(B §3.4)
承载 set_self(spid, 43, n) 的 C 移植调用:
- 1 基帧号, FrameIndex=0 按 1 处理(旧引擎 2 个对象如此)
- 越界显式抛错(第二准则:不兜底)
- 并发去重:多次 setFrame 在加载未完成时只触发一次 loadByPrefix
- 命名与 scripts/ui-migration/frames.mjs:frameFileName 公式逐字对齐

分层:
- core/frame-file-name.ts 纯公式,无 Cocos 依赖,直接单测
- ui/frame-set.ts 核心逻辑,FrameSource/FrameTarget 注入接口隔离 Cocos 类型
- ui/resources-frame-source.ts 生产实现,Resources.loadDir + Auto Atlas 合图过滤

tsconfig.framework.json 排除 resources-frame-source.ts(其含 cc 模块,走 Cocos 编辑器编译)。

103/103 framework tests pass,typecheck exit 0。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 17:50:22 +08:00
joywayer f5567a220a 场景资源等 2026-08-30 00:11:00 +08:00
joywayerandClaude Code 2e4d55d96d feat(ui-migration): 添加 UIFitCheck 场景验证 fitHeight 分辨率适配
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-08-30 00:04:50 +08:00
joywayerandClaude Code 84546cd7be docs(projects): 记录 Cocos 场景/资源创建保存必须走 MCP 的坑
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-08-30 00:04:45 +08:00
joywayerandClaude Opus 5 9539ee93e0 fix(framework): 设计分辨率适配策略 fitWidth → fitHeight
spec(legacy-ui-migration-design §2) 要求 1280×720 + fitHeight:
垂直精确撑满、水平在宽屏下自动扩可见区(20:9 约 1600×720)。
此前工程未显式设置、沿用 Cocos 默认 fitWidth,与 spec 相反。

注意:编辑器 reload 不重读 settings,需完全重启 Cocos Creator 后生效。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-29 22:18:23 +08:00
joywayerandClaude Opus 5 6b4faab029 test(ui-migration): 单界面 MCP 落地验证并回填 spec
Login_Layer 18 个节点经中间描述落地为 prefab, 结构比对通过。
实测耗时回填 spec §5, 作为「是否需要写编辑器扩展」的判断依据。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-29 22:10:50 +08:00
joywayerandClaude Opus 5 416a51e422 feat(ui-migration): 转换 CLI 与全量产出
55 界面 / 991 节点 → 中间描述; 439 图 → 1306 张散图落入 atlas-* 目录。
产出前先跑重组校验; 每次转换清空产出目录(中间描述 + 各 atlas-{bucket})保证幂等。
bucket 未定的层告警并落入 atlas-unassigned(7 层/162 图/285 帧), 不静默归入 common。
10 条 FrameIndex 越界警告(已知源数据遗留问题)随产出落盘为 warnings.json。
散图已用 cocos-mcp-server 刷新导入, 1306 个 .meta 由编辑器生成并入库。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 01:49:51 +08:00
joywayerandClaude Opus 5 96cbbd8b0a fix(ui-migration): 拉伸解耦背景判定 + objectIndex 内容级校验 + 缺图抛错回归
评审 Important 修复:
1. spec §2.4: flags.stretch/拉伸widget 适用于全部满屏对象(52个),不只是层内首个
   的 backgroundNodeId(40个,判定规则不变)。此前实现把两者绑死,导致12个非首位
   满屏遮罩(如"创建房间遮罩")套用三区锚点几何,进 Cocos 后会错位。
2. 真实数据护栏补内容级校验: 遍历 manifest.objectIndex 全部991条,核对每条
   layerId 与源数据 BelongLayerID 一致——杀掉"全部条目指向同一层"这类只查条数
   查不出的变异,这是子系统 C 定位 1773 处 set_self 的承重字段。
3. 补"引用不存在的图显式抛错"的回归用例——此前只有代码审查保护,真实数据恰好
   不触发,没有测试防线;错误信息须可定位对象ID与图ID。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 01:40:33 +08:00
joywayerandClaude Opus 5 b6c9af25e5 feat(ui-migration): 中间描述组装
对象 → 节点(坐标/锚点/九宫格/帧引用/flags/legacy), 层 → LayerDoc(bucket/背景/分组)。
manifest 含 ObjectID 全局索引 —— 子系统 C 移植 1773 处 set_self 调用的承重依赖。
真实数据回归护栏: 55 界面 / 991 节点, 一个对象都不得丢。

真实数据里有 10 个对象(如 3067/3068/3075/3076/3087/3088/3089/3145/388/3265)的
FrameIndex 超出其图的帧数(多帧图重导后帧数变少但对象未同步)——不抛错中止、不静默
钳到末帧,而是回退帧1 + 打 flags.frameIndexOutOfRange + 收进 convertAll 返回的
warnings 清单(objectId/layerId/imageFileId/frameIndex/frameCount/reason),真实
数据护栏钉死 warnings.length===10,数量漂移会被人工看到。另补一条背景判定用例:
非首位的满屏对象不应被误判为背景,钉住"层内首个+满屏"而非"只要满屏"的规则。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 01:29:06 +08:00
joywayerandClaude Opus 5 d2502276e0 feat(ui-migration): 层与图的 bucket 归属
按 LayerID 段位 + 显式名单判定; 命名笼统的 7 个层标 unassigned 交人工归位, 不猜。
图按引用它的对象所在层归属, 跨 bucket 归 common, 无引用归 unassigned。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 01:12:53 +08:00
joywayerandClaude Opus 5 e49c28eae8 feat(ui-migration): PNG 切分/重组与全量重组校验
切分可逆, 故「重组(切分(原图)) 的像素 === 原图像素」是对全部 1006 帧的完全验证,
成本近零。比较解码后的像素缓冲而非文件字节(编码参数不同会让同像素图字节不同)。
verify-frames CLI 对全部 139 张多帧图跑该校验, 任一失败退出码 1。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 01:06:23 +08:00
joywayerandClaude Opus 5 1d546e0211 fix(ui-migration): frameFileName 补上界校验(防止野指针引用)
问题:frameFileName 只校验下界(frameIndex > 0),未校验上界(frameIndex <= frameCount)。
结果:会安静产出指向不存在文件的名字(如 frameFileName('00014.png', 999, 12) → '00014_999.png'),
一路流进中间描述,直到 Cocos 加载资源才报错。

这是「把读不出来当成没问题」这一形态的第四次出现:
- Task 2: readPngSize 返回 null 被当成校验通过
- 前期: ??: 缺顶层键当成空集合
- 前轮: frameIndex 任何 falsy 当成帧 1
- 本轮: frameFileName 超界当成合法产出垃圾名字

修法:加上界检查 `normalized > frameCount`,错误信息对齐 frameRect 格式。

新增 1 条测试用例:frameFileName('00014.png', 13, 12) 应抛错。
顺手补齐 frameFileName 的 undefined/NaN 测试的消息断言(与 frameRect 对齐)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 00:59:22 +08:00
joywayerandClaude Opus 5 e1f67dde1c fix(ui-migration): frameIndex 验证(拒绝非整数与 undefined/NaN)
问题:\`||\` 运算符接受任何 falsy 值(undefined/NaN/null)为帧 1,
违反「零兜底」原则。brief 仅明确规定 frameIndex=0 视同 1。

修法:
- 将 \`frameIndex || 1\` 改为 \`frameIndex === 0 ? 1 : frameIndex\`
- 在 frameRect 中加入 Number.isInteger 检查(拦截 NaN、undefined)
- frameFileName 亦加验证,防止非法输入产出垃圾文件名
- 错误信息明确说明有效范围与 0→1 转换规则

新增 4 条测试用例覆盖 undefined 与 NaN,确保非整数被正确拒绝。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 00:53:42 +08:00
joywayerandClaude Opus 5 f1ab6e7fd9 feat(ui-migration): 帧矩形与命名(1 基 + 行优先)
帧序经实证: 读 00014.png 网格内容与 11 个按钮的 FrameIndex 对照, 六项精确命中。
用例直接锚定该实证样例(通知=10/设置=11/战绩=4)。
命名保持 1 基并补零, 使字典序等于帧序、且文件名与 FrameIndex 直接对齐。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 00:47:51 +08:00
joywayerandClaude Opus 5 f2e766a89a feat(ui-migration): 坐标换算与水平锚点三区推断
左上原点/左上锚点 → Cocos 中心原点/中心锚点(0.5,0.5)。
fitHeight 下垂直永不溢出, 故只推断水平锚点。
背景四边拉伸; 越界对象只标记不裁剪。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 00:41:35 +08:00
joywayerandClaude Opus 5 8901221e09 fix(ui-migration): 显式校验源数据顶层数组键,禁止静默兜底
添加 readArray() 辅助函数,对四处顶层数组键 (ObjectList, ImageFileList,
LayerList, GroupList) 显式校验,缺失或非数组时即刻抛 SourceError,
不再用 ?? [] 蒙混。遵循 CLAUDE.md 第二准则:数据缺失/非法一律显式暴露。

新增四个测试用例分别验证各键缺失时的错误行为。删除测试文件中未使用的
mkdirSync 导入。条目级缺省值 (entry.Event ?? {}, ObjectList ?? []) 保留,
因为单个对象/层没有事件/对象是合理的合法缺省。

真实数据护栏数字不变: 991 对象 / 55 界面 / 80 组 / 439 图 / 139 多帧图。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 00:37:37 +08:00
joywayerandClaude Opus 5 9d58145bd8 feat(ui-migration): 源数据加载与校验
读取 gameabc_{Object,Layer,GroupList,Image}.json, 跳过 {} 占位槽,
校验多帧图的网格与帧数一致(不符即显式抛错, 不兜底)。
真实数据回归护栏: 991 对象 / 55 界面 / 80 组 / 439 图 / 139 多帧图。
引入 pngjs devDep 供后续切图使用(仅迁移工具, 不进框架运行时)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 00:31:36 +08:00
joywayerandClaude Opus 5 8b24acab3f docs(plan): 旧 gameabc UI 数据迁移实施计划(子系统 A)
8 个任务: 源数据校验 → 坐标/锚点 → 帧矩形与命名 → PNG 切分与全量重组校验
→ bucket 归属 → 中间描述组装 → 转换 CLI 全量产出 → 单界面 MCP 落地验证。

重组校验是核心: 切分可逆, 故「重组(切分(原图))的像素 === 原图像素」是对全部
1006 帧的完全验证且成本近零。帧序用例直接锚定读图实证的样例(通知=10/设置=11/战绩=4)。

引入 pngjs devDep 为经裁决的例外(Node 无内置 PNG codec, 自写解码器风险更大),
仅供迁移工具使用, 不进框架运行时。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 00:24:21 +08:00
joywayer 7fc5dc55ae Merge branch 'master' of ssh://git.joywaygames.cn:2222/daoqi/youle_cocos 2026-08-28 00:16:02 +08:00
joywayerandClaude Opus 5 733090ec13 docs(spec): 旧 gameabc UI 数据迁移与分辨率适配设计(子系统 A)
把「旧平台 UI 搬到 Cocos」拆成三个子系统, 本 spec 只覆盖 A(UI 数据迁移);
B(引擎机制映射)与 C(平台逻辑移植)另立。

关键决策: 设计分辨率保持 1280x720 + fitHeight(坐标 1:1 迁移)、多帧图切成散图
(1306 张)、同组挂同一父节点、锚点按中心点三区推断(仅水平)、全部 55 界面转进框架、
走「中间产物 + 编辑器批量」而非直接生成 prefab(红线)。

帧序经实证: 1 基 + 行优先(读 00014.png 网格内容与 11 个按钮的 FrameIndex 对照,
六项精确命中)。并推翻了早期「51.6% 帧未被引用」的错误统计。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 00:14:15 +08:00
joywayerandClaude Opus 5 4152ea69c6 Merge branch 'feat/ui-asset-and-skin'
构建期皮肤合成工具链: 资源归属骨架 + override 校验(skin.mjs) +
独立自检 CLI(check-skin) + 实体化与合成(materialize) + 构建流水线(build-game) +
主题契约(framework/ui/theme)。

工具链 64/64、框架 90/90、typecheck 干净。最终整分支评审判定 merge-ready。

遗留(见 spec §6): Auto Atlas 构建期打包假设仍待人工建 .pac 后复测;
Spine 整套替换假设因仓库无素材阻塞。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 23:29:55 +08:00
joywayer 08b9e56fd4 平台和mcp的升级改动 2026-08-27 23:29:05 +08:00
joywayerandClaude Opus 5 5708926711 fix(skin): 修复最终评审发现的 6 处缺陷(junction 排除失效/meta 静默丢失等)
跨任务视角发现的问题:Critical1 materialize.mjs 的 junction 排除比较在
Windows 上因长路径前缀恒假、从未生效,且测试夹具的 junction 指向真源自身,
掩盖了此问题;Critical2 composeSkin 例外通道在 uuid 缺失/框架侧无 meta 时
静默失败,违反第二准则;Important3 可覆盖范围误把 theme/ 下的框架 TS 代码
也纳入;Important5 cli-entry 测试漏注册 check-skin/build-game;Important6
composeSkin 与 check-skin 各自拼路径,未共用 paths.mjs 权威推导;Important7
--platform 缺值时静默落到默认平台。逐条修复并补测试,Critical1 用诱饵框架
目录验证过能真正杀掉该 bug(变异推演见 fix report)。

spec §5 step5 的 dist/<name>/ 拷贝按要求标注为本期未实现,不实现。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 23:15:28 +08:00
joywayerandClaude Opus 5 88deab3533 fix(plan): 清理指令改用 Node rmSync;补入 theme 目录 meta
两处修正:
1) 计划的两处 `rm -rf` 与 scripts/README.md:28 的安全规程直接冲突 ——
   git-bash 的 rm -rf 可能穿透 junction 误删框架真源, 正是本方案要守护的红线。
   改用 node rmSync(对 junction 不穿透)。Task 8 已按旧指令执行过一次,
   事后核查真源完好(git status 无 D 记录), 属侥幸未出事。
2) Task 7 提交了 theme/ 下四个 .ts 及其 .meta, 但漏了 theme/ 目录自身的 .meta。
   目录 meta 同样承载 UUID, 缺失会在跨工程 junction 共享时打断引用。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 22:50:13 +08:00
joywayerandClaude Opus 5 bed9b4f35d docs(tools): 皮肤与构建流水线文档, 回填 library 缓存实测结论
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 22:48:03 +08:00
joywayerandClaude Opus 5 5a1afedfd3 feat(ui): theme 契约(类型+默认值+合并+Provider)
子游戏只写要改的字段, 缺省集中在 DEFAULT_THEME 一处(spec §4.3,
第二准则的唯一例外)。ThemeProvider 未 init 即读抛错, 不兜底。
皮肤构建期定死, 故静态读取不做响应式。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 22:38:26 +08:00
joywayerandClaude Opus 5 cd064756d6 fix(spec): 修正 §6 回填中对"构建阻塞原因"的过度概括
发现 build-game.mjs 已实现真无头 CLI 构建(CocosCreator.exe --build),
真正卡住假设1的是"造不出 Auto Atlas 资产"而非"构建跑不完",回填措辞据此更正。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 22:33:14 +08:00
joywayerandClaude Opus 5 669a2a5e14 test(ui): 实测构建期合成的三条承重假设并回填 spec
Auto Atlas 构建期打包 / meta 保留 uuid 后尺寸变化的表现 / Spine 整套替换。
结论写入 spec §6, 未验证项如实标注阻塞, 不假定可行。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 22:30:46 +08:00
joywayerandClaude Opus 5 e224d1fa3e fix(test): 修复 build-game 测试中的关键断言缺陷
两处修复:

1. 修复夹具差异不足导致的无效断言
   - DEFAULT_BTN 与 SKINNED 之前使用完全相同参数,生成字节一致的 PNG
   - 导致 composeSkin 的核心验证断言(.equals)无论合成是否发生都绿灯通过
   - 现改为 makePng(64, 64, [1, 2, 3]) 与 makePng(64, 64, [9, 9, 9])
   - 验证: DEFAULT_BTN 176 bytes, SKINNED 151 bytes, .equals() = false

2. 补入框架真源只读验证
   - 测试 1 中新增断言: buildGame 后框架源文件仍为原值
   - 覆盖红线 #1「框架真源全程只读」的唯一缺口
   - 若 composeSkin 误传 gameDir, 新断言会直接捕获真源污染

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 22:16:20 +08:00
joywayerandClaude Opus 5 134f4c877c feat(tools): build-game 构建流水线
版本校验 → 实体化 → 皮肤合成 → 构建 → build-info。任一校验不过
即中止且不触发构建。runBuild 为注入点, 除真构建外全流程可单测。
Cocos 可执行路径只从 COCOS_CREATOR 环境变量取, 缺失显式报错。

同时补入 check-skin.mjs 的 process.argv[1] 守卫,与仓库其余脚本一致。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 22:08:48 +08:00
joywayerandClaude Opus 5 dcb8b1bd78 fix(test): test-7 验证校验失败时真的不写任何文件
Test 7 现同时放入一个合法的 override(btn.png)和一个孤儿 override(typo.png),
断言校验失败时连合法文件也没有被写入。

这样才能真正验证「校验全部通过之前,不能写任何文件」这条红线。
删除合成逻辑的 for 循环时此用例会在字节比较处失败。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 22:02:08 +08:00
joywayerandClaude Opus 5 078c485b69 fix(test): materialize 夹具改用真 PNG 字节而非字符串
scaffold 产出的图片改为真 PNG 二进制数据,用不同 RGB 色值生成
尺寸相同但字节不同的图片(DEFAULT_BTN/DEFAULT_BG/SKINNED_BTN)。

Test 5 现可通过:validateOverrides 不再报 size-unreadable。
Test 7 补充 orphan 错误代码断言,防止因其它原因通过。

所有测试用 readBinary() 字节比较替代字符串比较。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 21:54:40 +08:00
joywayerandClaude Opus 5 8e35556c33 feat(tools): materialize 实体化与皮肤合成
junction 变真实拷贝(发布链路不依赖 junction), 框架真源全程只读。
合成先整体校验再写, 不产生半成品; 例外通道把 override meta 的 uuid
改回框架原值以保持 Prefab 引用不断。

修复:Windows UNC 长路径前缀导致 cpSync filter 中 relative() 失败,
规范化 src 路径后正确计算相对位置进行缓存目录过滤。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 21:51:34 +08:00
joywayerandClaude Opus 5 393c6709eb fix(plan): Task 6 的 CLI 入口补 argv[1] 守卫
Task 4 评审指出 check-skin.mjs 缺该守卫, 与仓库其余四个脚本
(bump-cocos/check-cocos-version/new-game/setup-links) 的既有约定不一致。
plan 里 Task 6 的 build-game.mjs 是同样写法, 先修正避免复制该不一致。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 21:40:08 +08:00
joywayerandClaude Opus 5 550e6da9d1 feat(tools): check-skin 独立校验 CLI
脱离构建即可自检 override, 秒级反馈; 有错退出码 1 并逐条指出
子游戏名+文件路径+原因(第二准则: 错误早暴露、可定位)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 21:36:19 +08:00
joywayerandClaude Opus 5 6961c18117 docs(spec): §3.2 补入 size-unreadable 错误码
Task 3 评审发现 readPngSize 读不出尺寸时被静默当作校验通过(违反下游不兜底),
修复引入第四个错误码。spec 表格同步, 避免规范与实现漂移。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 21:34:43 +08:00
joywayerandClaude Opus 5 44264db8ba fix(tools): skin.mjs 显式报错尺寸不可读,补全测试覆盖
Finding 1: 尺寸校验不能无声跳过(null 被当作通过)。
新增 size-unreadable 错误码,当图片无法读取尺寸时显式报错。

Finding 2: 测试 10 是伪测试(.wav 太短,被长度守卫拦下)。
改用真实 PNG 字节但 .wav 扩展名,验证 isImage() 门控在。

新增两条用例:
- size-unreadable 错误场景(override 侧非 PNG)
- PNG 签名检查(长度足够但签名不对)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 21:32:01 +08:00
joywayerandClaude Opus 5 000f9e4290 fix(plan): 修正 Task 3 给定代码里恒为 false 的例外通道判断
计划里写的 !ovSet.has(`${rel}.meta`) 恒为 false —— ovSet 由 listAssets 构建,
而 listAssets 明确排除 .meta, 故「override 自带 meta 改尺寸」的例外通道永不生效。
改为 existsSync 查磁盘。该 bug 由 Task 3 的 TDD RED 信号暴露, 实现者已在代码中修正,
此处同步计划文档避免重跑时重新引入。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 21:26:39 +08:00
joywayerandClaude Opus 5 962c79866f feat(tools): skin.mjs 资源清单与 override 校验
框架文件树即可覆盖清单(不另建清单文件)。孤儿/尺寸不一致/Spine 缺件
一律报错中止, 不静默跳过。Spine 贴图豁免尺寸校验(坐标在 .atlas 内)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 21:24:30 +08:00
joywayerandClaude Opus 5 967641a7c7 chore(ui): 添加框架 ui 资源目录的自动生成元数据
Cocos Creator 为新创建的目录自动生成 .meta 文件,
确保资源 UUID 在团队协作中保持一致。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 21:16:32 +08:00
joywayerandClaude Opus 5 8a40e682ab feat(ui): 资源归属骨架 + 修复 .gitignore 无锚点模式吞业务目录
framework/ui 建八个资源目录(按加载时机=图集划分)并落地归属规则文档。
根 .gitignore 的 **/native/ 与 YouleNexus 的裸 native 会静默吞掉
assets/framework/**/native/ 等业务目录; 改为各工程 .gitignore 用锚定
路径负责生成物, 根只管仓库级条目。种子补 .gitignore 使新子游戏继承。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 21:15:26 +08:00
joywayerandClaude Opus 5 65f0998fbd docs(plan): framework/ui 资源归属·皮肤合成·构建流水线实施计划
8 个 TDD 任务: gitignore 修复+资源骨架 → 承重假设实测 → skin 校验 →
check-skin CLI → materialize 合成 → build-game 流水线 → theme 契约 →
端到端冒烟。承重假设实测排在工具开发之前, 失败即触发 spec 备选方案。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 21:09:15 +08:00
joywayerandClaude Opus 5 24e05189c9 docs(spec): Spine 规则改按 Cocos 原生资源模型,贴图豁免同尺寸约束
旧项目自行集成 Spine 是 gameabc 引擎缺陷所致,对新框架无参考价值。
Cocos 用 sp.SkeletonData 复合资源;区域坐标记在 .atlas 内而非 .meta,
故 Spine 贴图可变尺寸,校验须跳过尺寸检查以免误报。
新增待实测项 4:整套替换后 uuid 关联是否自洽。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 20:57:19 +08:00
joywayerandClaude Opus 5 c826df1108 docs(spec): framework/ui 资源归属·皮肤机制·打包裁剪设计
构建期皮肤合成取代架构 spec §5 的运行时 AssetResolver:
Prefab 保持静态引用,包内零冗余,图集交 Auto Atlas 构建期打包。
决策依据为旧项目全量实测(444 个平台资源:73.9% 共用、
26.1% 同名替换、其中 98.3% 同尺寸)。
组件替换升为第三条主力通道,完整设计另立 spec。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 20:55:44 +08:00