phy: 行の先頭以外の r, rx, ry を取り込みで受け入れる(P-16解消)

KLE本家は拒否するが、VIAとRemapは読む。Remapで扱える基板を取り込めない
状態にしないため、位置を問わず受け入れる。本家のコードから行頭チェックを
外した挙動をそのまま採る。出力は従来どおり行頭にのみ置く。

Journal: journal/2026-09-21-kle-lenient-row.md

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SSKcJLPAaPLchG4LXxk6cY
This commit is contained in:
2026-09-21 21:57:45 +09:00
co-authored by Claude Opus 5
parent 6d0eba9346
commit c31360a772
2 changed files with 70 additions and 8 deletions
+4 -8
View File
@@ -334,6 +334,7 @@ const kleData = (ConfigObject | KeyLabel)[][]
- ユニットサイズ(mm)、ビジュアルオフセット、クラスター構造、部品ノード、per-key LEDはKLE JSONに出力されない - ユニットサイズ(mm)、ビジュアルオフセット、クラスター構造、部品ノード、per-key LEDはKLE JSONに出力されない
- バリアント構造はKLE JSONに出力しない。VariantGroupごとにバリアントを1つ選び、その配下だけを畳んで出力する - バリアント構造はKLE JSONに出力しない。VariantGroupごとにバリアントを1つ選び、その配下だけを畳んで出力する
- 部品グループ・スイッチの向きはKLE JSONに出力しない。sm, sb, st には取り込み時に保持した値をそのまま書き戻す - 部品グループ・スイッチの向きはKLE JSONに出力しない。sm, sb, st には取り込み時に保持した値をそのまま書き戻す
- r, rx, ry は行の先頭にのみ置く。KLE本家はそれ以外の位置に置かれたものを読まない
- p, sm, sb, st は空文字に戻す指定がKLE側で無視される。値を持つキーと持たないキーが混在する配置は、 - p, sm, sb, st は空文字に戻す指定がKLE側で無視される。値を持つキーと持たないキーが混在する配置は、
KLEで開いた時点で後続のキーに前の値が残る KLEで開いた時点で後続のキーに前の値が残る
- KLEの `p` はキーキャップのプロファイル名(SA、DCSなど)であり、PHYのプロファイルとは対応させない - KLEの `p` はキーキャップのプロファイル名(SA、DCSなど)であり、PHYのプロファイルとは対応させない
@@ -348,7 +349,9 @@ const kleData = (ConfigObject | KeyLabel)[][]
- 構造の復元は保証しない。KLE側での編集が大きいほど、クラスター構造は失われる - 構造の復元は保証しない。KLE側での編集が大きいほど、クラスター構造は失われる
- 配列の順番は照合に使わない。KLEは保存時にキーを「角度 → rx → ry → y → x」の順に並べ替えるため、順番は形状から決まる以上の情報を持たない - 配列の順番は照合に使わない。KLEは保存時にキーを「角度 → rx → ry → y → x」の順に並べ替えるため、順番は形状から決まる以上の情報を持たない
r, rx, ry を行の先頭以外に置いたKLE JSONが公開されている。KLE本家はこれを読み込まずエラーにする。 r, rx, ry は行の先頭以外に置かれていても受け入れ、そのキー以降に適用する。
rx, ry を持つキーでは、座標を (rx, ry) に戻してから x, y を加える。
KLE本家はこの位置の r, rx, ry をエラーにするが、VIAとRemapは読む。Remapで扱える基板を取り込めない状態にしない。
KLEの rx, ry, r はキーの絶対配置 (x, y, rotation) に変換して取り込む。元の rx, ry は保持しない。 KLEの rx, ry, r はキーの絶対配置 (x, y, rotation) に変換して取り込む。元の rx, ry は保持しない。
@@ -403,10 +406,3 @@ Beyond KLEはスイッチの品種を持たないため、対応づける先が
- 位置づけ: 未解決の問い。数値入力以外に、原点をどこへ吸着させるか - 位置づけ: 未解決の問い。数値入力以外に、原点をどこへ吸着させるか
- 現状の認識: キーのローカル原点(左上角)・キー中心・境界矩形の角と中心が候補。相対グリッドとは別の機構になる - 現状の認識: キーのローカル原点(左上角)・キー中心・境界矩形の角と中心が候補。相対グリッドとは別の機構になる
- 格上げ条件: 原点移動を実装し、吸着なしで足りるか、足りない場合どの候補が要るかを確認する - 格上げ条件: 原点移動を実装し、吸着なしで足りるか、足りない場合どの候補が要るかを確認する
### P-16 KLE本家が拒否するJSONを取り込むか
- 位置づけ: 未解決の問い。本文「KLE JSONの取り込み」が扱えていない
- 現状の認識: r, rx, ry を行の先頭以外に置いたJSONが公開されている(1505件中1件)。KLE本家はエラーにするが、
VIAのパーサーは読める。本家に合わせて拒否するか、読めるものは読むかが未定
- 格上げ条件: 取り込みの寛容さの方針をユーザーが決める
+66
View File
@@ -0,0 +1,66 @@
# 行の先頭以外の r, rx, ry を受け入れるか
## 結論
受け入れる。位置を問わず、そのキー以降に適用する。rx, ry は座標を (rx, ry) に戻してから x, y を加える
(KLE本家のコードから行頭チェックだけを外したときの挙動と同じ)。
決め手は Remap が読めること。ユーザーの判断: 「Remapで扱えるものを拒否したくない」。
| 案 | 取り込める基板 | 既存の解釈への影響 | 出力への影響 |
| --- | --- | --- | --- |
| 本家に合わせて拒否 | 1505件中1504件 | なし | なし |
| **r だけ受け入れ** | 1505件 | なし | なし |
| **r, rx, ry とも受け入れ(採用)** | 1505件 | なし | なし |
3案とも出力は変わらない。本家の serialize はクラスターが変わると必ず行を割るので、
r, rx, ry は常に行頭にしか出ない。寛容化しても非互換なJSONを生む側には回らない。
## 実データ
コーパス1505件(KLE本家プリセット12 + samples 9 + the-via/keyboards 1484)で、
行の先頭以外の r, rx, ry を持つのは `owlab/spring/spring.json` の1件だけ。内訳は r が15回、rx/ry は0回。
spring は Alice系。物理行ごとに配列を1つ書き、行の途中で r を 0 → 12 → -12 → 0 と切り替えている。
行頭チェックを外して読むと素直に開く(67キー、左右±12度のスプレイ + 回転なしの外側2列)。
```
0,3 x=3.25 r=0 0,4 x=4.44 r=12 0,8 x=8.72 r=-12 0,12 x=12.85 r=0
1,2 x=2.50 r=0 1,3 x=3.70 r=12 1,7 x=8.48 r=-12 1,11 x=12.60 r=0
```
## 寛容化が既存の解釈を変えないこと
本家 serial.js の該当箇所は throw だけで、意味づけは残りの行が決めている。
```js
if(key.r != null) { if(k!=0) {deserializeError(...)} current.rotation_angle = key.r; }
if(key.rx != null) { if(k!=0) {deserializeError(...)} current.rotation_x = cluster.x = key.rx; extend(current, cluster); }
```
throw を3つ消したパーサーと本家で全1505件を読み比べ、本家が読めた1504件は**キー配列が完全一致**。
変わるのは今エラーになる1件だけ。
## 他のツールの受け入れ
| ツール | 行の途中の r/rx/ry | 根拠 |
| --- | --- | --- |
| KLE本家 `serial.js` | 拒否 | `'r' can only be used on the first key in a row` |
| kle-serial (ijprest, TS移植) | 拒否 | 同じチェックを移植 |
| Vial `kle_serial.py` | 拒否 | `k != 0 and ("r" in item or ...)` |
| kicad-kbplacer | 拒否 | 同じチェックあり |
| VIA `@the-via/reader` | 許容 | reduceで位置チェックなし |
| Remap `KeyboardModel.ts` | 許容 | `Current.setOp()` に位置チェックなし |
| QMK `kle2xy.py` | 無関係 | r/rx/ry を読まない(回転を捨てる) |
Remap の `Current` を位置計算だけ移植して、throw を外した本家と突き合わせた。行の途中の r、
行の途中の rx/ry、途中で r=0 に戻す、spring 全体、いずれも一致する。VIA reader はコードを読んだだけで、
`cursor.y = ry` としか書いていないため rx/ry の扱いは本家と違う可能性がある(spring は rx=ry=0 なので影響なし)。
同じ突き合わせをコーパス全件でやると19件が不一致になるが、いずれも行頭側の ry の解釈やラベルなしキーの扱いで、
今回の話とは別件。簡易移植での比較なので Remap 本体で再現したわけでもない。追っていない。
## 取り込んで書き戻す
spring を寛容パーサーで読み、PHY経由で本家の serialize に通すと、5行 → 26行のJSONになる。
本家の deserialize が読め、配置も元と一致した。寛容化は本家で開けないファイルの修復経路になる。