-
方位提示:每个角/棱/面转魔方一套 <X>_CORNER_HINTS(label + 轴 dir,严格照抄该魔方 state 的 CORNER_NAMES/CORNER_AXIS)放 face_hints.ts;world 建 <x>Hints = new FaceHints(SIZE, hints, distanceMul, sizeMul) 加进 scene;SimPage render loop 按 puzzleKind 选 active 集、其余 .hide()、全部 .tick(dt),viewing 三元含该魔方 <x>Rotating;设置面板「字母」(settings.faceLabels)开 → forceLabels 让 active 集常驻(if(viewing||forceLabels)show),新魔方按 puzzleKind 自动生效无需额外接线。
-
多字符标签靠 makeLetterTexture 返回的 aspect 设 sprite.scale.x=size*aspect(否则挤扁);distanceMul 把标签推到实心块外(被挡=不见,Redi 角帽 ~3.7、Dino 开口缝 3.3)。
-
半转停住(holdPartialTurn):SimPage-管理魔方过阈值时若开则 <x>ResolveLive 返 move + 屏幕切向 → beginMove 取 anims → 逐帧 proj/FULL_PX→t∈[0,1] 设 pivot;pointerup 不 commit,存 partialSnapBackRef.current=()=><x>SnapBack(anims);开始新转动前 + toggle off 调 clearPartialFreeze();world.callbacks 注册「丢 ref」(已提交变更已重摆 pivot,只丢不弹);reset() 要 fire callbacks。
-
半转停住(NxN):holdPartial flag,转一层前必 world.controller.clearFrozen();PlayerControls 的 jumpToStep/打乱等所有驱动 cube 入口也先 clearFrozen()(它们不经 controller,锁不自解,否则 group.drag() 死循环卡死)。
-
半转停住(SQ1 两种招式):layer turn 走 sq1Drag(实时拖,sq1DragSnapBack 回弹);slice(/,180° 翻转露垂直内切面)只在开 holdPartialTurn 时改成实时拖——sq1SliceLiveStart(=beginMove({kind:'slice'},dir) 取 anims)+ 逐帧 sq1SliceLiveApply(竖拖 px/SLICE_FLIP_PX(240)→v∈[0,1] 走 applyAnimFrame)+ pointerup 存 sq1SliceLiveSnapBack;关时保持一次性 twist({kind:'slice'})(正常手感不动);触发前 sq1SlashValid(state) 闸 + pinch 打断要回弹。范本见 SimPage SQ1 块。其余引擎要原核露内切面同理:把"露切面的那种招式"做成实时拖+冻结。
-
结构着色(debugStructureColor):applyDebugStructureColors(cube,on) 按 userData.simRole 换材质实例(存 userData.origMat,off 还原);别改共享材质 .color(会串色关不掉);在 applySettings 末尾调(放 NxN hollow setter 后、用 mesh.material !== dbg 捕获原材质)。
-
立体贴片(thickness)+ 镂空(hollow):标对 body/core → 镂空自动有;贴片补 simStickerNormal(makeSticker 魔方)或 simRole='sticker'+simFlatten='scaleZ'(SQ1 式)→ 立体贴片有;机制 applyEngineBodyOverlay + applyStickerThickness,thickness=true 是 no-op(默认零回归)。
-
提示贴片背面(hint):applyHintFacelets(cube,on,bgHex) 给每 sticker 加淡色 BackSide ghost 子节点(随转动自动跟、raycast=()=>{} 不挡拾取);复用立体贴片那套 tag,贴片标对即自动有。
-
挖块(挖角/挖面/挖棱):Cube 实现 setCarve(on)(藏一次转动的会动块组,复用自己的 pivotsForMove/cycle 取 element-0 那组,off 恢复全部)+ simCaps 声明 carve:'corner'|'face'|'edge';所有 engine 魔方都该有,门控走 caps.carve。
-
按阶段展示色块(?stickering=,twizzle Stickering,issue #27):阶段 mask 单一源 engine/nxn/stickering.ts(cubing.js cube-like-stickerings 逐条翻译到 initial 坐标,SOLVED 帧授权颜色随块走,fixture 测试 tests/sim_stickering.test.ts);NxN 渲染走 instanced.setStickering(maskFn) per-slot 改 instance color(dim 必须 sRGB 域 ×0.5,线性域减半看不出),refreshStickerColors 是 applyStick/setFaceColors/hint 共用的色源;megaminx/fto cubing 渲染直接 TwistySection experimentalStickering 透传;下拉 StickeringSelect 在播放条最左,门控 caps.supports.stickering(false=隐藏,非置灰);换阶/换拼图后 SimPage effect 按 [worldTick,puzzleParam,query.stickering] 重挂。
-
原核 coreStyle:'raw'(无贴纸实色块身、棱对角双色/角三色)两套实现:
- NxN(instanced)=
instanced.ts setRawCore(on,faces,coreColor,border) 给 frame(+inner)换克隆几何(per-instance 面色属性)+ rawCore.ts onBeforeCompile shader,全阶(低/中阶 Phong+圆角 frame+inner;超高阶 N≥50 unlit Basic+_FRAME_LOW 无 inner;转层中空由 panelFan.ts 扇形彩色切面填,镜面改用补回的中心块填洞),见 [[project_sim_raw_core_and_logo]]。
- 着色器 lit/unlit 分叉:Phong(lit)用几何法向
vRawNrm=normal 分外壳(dot(gn,面法向)>0.5→沿 localPos 对角分割)/内壁(落 diffuse=coreColor=内核色,转层露深内核);Basic(unlit,超高阶无 normal attr)纯 localPos 最近可见面。「内核色」色块 = setRawCore 设 raw 材质 .color。
- 黑缝粗细必须与「六色/单色」无关:原核外壳面在「面内 chebyshev 距离 > 贴片半宽」处也落回内核色当黑缝(等同六色
_STICKER 内缩),贴片与 frame 同受缩放→逐块等宽。贴片半宽走 define.STICKER_INNER 单一源(Cubelet._STICKER 同源派生),禁再抄边宽常量——同一视觉元素多渲染路径时共享尺寸/颜色必走单一源,别各算一份。
- 镜面:
setRawCore(…,border) 第 4 参控缝色——普通 border:1(独立内核色缝:金/彩块+暗缝)、原核 border:0(缝跟随本体面色,连续无黑线);single 恒 raw;选原核时内核色跟随镜面配色(无中心块也回退镜面色);四量 mirrorRaw/rawOn/rawBorder/rawCoreColor 在 applySettings。
- engine-body 魔方(SQ1/Skewb/Mega/Pyra/…非 instanced)= 共享
engine/rawBody.ts,由 applyEngineBodyOverlay(root,hollow,debug,raw)(body 材质唯一 owner,优先级 raw>debug>hollow>base)驱动:对每个 simRole='body' mesh,从同 parent 的 sticker 兄弟反推有色面(simStickerNormal+cap 色 material[0].color),换 per-piece 原核材质 + 隐藏 sticker。shader 用法向 argmax(dot(objectNormal, faceN) 最大,非 NxN 的 position-based——非中心化圆体/斜挤出体 position 会切错;法向对任意形状稳),per-mesh uniform 喂面表,每材质唯一 customProgramCacheKey(否则 onBeforeCompile 共享 program 只留第一个的 uniform=全体串成一块色)。加新 engine 魔方:用 makeSticker(已自带 simStickerNormal)则原核零代码自动有;非 makeSticker(如 SQ1 local-z extrude)给每 sticker 补 userData.simStickerNormal(body 本地帧外法向,与 body 几何顶点法向同帧)。坑:body mesh 必须相对 piece group identity rotation(否则法向不同帧);pyra 的 PIECE_SHRINK 黑缝/黑星是引擎固有(normal 模式也有),非原核 bug。
-
顶面 logo logo:'none'|'site'|'custom' = NxN 专属:cube.ts setLogo 在 U 中心块顶面贴 plane(logo.ts 缓存 + 上传降采样,仅奇数阶);site 源 = /icons/CubeRoot.png(∛红蓝格透明底,非 favicon.svg 紫色);plane 48;Y = order*32 + (instancedRenderer.thickness?4:1) 跟随立体贴片(平贴片顶面仅 ~0.1,固定高会悬浮);非 NxN 空操作。
-
手指(指法演示,3x3 专属,engine/hands/):同步不挂事件 —— rig 每帧轮询 cube.table.groups[axis][l].angle(tween/拖拽都写它),零 import NxN 引擎;开关 SimSettings.hands → world.setHandsWanted,caps supports.hands = kind===3,手开时 refHalf 3.9。坑:① 左手四元数 = mirrorQuatX(右手) ∘ Rz(π)(几何在局部 y=0 镜像、世界姿态在 x=0 镜像,复合差一个局部 Rz(π));② 指根姿态必须 rootBase ∘ R(curl/splay) 叠加(直写 rotation 会抹掉拇指对掌基座);③ 招式队列同 tick 接续无零活动帧,轴/层类/方向切换都要显式 endGesture,否则上一手 weld 悬死;④ 姿态调优:pose 存「世界轴旋转序列」,Playwright 冻结层角(groups.y[2].angle=0.7 + scene.updateMatrix(),scene 是 matrixAutoUpdate=false)截图校准(headless rAF 不限帧,动画中间帧抓不到);精调用浏览器内坐标下降求解器(目标=指尖胶囊帽圆心落面外一个半径处 + 指腹朝按压向 + 指链采样点不进 96+r 立方包络 + 解剖正则,圆心进面内=肉从贴纸穿出);⑤ 前臂做独立件、肘锚 IK 指向腕点,别焊进手模型(腕转会变整臂绕魔方公转);肘锚 = home 腕点沿手局部 −x 推远(前臂 = 手的自然延长,固定世界锚曾垂直下垂被否);手模 = MANO 独占(2026-07-11 内置 generic-hand 退役,资产逐机 convert-mano.py 转换 gitignored,缺失时手部整体不可用;SMPL-X 真前臂切段同套管线);⑥ 手性硬约束:家位指位(指列朝后、食指在上、掌心朝魔方)决定魔方右侧手必须用 side=-1 几何(side=+1 摆该姿态掌心必朝外,差一个镜像纯旋转无解);⑦ 相机包络:手+前臂几何真包络 ≈5.8×SIZE(腕 166U+前臂 170U+帽 30U;肘锚 6.95×SIZE 处无几何别拿它算),主视图 resize 与 backView 的 near/far handsOn 分支用 max(distance−6×SIZE, 0.4×SIZE) —— near 必须钳正,perspective 滑杆下限 2 时 distance 仅 7.8×SIZE,负 near = 投影矩阵损坏任意角度乱切;手部 layer-1 补光要每个相机 camera.layers.enable(1);⑧ 指弹分三种扫法:拇指(F 族)=展开中节沿 F 面上扫、y 轴=伸展横扫、x/z 轴中指=splay 竖扫 —— 且绕 x 的旋转在 x=0 镜像下不变(M 族整个 B 面同向竖移),竖扫符号必须按世界系竖向逐手换算,镜像对称公式必打架;⑨ 指骨网格挂当前关节系跨 [0,len],别塞进「下一关节」组(它被挪到 x=len → 骨渲染在 [len,1.5len],掌指间一段真空 =「手指与手心断开」;关节数学正确指尖不露馅,极难肉眼定位 —— 判别:三维相连投影必相连,逐项排除 culling/clipping/depth 后清零关节摆直指量网格中心),锁 tests/hands_model.test.ts;⑩ 持久握姿:weld 提交(raw 层角 snap ±90° 倍数非零)烘进 per-hand grip 四元数不回家;解法框换握记号 ↑上手/↓下手/·回 home 各占一步(regrip() 动画 + isRegripping 闸播放;跳步用纯函数 simulateGrips 静态推演)—— 注意 recon 工具链 cleanForPlayer 把 ·↑↓ 当装饰剥掉:分段要在它之前,caret 同步喂原文,invert/mirror/反推打乱先 stripGripMarks。⑪ 皮肤贴图运行时烘焙(bakeHandTexture.ts,UV 光栅化逐像素 3D 域求值):须在 adaptGltfHand 后、入场景/摆 home 前调用(group 无父级);甲背 = 功能指腹方向反向(四指 = 手系 −z 投影⊥末节轴,拇指再绕根段轴滚 THUMB_CURL_PLANE_ROLL×side),禁用末节顶点法线提纯(被指尖前向面拽偏,甲画到指腹侧压在贴纸上隐形);甲色须按指尖顶点血色补偿(顶点色 g×0.89/b×0.86 与贴图相乘,不补则甲片潮红隐形);左右手 UV 布局不一致,两手各烘;锁 tests/hands_texture.test.ts。手势映射锁 tests/hands_gestures.test.ts。
-
背面小窗(backView.ts,NxN/SQ1/recon):相机 target+pivot 取 Box3.setFromObject(world.cube) 中心(先 traverse 清 InstancedMesh.boundingBox 强制逐实例重算,否则拿还原态陈旧盒)+ 纯 (0,0,distance) 偏移脱 pan;别钉原点(打乱质心偏移 / 主视图平移都会让小窗魔方偏位)。
-
小窗中心只在静止时采(commit 回调里同步重采 —— pivots 刚 snap 完;cube 换 / history 长度变失效重采),动画中锁住不逐帧追 AABB(角转类拼图转层单侧扫出,逐帧追=小窗整体漂移);主视图缩放 world.scale 不进小窗 fov(恒按 1,即 refHalfU 标定倍率),只跟 perspective。
-
小窗取景 refHalfU(比主视图 world.resize() 的 refHalf 更紧:主视图宽裕留 margin,170px 小窗里同样 margin 显得「太小」)按 kind 现调:hands 3.9 / pyraminx 2.45 / sq1 3.85 / megaminx 3.3 / fto 3.2 / (dino·redi·rex·heli·gear·skewb) 3.6 / NxN·ivy·mirror 默认 3。判据 = 让 3D 本体填满自己方框的比例 ≈ 左侧 sr-puzzlegen 图像面板(~84–91%),两角小窗视觉等大;Playwright 逐像素 alpha 量填充比标定(量时先关方位字母、量完复原)。新增异形魔方别沿用旧常量 3(实心多面体会溢框、pyraminx 会欠框 ≈65%),按其左图占比现测一个更紧的值填进这条三元链。
-
画布两个浮层同尺寸(--sim-float-size 由 layoutBackView 一次算出落 CSS 变量,.sim-image-overlay / .sim-backview 各取):左上=图像(PuzzleImageStudio 预览经 createPortal 落 imageHost,所有拼图都有),右上=背面小窗。图像浮层右下另有主图↔伴图交换钮(.sim-image-swap,sim.img.swap 持久化):开 = 伴图铺满画布、实时 3D 缩进左上 float-size 方框 —— 引擎路径由 resize 闭包读 imgSwapRef 真调渲染器尺寸(纯 CSS 缩放会废掉 Toucher 射线拾取),twisty 靠自身 ResizeObserver。框是硬编黑白双环(data-board-bg 5 档是定死艺术色不跟站点主题,主题 token 会在某些底色下隐形)。容器 pointer-events:none 让拖拽穿透到画布,仅 .sim-float-close 的 × 自带 auto。显隐入口:引擎小窗 + 图像浮层各自右上角 ×(关)+ 播放条对应角的找回钮(开,仅关着时出现);cubing.js 原生小窗钉不上 ×(区域归 player),播放条钮在 twisty 路径下改成常驻真开关(aria-pressed)—— 侧栏「小窗」toggle、「图像」折叠标题栏都已删,别再加回。
-
示意伴图(sim_svg_export_schematic,SR 范式):新引擎魔方给每张贴纸挂 userData.schematicPoly(理想晶格轮廓:局部系扁平 xyz、绕向朝外、不 inset/lift;缝烘在 scale 里→反烘如 pyra PIECE_SHRINK,缝烘在切割面偏移里→按 0 偏移重算如 skewb;polytope facet 类直接 stickerGeom.schematicPolyFromFacet;贴纸 mesh 会被压扁/带自身变换的挂 parent frame + schematicInParent 如 sq1);NxN instanced 走 mesh 级 schematicInstancedPoly(±HALF 整格 quad,mirror 时摘除)。挂了 = 伴图自动切示意导出器 + 黑边滑块出现;图像面板对所有拼图常在(2026-07-21 起无 imageStudio 注册项):spec 渲染器不认识的自动走 engineOnly 模式(实时矢量预览 + 截图组 + SVG/PNG,无 API 链接),连没引擎 world 的 twisty 拼图(PG 目录 / custom / cubing.js 渲染)也有伴图 —— TwistyPlayer vantage → exportSimSvg painter 镜像,零新增接线。测试范本 tests/sim_svg_export_schematic.test.ts(严格多边形数 + 晶格去重数 toBe 锁死;镜像 det 翻转各有用例)。网格是 visualcube inset 模型(贴纸向心缩 + 壳色衬底,滑块 = 缝宽占比),不是描边 —— 绝对 px 描边高阶会吞贴纸。
-
mask 直映(有 canonical id 空间的拼图):贴纸再挂 userData.stickerKey(建构标识如 edge1-2:3,复原帧、跟块走)→ tests/_engine_mask_derive.ts 加共轭派生(引擎置换从场景几何读:层内晶格质心绕轴转、落槽匹配)→ 重生成 lib/puzzle-image/data/engine-sid-map.json + engine-mask.test.ts 锁表 → SimPage 自动把遮罩烙进伴图镜像。已通:pyra/skewb/mega;NxN 要 posit/instance 身份考证,sq1 缺 id 空间。