topfans/docs/superpowers/specs/2026-07-06-remove-hardware-gyro-design.md
2026-07-06 21:20:24 +08:00

402 lines
25 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.

# 移除光栅卡硬件陀螺仪 · 设计方案
> **状态**:待审核 · **创建日期**2026-07-06 · **目标分支**feat/actvity
>
> **本方案替代 2026-06-30 引入的"加速度计 + One-Euro 滤波"方案**commit `4eab42b`)。旧 spec/plan 保留作为历史演进记录。
---
## 方案概述(*必读*
### 要解决的问题
**业务问题**
- 铸爱光栅卡H5 + App的倾斜预览在 App 端需要硬件陀螺仪/加速度计权限UX 摩擦大:
- iOS 13+ Safari / WKWebView 弹出 `requestPermission`,用户拒绝即不可用
- Android 12+ 强制 `BODY_SENSORS` 权限,应用商店审核严格
- 部分低端 Android 机陀螺仪硬件缺失,只能降级
- 户外/静止场景下用户无法(或不想)倾斜手机预览,缺少替代交互
**技术问题**
- 硬件传感器路径有 3 套Native.js Android / Native.js iOS / DeviceOrientationEvent代码 ~28KB`useLenticularStudioTilt.js`
- 4 天前刚把陀螺仪改为加速度计(`4eab42b`),整套 One-Euro 滤波、跳变拒绝、快慢双通道、预热跳过逻辑投入大、收益不明
- 原生插件 `imengyu-UniAndroidGyro`(含 AAR + iOS Framework成为单点依赖
- App 端倾斜效果与触摸拖拽效果**已经并存**`LenticularCard.vue` 的 `touchstart/move/end` 事件一直在 emit `simulate`),铸爱页(`lenticular-result`)却用 `skipBuiltInTouch=true` 屏蔽触摸、强制走硬件路径
### 整体实现路径
| Phase | 内容 | 工作量 |
|-------|------|--------|
| Phase 1 | 在 `useLenticularCraftTiltPreview` 新增 `simulateTilt / simulateTiltFromNormalized / simulateTiltReset` 公共方法(保留旧路径并行) | 0.5 天 |
| Phase 2 | 新增 `<ManualTiltSlider>` 独立组件 | 0.5 天 |
| Phase 3 | `<LenticularCard>` / `<HolographicCard>``showSlider / sliderValue` prop + `update:sliderValue / sliderReset` emitv-model 配套),引入 `<ManualTiltSlider>` | 0.5 天 |
| Phase 4 | `lenticular-result.vue` 切换:改触摸接收器、加滑块、删 3 处 `scheduleTiltStart/stopTiltPreview` 调用 | 0.5 天 |
| Phase 5 | 清理收尾:删 `useLenticularStudioTilt.js`、`useOneEuroFilter.js`、原生插件目录、manifest 注册段、权限与隐私描述 | 0.5 天 |
| QA | 4.3 验收清单iOS 真机 / Android 真机 / H5 Chrome | 1 天 |
| **合计** | | **~3.5 天** |
### 关键决策
1. **彻底移除硬件传感器代码**(不保留"硬件 / 手动"切换开关)— 决策依据CLAUDE.md MVP 先行原则,"应急开关"属于 YAGNI用户已确认手动模拟可以替代硬件方案
2. **滑块做成独立组件** `<ManualTiltSlider>`,与卡片解耦 — 便于未来页面级复用
3. **触摸拖拽路径保留**`LenticularCard` / `HolographicCard` 已有),与滑块**不互斥**,数据驱动
4. **离散档位逻辑保留**(铸爱页 2 层卡硬切效果不动)— 与硬件源无关
5. **删除原生插件目录** `frontend/nativePlugins/imengyu-UniAndroidGyro/` — 这是物理上解除单点依赖
6. **iOS `NSMotionUsageDescription` 同步删**(在 `manifest.json`)— 不删会导致应用商店审核仍有"访问运动传感器"声明
7. **历史设计文档保留**2026-06-30 accel-oneeuro design/plan— 作为设计演进记录,不删
### 核心架构图
```
┌────────────────────────────────────────────────────────────┐
│ 手动输入(双源) │
│ ┌────────────────────┐ ┌────────────────────────┐ │
│ │ LenticularCard.vue │ │ <ManualTiltSlider/> │ │
│ │ (触摸拖拽) │ │ (滑块 0..1) │ │
│ └─────────┬──────────┘ └───────────┬────────────┘ │
│ │ emit('simulate', x, y) │ change: v │
│ │ 归一化 -1..+1 │ 归一化 0..1 │
└────────────┼─────────────────────────────┼────────────────┘
▼ ▼
┌─────────────────────────────────────────────────────┐
│ useLenticularCraftTiltPreview │
│ │
│ simulateTiltFromNormalized(x, y) // 触摸入口 │
│ simulateTilt(dx, dy) // 滑块入口(度) │
│ simulateFromSignedDegrees(dx, dy) // 内部(度) │
│ simulate(gamma, beta) // 内部(-1..+1) │
│ ↓ │
│ 离散档位映射 (TILT_ACTIVATE/DEACTIVATE 滞回) │
│ ↓ │
│ useLenticularPreview 渲染循环 │
│ ↓ │
│ engine.feedSimulatedTilt() → layerTransforms │
└─────────────────────────────────────────────────────┘
<LenticularCard :transforms />
<HolographicCard :viewAngle />
```
---
## 文档说明
- **适用范围**:前端 uni-app 铸爱光栅卡模块(含全息卡);不影响后端、不影响其他业务
- **前置版本/历史**
- `4eab42b` (2026-07-01) 引入加速度计 + One-Euro 滤波方案
- `2855cd5` 之前使用陀螺仪
- `10168f8` AI 生成镭射卡功能
- `889c923` 镭射新链路
- **工作量估算**:约 3.5 天(开发 2.5 天 + QA 1 天)
- **目标读者**铸爱光栅卡模块的开发者、iOS/Android 打包配置维护者、QA 验收人
---
## 1. 架构与数据流
参见"核心架构图"小节。
### 数据流要点
1. **彻底删除** `useLenticularStudioTilt.js`28KB / 894 行)+ `useOneEuroFilter.js`~75 行)
2. **API 统一入口**`useLenticularCraftTiltPreview` 暴露 `simulateTilt(dx, dy)`(度)作为唯一倾斜入口
3. **触摸路径**:保留 `LenticularCard.vue` / `HolographicCard.vue` 的触摸事件,把 emit 转发到 `simulateTiltFromNormalized`(在 `useLenticularCraftTiltPreview` 内部做归一化 → 度数转换,转换系数 **45°/单位**
4. **滑块路径**:页面级 `<LenticularCard show-slider v-model:slider-value="tiltValue" @slider-reset="tiltValue = 0.5" />`(卡片内部嵌入 `<ManualTiltSlider>`所有权归调用方0.5=中位 → 0°
5. **铸爱页lenticular-result**:去掉 `skipBuiltInTouch=true` 改为默认false去掉 `scheduleTiltStart` 调用(无传感器要启动了)
---
## 2. 文件变更清单
### 2.1 删除3 项)
| 文件 / 目录 | 大小 | 删除理由 |
|------|------|---------|
| `frontend/composables/useLenticularStudioTilt.js` | 28KB / 894 行 | 整体硬件传感器模块彻底无用 |
| `frontend/composables/useOneEuroFilter.js` | ~75 行 | 仅 `StudioTilt` 一处使用grep 验证:仅 1 处 import |
| `frontend/nativePlugins/imengyu-UniAndroidGyro/` | 含 AAR + iOS Framework + package.json | 原生插件无调用方后即可整体删除 |
### 2.2 新增1 项)
| 文件 | 说明 |
|------|------|
| `frontend/components/lenticular/ManualTiltSlider.vue` | uniapp `<slider>` 包装组件,双向绑定 0..1,含复位按钮 |
### 2.3 修改8 个文件)
| 文件 | 关键变更 |
|------|---------|
| `frontend/composables/useLenticularCraftTiltPreview.js` | ① 删 `useLenticularStudioTilt` 引用和 `scheduleTiltStart/stopTiltPreview` ② 新增公共方法 `simulateTilt(dx, dy)`(度)③ 新增 `simulateTiltFromNormalized(x, y)`(适配触摸)④ `lockPreviewStill` 保留 |
| `frontend/composables/useLenticularPreview.js` | 改动最小:注释更新(移除"接受 simulate() 注入"的隐含承诺),内部逻辑不动 |
| `frontend/composables/useHolographicPreview.js` | 删 `startGyro/startDeviceOrientation` 等所有 `DeviceOrientationEvent` 路径;保留 `simulate(x, y)` 作为唯一入口;移除 OneEuro 引用 |
| `frontend/components/lenticular/LenticularCard.vue` | ① 新增 prop `showSlider: Boolean`,默认 `false` ② 新增 prop `sliderValue: Number`0..1,双向)③ 模板内 `v-if="showSlider"` 渲染 `<ManualTiltSlider>` ④ 触摸开始/移动 emit `simulate(x, y)` 不变;**触摸结束**改为**不再 emit `(0,0)`**(与滑块数据驱动相容,见 4.1 |
| `frontend/components/lenticular/HolographicCard.vue` | 同上prop 一致;触摸结束同样**不再 emit `(0,0)`** |
| `frontend/components/lenticular/CastloveLenticularPreview.vue` | 加 `show-slider` prop 透传 |
| `frontend/pages/castlove/lenticular/lenticular-result.vue` | ① 删 `scheduleTiltStart/stopTiltPreview` 调用3 处)② 删 `:skip-built-in-touch="true"`,改用触摸 emit ③ 加 `show-slider` prop ④ `@simulate` 改为 `(x, y) => simulateTiltFromNormalized(x, y)` |
| `frontend/manifest.json` | ① 删 `app-plus.nativePlugins.imengyu-UniAndroidGyro`30-44 行)② 删 `BODY_SENSORS` 权限69 行)③ 删 `NSMotionUsageDescription` 隐私描述82 行) |
| 构建设置 | 无iOS Info.plist 由打包机自动生成,修改 `manifest.json` 即可生效) |
### 2.4 不修改但需回归验证
- `frontend/pages/castlove/lenticular/lenticular-create.vue`:不挂载 LenticularCard 实际渲染,无需改
- `frontend/pages/castlove/lenticular/lenticular-thinking.vue`:同上
- `frontend/utils/lenticular-engine.js`:渲染逻辑与输入源无关,无需改
---
## 3. API 设计
### 3.1 `useLenticularCraftTiltPreview(layersRef)`
**返回对象**
| 字段 | 类型 | 状态 | 说明 |
|------|------|------|------|
| `physics` | `Reactive<PhysicsConfig>` | 保留 | 物理参数 |
| `layerTransforms` | `Ref<Object>` | 保留 | 渲染输出(直接喂给 `<LenticularCard :transforms>` |
| `simulate(gamma, beta)` | `(number, number) => void` | 保留 | 内部用,归一化输入 |
| `relax(factor)` | `(number=0.85) => void` | 保留 | 平滑归零 |
| `lockPreviewStill()` | `() => void` | 保留 | 强制回中(清空离散档位状态 `lastLayerIdx` / `snapCooldownUntil` / `dirSign` / `dirOppositeStreak` + `simulate(0, 0)`)。使用场景:进入页面初始化 / 用户主动点"归位"按钮 / 切换卡类型时。**不再**由 `scheduleTiltStart` 触发Phase 4 之后该调用一并删除)。 |
| `simulateTilt(dx, dy)` | `(number, number) => void` | **新增** | 滑块/外部入口,单位**度** |
| `simulateTiltFromNormalized(x, y)` | `(number, number) => void` | **新增** | 触摸入口,-1..+1 转 °(系数 45° |
| `simulateTiltReset()` | `() => void` | **新增** | 滑块点"复位"时调用 |
| `gyroSourceLabel` | `Ref<string>` | **删除** | 不再有硬件源 |
| `scheduleTiltStart` | `() => void` | **删除** | 不再启动传感器 |
| `stopTiltPreview` | `() => void` | **删除** | 同上 |
**内部状态**`lastLayerIdx`、`snapCooldownUntil`、`dirSign`、`dirOppositeStreak` 全保留(离散档位逻辑不动)。
### 3.2 `useHolographicPreview()`
| 字段 | 类型 | 状态 |
|------|------|------|
| `physics` | `Reactive` | 保留 |
| `viewAngle` | `Reactive{x,y,z}` | 保留 |
| `isWebGLReady`, `hasWebGLError`, `fps` | `Ref` | 保留 |
| `simulate(x, y)` | `(number, number) => void` | 保留(成为唯一驱动入口) |
| `relax(factor)` | 保留 |
| `onWebGLReady/onWebGLError/onFPSUpdate` | 保留 |
| `startGyro/stopGyro` | **删除**(设备方向监听整套删) |
| `gyroSource` | **删除** |
| `accelHandler`, `accelBaselineReady`, `accelBaseX/Y` | **删除**(基线追踪是 DeviceOrientationEvent 配套) |
| `detectPerformanceTier`, `HOLO_PERFORMANCE_PRESETS` | 保留(独立工具函数) |
### 3.3 `<LenticularCard>` / `<HolographicCard>` 新增 props
```ts
defineProps({
// ... 既有 props ...
skipBuiltInTouch: { type: Boolean, default: false }, // 保留
showSlider: { type: Boolean, default: false }, // 新增:是否显示内置滑块
sliderValue: { type: Number, default: 0.5 }, // 新增v-model:sliderValue 双向绑定
})
const emit = defineEmits([
'simulate', // 保留:触摸拖拽 emit (x, y) -1..+1
'update:sliderValue', // 新增v-model 配套v-model:sliderValue 双向绑定)
'sliderReset', // 新增:滑块复位按钮 emit ()
])
```
**emit 转发逻辑**(卡片内部):
```vue
<ManualTiltSlider
v-if="showSlider"
:value="sliderValue"
@change="v => emit('update:sliderValue', v)"
@reset="emit('sliderReset')"
/>
```
- `gyroSource` prop 保留但写死为 `'simulation'`(不删 prop 以避免破坏外部传参,向后兼容,**仅作为显示字段**,不参与业务逻辑)
- **滑块所有权归调用方**:调用方使用 `v-model:sliderValue="tiltValue"` 双向绑定,滑块拖动时自动更新 `tiltValue`;调用方再将 `tiltValue` 通过 `simulateTilt` 喂入 composable
- `sliderReset` emit 后,调用方负责将 `tiltValue` 重置为 0.5
### 3.4 `<ManualTiltSlider>` 新组件 API
```ts
// 文件frontend/components/lenticular/ManualTiltSlider.vue
defineProps({
value: { type: Number, default: 0.5, validator: v => v >= 0 && v <= 1 },
disabled: { type: Boolean, default: false },
showReset: { type: Boolean, default: true },
min: { type: Number, default: 0 },
max: { type: Number, default: 1 },
step: { type: Number, default: 0.01 },
})
const emit = defineEmits<{
change: [v: number] // 用户拖动
reset: [] // 点复位
}>()
```
- 视觉:卡片下方 32rpx 高度的细条 + 居中圆点 thumb + 右侧小图标"↺"
- 平台:使用 `<slider>` uniapp 组件App/H5/小程序都支持,原生渲染,跟手)
### 3.5 数值约定一致性
| 上下文 | 范围 | 备注 |
|--------|------|------|
| 触摸 emit | 实际 [-1.1, +1.1],有效 [-1, +1] | 现有 `LenticularCard.dispatch()` 末尾 `* 1.1` 系数放大line 183-184允许轻微超出模拟路径中由 3.5 公式截断到 [-45°, +45°] |
| 滑块 value | 0..1 | 中位 0.5 = 0° |
| `simulateTilt(dx, dy)` | -45..+45 度 | 物理量纲,与旧 `simulateFromSignedDegrees` 一致(推导:触摸 x∈[-1,+1]×45° 与滑块 v∈[0,1]×(±45°) |
| `simulate(gamma, beta)` | -1..+1 | 引擎层归一化 |
| 滑块 → 度 | 公式 | `dx = (v - 0.5) * 90`v ∈ [0,1] → dx ∈ [-45°, +45°] |
| 触摸 → 度 | 公式 | `dx = x * 45`x ∈ [-1, +1] → dx ∈ [-45°, +45°] |
---
## 4. UI + 错误处理 + 测试
### 4.1 滑块 UI 形态
```
┌────────────────────────────────────────────────┐
│ │
│ ┌──────────────────┐ │
│ │ │ │
│ │ LenticularCard │ ← 触摸区 │
│ │ │ (emit simulate) │
│ │ │ │
│ └──────────────────┘ │
│ │
│ ↺ [────●──────────] │
│ 0 0.5 1 │
└────────────────────────────────────────────────┘
```
| 元素 | 规格 |
|------|------|
| 位置 | 卡片下方 16rpx 间距,水平居中 |
| 高度 | 48rpx |
| 滑块本体 | `<slider>` uniapp 组件min=0 / max=1 / step=0.01 |
| Thumb | 圆点 24rpx 直径,主题色高亮 |
| 复位按钮 | 左侧 `↺` 图标 32rpx点击 emit `sliderReset` |
| 颜色变量 | 与 LenticularCard 主题色一致(透传 prop 或写死 #6E7AFF |
**两种输入源的优先级规则**
- 触摸拖拽 + 滑块**不互斥**:都向同一 `simulateTilt*` 入口打,**最后一次操作**生效
- 触摸结束 emit `(0, 0)`,会盖掉滑块当前位置 → 这是 bug
- **修复方案**:触摸结束 emit 改为不发送 `(0,0)`,改由调用方决定是否 `relax(0.86)`,或 `lockPreviewStill()`
- 滑块拖动时也类似:**不**在拖动结束时 emit reset**完全由数据驱动**(滑块 0.5 = 中位 = 不倾斜)
### 4.2 错误处理 / 边界场景
| 场景 | 处理 |
|------|------|
| `skipBuiltInTouch=true` + `showSlider=true` | 都允许,触摸被屏蔽,滑块生效 |
| `skipBuiltInTouch=false` + 触摸与滑块同时操作 | 最后一次事件胜出,无须加锁 |
| 触摸事件 `e.touches` 为空(`changedTouches` 也为空) | 现有代码已用 `if (!touch) return` 兜底,保留 |
| 滑块 value 超出 0..1(外部 bug | 组件 prop validator 拦截console.warn |
| `simulateTilt` 收到 `NaN`/`Infinity` | 入口处 `Number.isFinite()` 检查,否则 `console.warn` 丢弃 |
| `layersRef` 长度为 0 或 1 | 离散档位逻辑已有 `if (count < 2) return` 兜底,保留 |
| App 端 WebView 触摸延迟 | uniapp `<slider>` 原生组件,跟手性 OK触摸事件延迟是 uniapp 通用问题,沿用现状 |
| 旧硬件传感器代码残留 | `useLenticularStudioTilt.js` 整体删除,无残留风险 |
| **Phase 5 漏删** `BODY_SENSORS` 权限 | §2.3 已列入修改清单QA 用附录 grep 命令兜底校验 |
| **Phase 5 漏删** iOS `NSMotionUsageDescription` | §2.3 已列入修改清单QA 用附录 grep 命令兜底校验 |
### 4.3 测试策略
**前端测试原则**(来自 memory `share-impl-test-policy.md`):前端不写 Vitest**走手动验收 + 后端接口测试**。
| 类型 | 覆盖范围 | 方法 |
|------|---------|------|
| 后端接口测试 | 不涉及本次(无后端变更) | — |
| 前端手动验收 | 所有修改页面iOS 真机 / Android 真机 / H5 Chrome | 见下方验收清单 |
| 单元验证 | `useLenticularCraftTiltPreview``simulateTilt` / `simulateTiltFromNormalized` / 离散档位逻辑 | 在 DevTools console 用 `console.log` 直接调用验证(不再单独写测试文件) |
| 平台覆盖 | iOS 真机、Android 真机、H5 Chrome | QA 走查 |
**手动验收清单(每平台各一遍)**
1. 打开 `lenticular-result` 页面,确认卡片显示正常
2. **触摸拖拽**:手指在卡片上左右拖动,卡片光栅效果跟随;松开手指不强制回中(验证 4.1 修复)
3. **滑块拖动**:拖动滑块 0 → 1卡片光栅从最左切到最右铸爱页 2 层卡应硬切档位)
4. **复位按钮**:点击 ↺,滑块回 0.5,卡片回中间
5. **组合**:拖动滑块到 0.3,然后触摸卡片到最右 → 触摸胜出
6. `manifest.json``BODY_SENSORS` / `NSMotionUsageDescription` / `nativePlugins.imengyu-UniAndroidGyro` 已删除grep 验证)
7. 旧 iOS 用户授权提示**不再弹出**(验证 DeviceOrientationEvent 已删干净)
8. Android 真机扫描APP 启动时**不再**申请 BODY_SENSORSadb logcat | grep SENSORS
9. `lenticular-create` / `lenticular-thinking` 页面**无回归**(这两个页面无 LenticularCard 实际渲染,但要确认不报错)
---
## 5. 实施顺序 + 风险 + 回退
### 5.1 实施顺序5 个阶段,每阶段独立可回退)
| Phase | 内容 | 涉及文件 | 风险 | 验证 |
|-------|------|---------|------|------|
| **1. 底层 API** | 在 `useLenticularCraftTiltPreview` 新增 `simulateTilt` / `simulateTiltFromNormalized` / `simulateTiltReset`**保留**旧 `scheduleTiltStart` 等方法(先并行不删) | 1 文件 | 极低(纯新增) | 单元调用验证 |
| **2. 新组件** | 新增 `ManualTiltSlider.vue`,独立可单测 | 1 新文件 | 极低 | H5 Chrome 单独挂载测 |
| **3. 卡片接入** | `LenticularCard` / `HolographicCard``showSlider/sliderValue` prop + `update:sliderValue/sliderReset` emitv-model 配套),引入 `<ManualTiltSlider>` | 2 文件 | 中prop 增减可能影响外部调用方) | lenticular-result 页面挂载验证 |
| **4. 页面切换** | `lenticular-result.vue`:① 改 `@simulate` 接收器 ② 加 `show-slider` ③ 删 `scheduleTiltStart` 等 3 处调用 | 1 文件 | 中 | 触摸 + 滑块组合验收 |
| **5. 清理收尾** | ① 删 `useLenticularStudioTilt.js` ② 删 `useOneEuroFilter.js` ③ 删原生插件目录 `frontend/nativePlugins/imengyu-UniAndroidGyro/` ④ 删 `manifest.json``nativePlugins` 注册段、`BODY_SENSORS`、`NSMotionUsageDescription` ⑤ PR 描述提示 | 3 文件 + 1 目录 | 中(删除 28KB + 改 manifest | grep 残留 + 真机无传感器调用 |
**关键不变量**Phase 1-4 完成后**仍然可回退**到当前状态(旧 `scheduleTiltStart` 路径保留运行);只有 Phase 5 是真正不可逆。
### 5.2 风险点
| 风险 | 等级 | 缓解措施 |
|------|------|---------|
| `useLenticularStudioTilt.js` 删除后存在隐藏引用 | **高** | Phase 5 之前全局 grep `useLenticularStudioTilt` / `useOneEuroFilter` / `BODY_SENSORS` / `NSMotionUsageDescription` / `imengyu-UniAndroidGyro` 必须 0 命中CI 加 grep 校验 |
| 触摸结束 emit `(0,0)` 导致与滑块状态打架 | **中** | 已在 4.1 给出修复方案(触摸结束不强制发 0,0由数据驱动 |
| `HolographicCard.vue` 当前无页面引用 | 低 | 按"全套都改"原则同步清理,**不单独删除**该组件(可能未来用) |
| 4eab42b 新增的两个 accel-oneeuro 文档变成历史 | 低 | **保留**不删(作为设计演进记录),但在新 spec 开头加"本方案替代 2026-06-30 accel-oneeuro 方案"说明 |
| iOS `NSMotionUsageDescription` 不在仓库 | 中 | PR 描述 / iOS 开发者文档显式提示 |
| Android `BODY_SENSORS` 权限是 4eab42b 才加的 | 低 | manifest 改动是单点git blame 清晰 |
| 触屏 + 滑块**同时**操作的边界 | 中 | 设计上接受"最后一次胜出"无锁4.2 已说明 |
| 真机回归范围 | 中 | iOS/Android/H5 三平台均需过 4.3 验收清单 |
| 设计师未确认滑块视觉 | 低 | 4.1 已给出 ASCII mock + 规格UI 沿用主题色即可 |
| 滑块 emit 命名(`update:sliderValue`)与 `simulate` 风格不同 | 极低 | 滑块用 Vue 3 标准 v-model 模式(`update:sliderValue`),与现有 `simulate(x, y)` 风格不同但符合 Vue 约定§3.3 命名表已标注 |
### 5.3 兼容回退方案
| 阶段 | 回退方式 | 粒度 |
|------|---------|------|
| Phase 1 出问题 | revert 单 commit | 1 文件 |
| Phase 2 出问题 | 删除新文件 | 1 文件 |
| Phase 3 出问题 | revert prop 改动 | 2 文件 |
| Phase 4 出问题 | revert 页面改动 | 1 文件 |
| Phase 5 出问题 | revert 删除 + manifest | 3 文件 + 需回补权限字符串 + 恢复插件目录 |
| **整体回退** | `git revert <merge-commit>` | 一次到位 |
**应急开关**(可选,**不实施**除非 QA 需要):
- 保留一个 `LENTICULAR_USE_HARDWARE_GYRO` 全局开关,默认 `false`
- 如果线上发现严重问题,远程打开临时回退到旧路径
- 决定:**不实现**。理由CLAUDE.md MVP 先行原则 + 28KB 代码就为"应急"留着属于 YAGNI。Phase 1-4 并行保留旧路径已经足够
### 5.4 范围确认
| 维度 | 范围 |
|------|------|
| **包含** | 5 个 composables删 2 改 3删 useLenticularStudioTilt + useOneEuroFilter改 useLenticularCraftTiltPreview + useLenticularPreview + useHolographicPreview+ 1 新组件 + 1 原生插件目录(删)+ 2 卡片组件LenticularCard + HolographicCard+ 1 包装预览组件CastloveLenticularPreview+ 1 页面 + manifest |
| **包含** | 1 个新 spec 文档 `docs/superpowers/specs/2026-07-06-remove-hardware-gyro-design.md`(本文件) |
| **不包含** | 后端代码(无后端变更) |
| **不包含** | `lenticular-create.vue` / `lenticular-thinking.vue`(无 LenticularCard 实际渲染) |
| **不包含** | 删除 4eab42b 的 accel-oneeuro 设计/实施文档(保留为历史记录) |
| **不包含** | 重构 LenticularEngine 渲染逻辑(本次不改) |
| **不包含** | "未来接回硬件"的接口预留YAGNI |
### 5.5 文档同步
- **新增**`docs/superpowers/specs/2026-07-06-remove-hardware-gyro-design.md`(本文件)
- **保留**`docs/superpowers/specs/2026-06-30-lenticular-android-accel-oneeuro-design.md`(历史)
- **保留**`docs/superpowers/plans/2026-06-30-android-accel-oneeuro-implementation.md`(历史)
- **保留**`docs/superpowers/specs/2026-06-26-lenticular-webgl-engine-design.md`WebGL 引擎与倾斜源无关)
- **保留**`docs/superpowers/specs/2026-05-22-lenticular-gyro-optimization-design.md`(原始陀螺仪方案)
---
## 附grep 校验清单Phase 5 前必跑)
```bash
# 应全部 0 命中
grep -rn "useLenticularStudioTilt" frontend/
grep -rn "useOneEuroFilter" frontend/
grep -rn "imengyu-UniAndroidGyro" frontend/
grep -rn "BODY_SENSORS" frontend/
grep -rn "NSMotionUsageDescription" frontend/
grep -rn "imengyu" frontend/
```