ColorYourModel — 通往「可用 v0.1」路线图 · Backlog(对抗证伪快照)
| 文档日期 | 2026-08-06 |
|---|---|
| 状态 | PAUSED(已证伪,待 REVISE) — 用户断网前要求先记录 backlog |
| 方法 | Adversarial Development Loop:PLAN(候选路线图)→ REFUTE(独立子 agent 证伪)→ [REVISE 未做] |
| 当前分支 | feat_theme_persist_paint_ux |
| 候选路线图 | 见文末附录 A |
| 证伪结论 | 7 个 [blocker] + 6 个 [major] + 5 个 [minor],路线图不能作为 v0.1 可靠路径 |
| 实测基线 | npx tsc --noEmit exit 0 ✅;cargo test --lib 13 passed(全在 segment::manual 8 + segment::sdf 5,export/paint/mesh/commands 零覆盖);git tag -l 空(README 称 v0.1 已完成但无 tag 支撑) |
⚠️ 本文件是「继续开发」的接棒点。恢复时从 §0 五个待裁决项 与 §2 修正后顺序 开工,不要从被推翻的候选路线图续。
一、五个必须先行裁决的产品/PRD 项(不裁决则后续全部不可达)
| ID | 裁决内容 | 关联 blocker | 现状 |
|---|---|---|---|
| D1 | FR-PNT-03:Fill 目标优先取 hover,还是高亮跟随 click 命中分区? |
B5 / P0-3 | 未决。若改 click 权威,则删 lastValidHoveredSegmentRef 整条通路,P0-3 与 B4 一并根治 |
| D2 | PRD 待确认 #1:默认大分区(如 seg=0 占 150 万面)上 Fill 的期望语义(整段填充 / 半径降级 / 禁用提示) | B2 / A4 | 未决。不裁决则 A4 字面判据「所填==高亮」按降级路径必挂 |
| D3 | PRD 待确认 #3:A9 验收基线切片软件(OrcaSlicer / BambuStudio / PrusaSlicer 选哪个矩阵) | B6 / A9 | 未决。不裁决 A9 无判据 |
| D4 | PRD 待确认 #5:分区合并/拆分优先级是否高于 P0 修复 | M4 | 未决(路线图误写成「明确排除」,实为待确认) |
| D5 | 手动区豁免统一到「无条件豁免」还是「≤0.95 上限」 | M1-1 | 未决。这是产品决策不是重构 |
同步动作(M3):在裁决/任何代码改动前,先改 README.md 三行错误前提,否则后续 REFUTE 会按错前提取证:
README.md:40「GPU 面拾取:颜色编码 ID 渲染到 RenderTarget」→ 实际 iter4 已换射线拾取(FR-VIEW-07 明令禁止 GPU 回读)README.md:250[x] v0.1 — ... + 3MF 导出→ A9 从未验证,且这是仓库第一个 tagREADME.md:251[x] v0.2 — GPU 拾取→ 同上失真
二、修正后的开发顺序(取代被推翻的候选路线图)
Phase -1 裁决门(D1–D5 + 改 README 三行)— 必须先于任何代码改动,书面结论进 journal
Phase 0 三线并行,全部不依赖 P0 修复
0a 校准仪器 (B1):HUD highlighted 改为与渲染同源取值,增打 renderedHover= / fillTarget= 两字段
0b A9 前置 (B6):补 export::threemf 单测导出 XML 逐条对规范;最小 STL 真机导出送切片软件
0c A6 前置 (B7):150 万面 + Fill 大分区,实测 10 次撤销/重做的内存与 IPC 耗时
EXIT:0a 通过前,任何 Fill 相关读数一律不采信
Phase 1 P0-2 统一闸门(签名至少 (segId, segments, total);同时统一 v7 sticky — fill 侧无对应物)
Phase 2 P0-1 复验(在校准后的仪器上)+ 按 D2 结论重写 A4 判据
Phase 3 P0-3:按 D1 — 若判 click 权威则删 lastValidHoveredSegmentRef(根治);若判保留 hover 优先,先修 B4 永久钉死再做安全阀
Phase 4 A9 收口(0b 已排格式风险,此处做真实模型规模验证)
Phase 5 收口 + 打 tag v0.1.0(注意是仓库第一个 tag)
B3(NSIS 构建)在 journal 已多次成功(低风险),应归入「每次提交必跑」例行项,不与 A9 捆绑(M6)。
三、[blocker] 级发现(必须解决,附 file:line)
B1 — Phase 0 的判据仪器(HUD)与屏幕不同源,六次"决定性证据"全是仪器读数
- HUD highlighted:
Viewport.tsx:1183←facesBySeg.get(targetLbl).length,targetLbl = opts.hoveredSegment ?? clickedLbl(:1182) - 屏幕黄色高亮:
Viewport.tsx:1786条件hoveredSegment !== null && !giantSegmentIds.has(hoveredSegment),渲染源是 store 的hoveredSegment(:1788) - 三重分叉:① HUD 用
targetLbl(含 click 回退 +lastValidHoveredSegmentRef复活值),屏幕用 store 值;② HUD 不过giantSegmentIds,屏幕过;③ hover=null 时屏幕渲染 0 面,HUD 报 clickedLbl 全量。 - 推论:journal 六条
seg=0(click)读数意味着 storehoveredSegment恒为 null →:1786高亮组件根本没渲染 → 屏幕高亮面数=0 不是 1498252;且主诉是"Fill > 高亮"(溢出),HUD 数据12 << 1498252方向相反。七版修复建立在自相矛盾的读数上。 - whole-segment 路径下 HUD 是同义反复:后端返回该 label 全部面,HUD 再拿
facesBySeg.get(同 label)比 → 恒 ✓,测不出"填的与看到的是否一致";陈旧 hover 填错区时给假 ✓。 - 违反 C3「取证优先于修改」。修正:Phase 0 前插 iter31a 校准仪器。
B2 — 闸门统一后 A4 按字面判据仍必挂(隐性 blocker)
点 seg=0(share≈1.0):hover 侧 giant→不高亮→0 面;fill 侧降级 fill_paint(radius)(usePaintTool.ts:146-150)→12 面;12≠0→仍 MISMATCH。降级是 FR-PNT-02 有意设计,闸门统一只让"决策一致"≠"面集一致"。叠加 D2 未决 → "整段填充"语义未定义。
B3 — 顺序与 docs/04 既有结论冲突且两出口都错
docs/04:128 顺序 P0-2→P0-1→P0-3,docs/04:139 明写「P0-2 是 P0-1 结构性成因」。候选路线图 Phase0=验 P0-1、Phase1=P0-2 顺序颠倒且未说明为何推翻。两 EXIT 都失效:收敛只证"这次没降级";不收敛极可能是 B1 假阳性。
B4 — v7 sticky 引入"永久 hover 钉死"活体缺陷
Viewport.tsx:1477let nextHover = prevHover(miss 时不清)Viewport.tsx:1484if (!(prevIsManual && lbl < MANUAL_SEGMENT_OFFSET)) nextHover = lbl— manual 入驻后任何 auto 段永远无法覆写- 唯一清除点:
:1585(pointerleave)与:1328(切套索);fill 优先取 hover(usePaintTool.ts:107) - → 用户 hover 过一次手动区后,只要指针不离开画布,之后在模型任意位置点 Fill 都填那个手动区。Phase 0 回归脚本必须含反例:hover 手动区 A → 移到模型另一端 auto 区 B → 点 Fill,预期填 B,实测填 A。
B5 — P0-3 把治标当治本,根治会撞 PRD 铁律
- fill 取 hover:
usePaintTool.ts:107const label = (hovered != null) ? hovered : (md?.segmentLabels?.[faceId] ?? undefined) - 陈旧 hover 是刻意实现:
Viewport.tsx:1236-1237hoveredSegment: snap ?? lastValidHoveredSegmentRef.current— store 空时主动复活历史值(iter29 v5) - 在此加 TTL 安全阀三重无效:① pointermove 不记时间戳,TTL 无基准;② "面数上限"是第三套闸门,与 P0-2 要统一的两套再次分叉;③ 修不了 B4(钉死的是"新鲜"值)。
- 根治:fill 改以 click 命中面的 segment 为准,删
lastValidHoveredSegmentRef整条通路。但与FR-PNT-03冲突 → 属 PRD 级变更,须先裁决 D1。
B6 — 3MF 不兼容风险源码级已可判定,绝不能等到 Phase 4
src-tauri/src/export/threemf.rs:
:84m:basematerials、:95m:base属核心规范默认命名空间,不属 Materials 扩展(xmlns:m):112("m:basematerials","1")两规范都不存在此属性;核心规范用pid/pindex:149-150三角形pid="1" p1="..."引用的 resource id=1 因上条未注册成功 → 悬空引用<build><item objectid="2"/>(:174-185)存在 → 几何体会显示,颜色极可能整体丢失或报错。- export 模块零单测(docs/04 称"fixture 单测"不准确,实际一个都没有)。修正:A9 前移并行,先补单测打 XML 对规范逐条比。
B7 — A6 从未在目标规模验证,P0-3 前提不成立
- 快照:
Viewport.tsx:1351-1354pushUndo(...)每次 pointerdown 同步执行;150 万面→单次快照 ≈12MB,栈上限appStore.ts:343slice(-19)→20 层→常驻约 240MB - 回滚 IPC:
useTauriCommand.ts:189→ 后端restore_face_colors(segment.rs:272-276),Tauri 把两数组序列化为 JSON 数字数组 → 单次撤销传 600 万+150 万个十进制数;A6 要求 10 次撤销+重做=20 次往返 - P0-3 立论("撤销栈可能不足以完全回滚")依赖 A6,而 A6 本身可能是独立 blocker。A6 必须与 Phase 0 并行验证,用例须 150 万面 + Fill 大分区。
四、[major] 级发现
M1 — 闸门不对称真实存在,但候选方案描述与签名都错
| 维度 | hover giantSegmentIds(Viewport.tsx:1028-1040) |
fill fillWholeSegment(usePaintTool.ts:125-130) |
|---|---|---|
| 人口 | autoSegs = segs.filter(id<100000)(:1033) |
realSegs = segs.filter(faceCount>0)(:122)含手动区 |
| 数量分支 | autoSegs.length<=1→静音全部 auto(:1034-1036) |
realSegs.length>1 才允许整段(:129) |
| 占比阈值 | faceCount > total*0.8(:1038-1039) |
share <= 0.8(:129) |
| 手动区 | id>=100000 无条件豁免(:1033/:1039) |
豁免但受 0.95 上限(:128) |
| 额外闸门 | v7 sticky !(prevIsManual && lbl<100000)(:1484) |
无对应物 |
| 常量可见性 | 仅 WHOLE_SEGMENT_MAX_SHARE、MANUAL_SEGMENT_OFFSET(:7) |
MANUAL_REGION_MAX_SHARE 仅 usePaintTool.ts:16/128 |
- M1-1:手动区豁免两侧语义不同(hover 无条件 / fill ≤0.95)→ 统一到哪侧是产品决策(D5)。
- M1-2:拟议签名
shouldWholeSegmentFill(segId, share)表达不足——autoSegs.length<=1与realSegs.length>1需完整 segments 列表,至少需(segId, segments, total);否则会静默丢掉数量分支制造新不对称。 - M1-3(docs/04-P0-2 未记载的第三处不对称):
autoSegs.length<=1vsrealSegs.length>1。反例:1 auto + 2 manual → hover 静音该 auto(length=1),fill 却允许整段(length=3)→ 填 N 面/高亮 0 面。 - M1-4:REFUTE 目标应改为"同一
(segId, segments, total)输入下返回同一布尔值",非"字节一致"。
M2 — P1-6 应写成条件门,非无条件放 Phase 6
appStore.ts:418 version:1 无 migrate 属实;:435 merge: mergePrefs、:124-126 范围校验、:198 clamp 已在 → FR-PRF-04 满足。真风险:Phase1 若把 0.8/0.95 做成偏好项或 Phase3 安全阀阈值进 partialize,存量用户升级后旧值覆盖新默认 → A7 表面过但行为回退。⚠️ 反论据::110-115 B1 注释明写「不要用 migrate」——仅针对 legacy cym-theme 键,补 migrate 前须在 journal 限定该注释范围(同类 P1-3 陷阱)。条件门:若 v0.1 未改 partialize 8 项默认值→留 Phase6;一旦触碰→升级为发版前必做。
M3 — P2-8 放 Phase 5 让 v0.1 定义自相矛盾且自伤
见 §一 D 同步动作(README:40/:250/:251)。另:CHANGELOG 实际停在 Round 3-4,docs/04:94 记"Round 7-8"清单自身也漂。
M4 — 排除项与 PRD 待确认 #5 冲突,状态标错
路线图写「明确排除 v0.1」把 PRD:198 待确认写成已决。改两栏:「已确认排除:调色板历史 / 批量处理 / OrcaSlicer 插件形态集成」/「待用户确认排除:分区合并/拆分(PRD 待确认 #5)」。
M5 — Phase 1 是同一思路第 8 次,C3 铁律被架空
iter25-30 已 7 轮同思路(追 hoveredSegment 传递链路)变体。必须显式写明"换角度":(i) 放弃 hover 优先改 click 权威(B5);或 (ii) 后端返回本次实际填充面集,前端用同一面集渲染高亮,从根消灭"两个面集"概念。
M6 — Phase 4 把 A9 与 B3 捆绑,失败模式不同
B3(NSIS)已多次成功低风险;A9 零验证零单测源码级已见格式问题。B3 归「每次提交必跑」例行项,A9 单独立项前移。
五、[minor] 级
- m1 行号漂移:闸门实际
Viewport.tsx:1028-1040,只引1033-1039漏掉autoSegs.length<=1早退(:1034-1036),正是 M1-3 所在。 - m2 「P0-1 最终收敛」预设结论,违反 C3 精神,改中性「P0-1 复验」。
- m3
P2-8 README同步同时出现在 Phase5 与 Phase6,职责不清。 - m4 「cargo test 13」须加限定:全在 manual(8)+sdf(5),paint/export/mesh/commands 0 覆盖,当工程护栏会高估强度。
- m5 排除项「OrcaSlicer 插件」与 A9「产物在 OrcaSlicer 打开」措辞近易误读,改「OrcaSlicer 插件形态集成(不影响 A9 导出兼容性验证)」。
六、恢复开发时的下一步(REVISE 阶段)
- 与用户确认 D1–D5 五个裁决项(最可能是一次对话/留言敲定)。
- 按裁决改
README.md三行(M3),提交。 - 派 REFUTE 重做 iter31a 仪器校准方案(B1),确保 HUD 与渲染同源。
- 按 §二顺序逐 Phase 走完 PLAN→REFUTE→REVISE→IMPLEMENT→TEST→RETROSPECT,每轮追加 loop-journal 条目。
- 同步把状态回填到项目「计划」待办(P0-2/P0-3/P0-4 + 新增 A6/A9 前置 + README 校正),断网恢复后再用 wb-issues 工具同步。
附录 A — 被证伪的候选路线图(原始)
定义"可用 v0.1"门槛 = PRD §6.1 A1–A9 全过 + §6.2 B1–B3。
4 个 P0 必须清零:A4(Fill一致性)、A9(3MF E2E)、P0-2(闸门不对称)、P0-3(陈旧hover安全阀)。
Phase 0 验证闸门(iter31):真机回归 iter30 v7 ... EXIT 收敛→A4过 / 不收敛→重新取证
Phase 1 P0-2 统一闸门(iter32):抽纯函数 shouldWholeSegmentFill(segId,share) 供 hover/fill 共用
Phase 2 P0-1 最终收敛(iter33)
Phase 3 P0-3 陈旧 hover 安全阀 + A6 撤销(iter34)
Phase 4 P0-4 3MF E2E(iter35):导出→OrcaSlicer验色 + NSIS安装包
Phase 5 收口:打 tag v0.1.0;校正 README/CHANGELOG/PRD
Phase 6(发版后) P1-3/P1-8/P1-6/P1-7/P2-8
Phase 7(技术债) P1-4/P1-1/2/P2-9/P2-10
明确排除:选区合并/拆分、调色板历史、批量处理、OrcaSlicer 插件