feat(playtest): add the character playtest workbench - #131
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
@fennoai /review |
Review ResultFound 1 issue.
Verified:
Note: |
|
Confirmed and fixed in 6840a08. Measured on a running page before the fix: the sprite resolved to Root cause is the Tailwind 4 upgrade: translate utilities now compile to the standalone Second-order effect you did not mention, and the reason this was worth fixing before merge rather than after: Fix drops Added |
|
/review -claude |
需要 review 的话,直接输入"/review" 这个 slash 指令即可,这个 slash 会走专用的 review skill、subagents |
Command Clarification明白。这个触发内容是在说明用法,不是直接请求我重新 review,所以本次没有启动 PR review。 需要我走专用 review 流程时,请直接发送: |
There was a problem hiding this comment.
Playtest Workbench 审查
整体质量高:纯运行时逻辑与 React 胶合层 分离干净、rAF 用 ref + 函数式 setRuntime 正确避开了闭包陷阱、架构边界测试(别名与相对路径都折算)是很强的护栏。安全面无问题——路由参数经 encodeURIComponent,错误信息已脱敏,后端 imageUrl 只进 <img src>(非脚本 sink)。
以下为具体发现,已按位置放置为行内评论:
重要
runtime.tsadvanceRuntime:循环动作 + 非正durationMs会导致 while 死循环,当前仅靠model.ts的远端Math.max(1,…)clamp 兜底,而advanceRuntime已导出、测试直接手搓 action 绕过该保护。runtime.ts/use-playtest-runtime.ts:rAF 每帧都{ ...runtime }返回新引用,即使角色静止/停在末帧也会 ~60fps 全树重渲染。建议在advanceRuntime无变化时返回原引用,让useStatebail out。index.tsx:key={initialActionId ?? ''}的整树 remount 与usePlaytestRuntime内部基于initialActionId的 reset effect 重复,且更重(丢失 bounds、重装监听、重跑预加载)。二选一。
次要
index.tsx:fetch effect 依赖[characterId, outfitId],但 body 只用characterId;同角色换 outfit 会多余重拉整份 Character。README.md:「按住切 walk / 松开切 idle」表述为无条件,但setDirectionInput在缺少对应 action 时回退到当前 action(仍移动)。属优雅降级边界,建议补注「存在 walk/idle 动作时」。README.md:两者都缺失时的 100ms 硬默认(DEFAULT_FRAME_DURATION_MS)未在文档中提及。
|
@fennoai 先问一个前置问题,请只回答,暂时不要做任何改动。 本 PR 的 head 分支在 fork 上:huyanxius/Windup:feat/playtest-workbench,不在 1024XEngineer/Windup 主仓。PR 的 maintainer_can_modify 为 true。 你有没有权限把提交推到这个 fork 分支? 如果有,我接下来会把要修的几条发给你,你在这个分支上改。 |
Permission Answer有权限判断上可以推:我检查了 需要说明一点:真正执行 本次只回答问题,没有改代码、没有提交、没有推送。 |
|
@fennoai 那好,对于你提出的问题反馈如下,四条采纳,一条按下述范围采纳。请在本 PR 分支上修复。 2. index.tsx 的 key 与 hook 内复位重复(采纳) 3. index.tsx 的 outfitId 冗余依赖(采纳) 4. README 表述绝对化(采纳) 5. advanceRuntime 每帧新对象(按缩小的范围采纳) 约束
|
Completed已在本 PR fork 分支 Commit: 改动范围保持在
验证结果:
Push succeeded; workspace is clean. |
|
@fennoai /compact |
Compact SummaryCurrent PR context:
Recent completed work:
Verification from the last implementation run:
No repository changes were made for this |
|
@fennoai 长期记忆:在本仓库中所有的 Github comments 都对我使用中文。 |
已记录已写入长期记忆:在 本次没有改动仓库代码。 |
There was a problem hiding this comment.
Nit: 这种一般不用提交,边界通过文档/注释/良好的目录组织一般就能体现了,不用单独写测试代码来保证
实现
/playtest/:characterId/:outfitId,替换 #70 留下的 13 行占位页。Closes #130
Why
角色资产做完之后,
main上没有任何地方能验证它「在游戏里是不是这样」。资产库看得到静态帧,看不到帧按各自时长播起来、按方向键走起来是什么样。帧序错了、时长错了、脚底线飘了,静态列表里都看不出来,动起来一眼就知道。这是导出前的最后一道人工确认。08-03 导师会点名 Playtest 与外部交互最少,适合独立成 issue 单独推进,本 PR 兑现它。
Change Description
pages/playtest/,源码 6 个文件、测试 6 个文件、README 1 个文件,全部落在页面目录内,未改动app、entities、features、shared任何一行。app.tsx上/playtest/:characterId/:outfitId已存在,本 PR 只替换它指向的实现。Implementation Approach
characterApis.get,不接受app层注入的服务——frontend-architecture-v3.md第 2 节与app/app.tsx的注释都写明app只做启动和路由。写法与character-detail、asset-library一致。Frame.index,不按数组下标。 后端整棵下发资产树,数组顺序没有契约保证;照数组播的话顺序一变动画就乱,而且乱得不报错。排序在适配器里做一次,运行时之后只按下标推进。Action.loop带进运行时,loop: false的攻击、跳跃走到末帧就停,不回首帧变成假循环。ApiError的业务码 404(HTTP 200 信封内)与传输失败分别显示「角色不存在」和「角色读取失败」。这一点比character-detail、asset-library细:那两个页面把所有失败收成一句,而 Playtest 是靠带两个 ID 的深链进来的,角色被删或 ID 抄错是常见情况,跟后端挂了不是一回事。src/test/project-assets-backend.ts(feat(projects): implement project center and asset library #119 引入的 HTTP 替身)。那份 fixture 里 walk 的后端帧序恰好是2、0、1,正好把排序这条钉死。-translate-x-1/2和内联transform。Tailwind 4 起 translate 工具类编译到独立的translate属性而非transform,两者叠加而非覆盖,静止位置因此左偏半个精灵宽,measureBounds依赖「静止即居中」算出的左右边界也跟着错位。已去掉工具类,实测精灵中心与舞台中心重合。advanceRuntime按「攒够当前帧时长就翻帧、并从已攒时间里扣掉它」推进。帧时长为 0 时扣掉的是 0,判断条件又恒成立,循环动作会在requestAnimationFrame回调里转不出来,表现为标签页卡死。生产路径由model.ts的 clamp 兜住,但advanceRuntime是导出的纯函数、测试已在绕过 model 直接构造数据,运行时安全不该依赖另一个文件里的隐式不变量,因此在循环内也 clamp 到 1ms。key强制 remount 和 hook 内的复位 effect 做同一件事,remount 还更贵——丢掉已测量的 stage 边界、拆键盘监听、重跑帧预载。现在只留 effect。@/features字面量拦不住../../../features/workflow-controller——两种写法解析到同一个目录。已用一个违规探针文件验证过:加上测试红,删掉转绿。Screenshots
Before —
main上的占位页:After — 待机,左侧列出该造型下全部有帧的动作:
After — 切到行走:
截图取自本地 dev server,配一个只存在于本机、按
API_CONTRACT.md返回固定角色的桩服务(素材为 asset-lab 的 samurai,36 帧待机 / 行走 / 跳跃)。桩服务与素材都不进仓库。Testing
本地执行了 CI 工作流中的全部步骤:
npm run format:check— 通过(66 个文件)npm run lint— 通过,无告警npm run typecheck— 通过npm run test— 通过(17 个文件 / 69 个用例,其中 playtest 7 个文件 / 30 个用例)npm run build— 通过(284.16 kB / gzip 89.63 kB)浏览器手工核对:帧按时长播放、点击切换动作、A/D 与方向键操控、松开回到待机、窗口缩放后移动范围重算、底部操控胶囊在一屏内不需滚动。
Follow-ups
GET /characters/{id}在main上只有数据模型与抽象接口,web/api/下没有对应路由(属 feat(backend): 后端全栈实现——基础设施 + 领域模块 + 用户认证 + API 契约 #75 范围)。字段契约已逐字段对齐,接口挂载后本页面无需改动。在此之前正式路径显示「角色读取失败」。preloadActionFrames预载当前造型下全部动作的帧,动作多、帧数大时首次进入的网络开销未做上限。stage.test.tsx只能钉住成因(transform 字符串完整、元素上不出现 translate 工具类),jsdom 不排版,偏移本身测不出来。同类的布局问题在本仓库目前只能靠人眼或浏览器实测。advanceRuntime在结果无变化时返回原引用,但只有非循环动作停在末帧且角色未移动时会命中。循环动作播放期间frameElapsedMs每帧真的在变,仍然每帧重渲染一次。要消除这部分开销需要把frameElapsedMs移出 state,本 PR 不做。window上,Playtest 路由内没有其他会抢按键的输入控件;将来页面上出现输入框时需要复核isTypingTarget的覆盖面。Related
playtest-inspection实体;本 PR 只做「用已确认的帧操控角色」这一件事,不含质检。两者取舍需另行决定。