# 实验五 · AI 协作记录（ai-log.md）

> 说明：以下三项按“先自己预测 → 再问 AI → 自己选测试 → 记录实际结果 → 改正结论”的顺序完成。
> 所有数值均来自本机实测（Chromium，WebGL2），不是凭肉眼判断；完整数据见 `README.md` 第 4 节。
> AI 负责给假设、给最小测试和讨论方案；**最终选择测试、判断结果、修改代码由本人完成**。

---

## 协作 1 · 概念核验（3 分，对应 B / C）

### 1.1 我先写下的预测（提问之前）

| 改变哪一项 | 画面 | 模型世界中心读数 | 判断依据 |
|---|---|---|---|
| 只改 M（按 D 平移模型） | 物体移动 | 应该变化（0 → 0.1） | 世界坐标被 M 改了 |
| 只改 V（切换观察预设） | 方向改变 | 应该**不变**（0.00） | 相机换了，物体没动 |
| 只改 P（正交 ↔ 透视） | 近大远小关系改变 | 应该不变 | 只换投影，M/V 原样 |
| 只改 FOV | 物体在画面中占比变化 | 不变 | 属于 P，不是 Model 缩放 |

### 1.2 问 AI 的提示词（按指导书 8.1 改写）

> 在 WebGL2 右手坐标系、列向量和 P·V·M 的顺序下，我想比较“移动物体”与“移动相机”、“正交”与“透视”。请给我三组能区分原因的控制变量实验，指出每组应改变 M/V/P 中的哪一项，说明可以打印哪些数值进行核验；不要直接生成整套代码。

### 1.3 AI 给出的建议（摘录）

1. **M 组**：保持 eye/target 与投影不动，只让 `tx` 增加 0.1；打印 `stat-world`、`stat-camera`，并直接读 `state.tx`。若世界中心变了而 eye 不变，就是 M 的作用。
2. **V 组**：保持 M 全零（tx/ty/tz/rx/ry/scale），依次切换正/侧/顶/斜上四个 eye；打印 eye、target 与 `stat-world`。世界中心必须恒为原点，变化只发生在 V。
3. **P 组**：固定同一个 M、V 与两个等尺寸、不同深度的立方体；分别用正交 `ortho(-w/2,w/2,-h/2,h/2,near,far)` 与透视 `perspective(fov,aspect,near,far)`，把两立方体 8 个角的**裁剪坐标**算出来再除以 w，比较 NDC 宽度比。另建议打印 `aspect = canvas.width/canvas.height`，因为它进 P 不进 M。
4. AI 还提示：FOV 变化会同时改变所有深度的成像，宽度比 `近/远` 保持不变；Model 缩放虽然也让物体变大，但世界坐标与深度关系一起变，所以两者不能等价。

### 1.4 我实际选择的测试与结果

| 组 | 我做的操作 | 实际数值 | 是否符合预测 |
|---|---|---|---|
| M | 按 D 键一次 | `tx 0 → 0.1`，eye 仍为 `(0,1.8,8.5)`，世界中心 x 变为 0.10 | 是 |
| V | 依次点正面 / 侧面 / 顶部 / 斜上方 | eye 四组变化，`stat-world` 四次均为 `(0.00, 0.00, 0.00)` | 是 |
| P | 透视 50° ↔ 正交（同 M、同 V） | 近/远立方体 NDC 宽度比：透视 **1.332**，正交 **1.000**；切换后 `tx/ry/ex/ez` 仍为 0/0/0/8.5 | 是 |
| P（FOV） | 透视 50° → 80° | 近立方体宽度 0.1021 → 0.0567，远立方体 0.0766 → 0.0426，**比值仍是 1.332**；世界中心不变 | 是 |

补充核验：`near=8` 时模型前缘 `(0,0,1)` 的 `z_ndc = -1.145 < -1`，被近平面裁掉，但对象仍存在于世界中（把 near 调回 0.1 立即重新出现）——这不是“删除对象”。

### 1.5 我的结论（人工判断）

- “画面变了”至少对应 M、V、P 三种完全不同的原因，**必须用状态读数 + 控制变量**区分，不能只看图。
- AI 建议里“打印 NDC 宽度比”最有用：正交的比值恒为 1，透视的比值等于两物体到眼点距离之比（实测 11.5/8.5 ≈ 1.35，与 1.332 同量级，差异来自离轴位置的透视形变）。
- FOV 与 Model 缩放都对“表观大小”有影响，但 FOV 不改变世界坐标、不改变近远比值；Model 缩放改变世界坐标且不改变近远比值——两者机制不同，不能互相替代。

---

## 协作 2 · 真实故障诊断（4 分，对应 C / D）

### 故障一：Task E 页面空白，状态栏停留在“正在初始化模型…”

**Console 现象**：`Uncaught SyntaxError: Unexpected token ')'`（task-e.js），随后 `CG5App is not defined`。

**我先提出的三种假设**

1. 整个 `task-e.js` 的 IIFE 存在语法错误，导致里面的 `ex.extra` 和页面启动代码都没执行；
2. `CG5Start("e")` 内部抛错（例如 WebGL2 上下文创建失败）；
3. `ui.js` 生成的控件缺失，`app.js` 取不到 `model-select` 而抛错。

**每项最小测试**

1. 在 Console 执行 `Object.keys(CG5Exercises)`：结果只有 `normalize/eyePreset/view/projection/drag/wheel/key`，**没有 `extra`**——说明 `ex.extra` 的赋值根本没跑到，更像语法错误而不是运行期异常。
2. 执行 `node --check js/task-e.js`：报 `SyntaxError: Unexpected token ')'`，定位到 `readPivot()` 的 `Math.max(.6, Math.min(4, num("e-r", DEF.r))` 少写了一个右括号。
3. 手动执行 `CG5Start("e")`：成功返回“茶壶造型 · 858 顶点 / 1512 三角面”，排除假设 2、3。

**根因与修复**：`readPivot()` 中 `Math.max(...)` 括号不配对，解析阶段就失败。补上 `)` 后刷新页面，`CG5App` 正常，状态栏显示模型信息。

### 故障二：Task E 的绕点标记“设了 35° 却几乎不动”

**Console 现象**：无报错，矩阵数值全为有限值，因此不是 NaN/着色器问题。

**AI 提示的三种假设**

1. 角度单位重复换算：`CG5.R(axis, angle)` 内部已经 `angle*π/180`，如果传入的已经是弧度，就会变成 35° 的 1/57；
2. 矩阵链顺序错（`T(P)/Ry/T(-P)` 的乘法方向或 `chain` 顺序不对）；
3. 标记的最终位置被 `T(q)` 覆盖，或轨道半径被读成 0。

**我选的最小测试（一次只验一个假设）**

- 取 `P=(-1.5,0.1,0.2)`、`q=(0.7,0.25,0.2)`，直接算 `T(P)·Ry(90°)·T(-P)·q`：
  - 手算期望：绕 P 转 90° 后 `(P.x + (q.z−P.z), q.y, P.z − (q.x−P.x)) = (-1.50, 0.25, -2.00)`；
  - 实测得到：`(0.70, 0.25, 0.14)`，几乎等于 q 本身 → 说明实际转角约为 0.6°，正好是 `35°×(π/180)`，**确认是重复换算弧度**。
- 再用 `θ=0°` 与 `θ=180°` 两组验证：0° 应保持 q，180° 应关于 P 对称翻到 `(-3.70, 0.25, 0.20)`；修复后两者均吻合，排除假设 2、3。

**根因与修复**：`ex.extra` 里写成了 `const theta = s.orbit * RADD; CG5.R("Y", theta)`，而 `CG5.R` 接收的是**角度制**。改为直接把 `thetaDeg` 传给 `CG5.R("Y", thetaDeg)`，子标记自转改为 `thetaDeg*2`；修复后 `θ=90°` 实测 `(-1.50, 0.25, -2.00)` 与手算一致。

### 我记录下来的排查方法

- 先看 **Console 第一行错误**：语法错误与运行期错误要分开处理（`node --check` 一秒定位语法问题）。
- 数值不对但无报错时，**不要改代码试运气**，先用一个能手工算的 θ（90°/180°/0°）比对 `M·q` 的坐标。
- 单位、矩阵链顺序、`T(q)` 位置三者必须分别验证，一次只动一个变量。

---

## 协作 3 · 工作台方案比较（3 分，对应 E）

### 3.1 问 AI 的提示词（按指导书 8.3 改写）

> 我已完成 OBJ 导入、四视角、正交/透视以及鼠标键盘 T/R/S。请给出两个能在 40 分钟内复用现有 WebGL2 模块的观察台拓展设计，对比功能、矩阵链、交互参数和验证难度；不要引入 Three.js、光照、纹理或复杂模型编辑器。

### 3.2 AI 给出的两个方案（摘录并对照）

| 比较项 | 方案 1：绕点观察 + 父子部件 | 方案 2：正交/透视双画布同步对照 |
|---|---|---|
| 功能 | 参考点 P、绕点运动的父标记、继承父变换的子标记、轨迹环、P 与半径可输入 | 同一 M/V，左右两个 canvas 仅 P 不同；拖动左画布时右画布同步 |
| 矩阵链 | `M_orbit = T(P)·Ry(θ)·T(-P)·T(q)·S`；子标记 `M_orbit·T(q)·Ry(2θ)·T(o)·S` | 两条链只差 P：`P_ortho·V·M·N` 与 `P_persp·V·M·N`，共享 M/V 缓存 |
| 交互参数 | P.x/P.y/P.z、轨道半径 r、θ（复用已有的“标记绕Y旋转”滑杆） | 两套 canvas 尺寸/aspect、两套 near/far/FOV，需处理双画布指针同步与焦点 |
| 验证难度 | 低：θ=90° 时 `M_orbit·q` 有唯一手算值可比对；P 变化时标记绕 P 而非原点 | 中：要保证两画布 aspect 各自正确、拖动同步无延迟，还要防止两套状态互相串改 |
| 40 分钟可行性 | 高：只加一个 `extra()` 返回绘制对象，不改框架渲染循环 | 低：要改 `engine.js`/`app.js` 支持第二画布，框架改动面大 |

AI 另外提醒：方案 1 容易退化成“只是多画几个方块”，必须让 P 可输入、并给出 `M_orbit·q` 数值读出，否则无法证明绕点矩阵真的生效。

### 3.3 我的选择与理由（**最终由我决定**）

**采用方案 1**，并在其基础上自己加了两处：

1. **实时读出 `M_orbit·q`**：把矩阵作用后的坐标直接写到面板，θ=90° 时与手算 `(-1.50, 0.25, -2.00)` 对照，避免“看起来在转”但实际绕原点的假实现。
2. **额外做 Challenge 2（右键轨道相机）**：不改第二画布就能复用 V 的基向量，代价小；实测拖动后 eye 从 `(0,1.8,8.5)` 变为 `(-5.81,3.13,5.65)`，到 target 距离仍为 8.68，世界中心不变。

**不采用方案 2 的原因**：40 分钟内要改框架的双画布渲染与事件分流，风险和验证成本都高于收益；本实验 C 已经用“同一画布切换 P + NDC 宽度比”完成了正交/透视对照，双画布属于加分项而非必要项。后续若时间允许，可在 Challenge 1 中再做。

### 3.4 AI 协作的边界

- AI 提供的三组控制变量、故障假设、方案比较都只作为**候选**；是否采用、参数取多少、结论是否成立，由我用实测数值判断。
- 本次两处真实故障（语法错误、弧度重复换算）都是我先复现、再由 AI 给假设、最后由我写最小测试定位，AI 没有直接重写整个文件。
