function qnb = triad(ib, in)
% triad - TRIAD 双矢量定姿（Wahba 问题的确定性两矢量解，Mini-INS M-A）
%
% 对标 PSINS `base/AHRS/accmag2att.m` 的思路（两矢量构造姿态阵），自写实现。
%
% ─── 公式层（TRIAD 构造，公式 6.1）──────────────────────────────────
%   输入两对"观测/参考"向量（内部归一化）：
%     ib = [b1 b2]  体系观测（列）：第 1 列优先级高（如加计 ≈ 天向），
%                   第 2 列（如磁）只用于补足绕第 1 列的旋转自由度
%     in = [n1 n2]  导航系参考（一一对应，如 [天向；地磁北极向]）
%   体标架：t1 = b1/|b1|；t2 = (t1×b2/|b2|)⁻；t3 = t1×t2
%   导航标架同构构造 → 两两正交、同为右手系
%     Cbn = Tn · Tb'    （体→导航；Cbn·b1̂ = n1，Cbn·t2b = n2）
%
% ─── 输入/输出 ─────────────────────────────────────────────────────
%   ib / in  3×2 矩阵（列向量对），对应关系必须正确！
%   qnb      4×1 姿态四元数（体→导航，q2cnb 的入口约定）
%
% ─── 新手备注 ───────────────────────────────────────────────────────
%   · 为什么第 2 列"低人一等"？两向量才能钉死一个姿态：第 1 列定
%     "天向"（pitch/roll），第 2 列只贡献绕天向的转角（yaw）——若两
%     列不垂直，第 2 列里与第 1 列平行的分量会被叉乘扔掉；
%   · 加计参考向量取导航系 [0;0;1]（ENU 天向）：准静态下比力 ≈ 天向，
%     加计测的就是它；磁参考取地磁北极向（magsimu 生成的 m_n）；
%   · TRIAD 是"硬解"——观测有噪声它照单全收；噪声大的场合用 QUEST
%     （最小二乘加权）或直接上 Mahony 滤波。

b1 = ib(:, 1) / norm(ib(:, 1));                  % 体标架轴 1（第 1 观测，公式 6.1）
b2 = cross(b1, ib(:, 2));                        % 轴 2 候选：b1 × 第 2 观测
b2 = b2 / norm(b2);                              % 归一化（自动丢弃平行分量）
b3 = cross(b1, b2);                              % 轴 3 = b1×b2（补全右手系）
Tb = [b1, b2, b3];                               % 体标架（列 = 三轴）

n1 = in(:, 1) / norm(in(:, 1));                  % 导航标架轴 1（第 1 参考）
n2 = cross(n1, in(:, 2));                        % 轴 2 候选：n1 × 第 2 参考
n2 = n2 / norm(n2);
n3 = cross(n1, n2);
Tn = [n1, n2, n3];                               % 导航标架（与 Tb 同构）

Cbn = Tn * Tb';                                  % 体→导航（公式 6.1：Cbn·bî = ni）
qnb = cnb2q(Cbn);                                % 转四元数（本库入口约定）
end
