Files
Beyond-KLE/journal/2026-09-21-variant-shared-key.md
ewdernaandClaude Opus 5 6d0eba9346 phy: 複数のバリアントで成立するキーはVariantごとに複製して置く
隣り合うキャップが同じフットプリントを食い合う基板(MACRO1)があり、選択を独立な
VariantGroupに必ず分解できるわけではない。分解を強いると、キャップが重なる組み合わせを
作れてしまう。複製してもELCは接続先を、IDXは番号を共有できるので他レイヤーは壊れない。
どのキーノードが同じ物理キーかはBeyond KLEが判定せず、設計者の情報として扱う。

前のコミットで「独立したVariantGroupに分けて表す」と書いたが、MACRO1は下段を含めると
分解できない。あわせて訂正する。

Journal: journal/2026-09-21-variant-shared-key.md

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01246a5KFLmwcRXriZvX8fdJ
2026-09-21 21:34:10 +09:00

149 lines
8.0 KiB
Markdown
Raw Permalink 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.
# 複数のバリアントで共通のキーを持つ基板があるか
## 結論
実在する。そのうえで、共通キーはVariantごとに複製して置く。同じ物理キーかどうかはBeyond KLEが判定しない。
分解して複製を避けられる基板もあるが、避けられない基板もある。
| ボード | 選択肢 | 分解 |
| --- | --- | --- |
| LW67 | ANSI / ANSI Split Backspace / ISO / ISO Split Backspace | 独立した2つの選択の積。VariantGroupを2つに分ければ複製は要らない |
| MACRO1 | Numpad / Southpaw / Macropad | 分解できない。独立な選択に割ると、キャップが重なる組み合わせが作れてしまう |
複製を許す判断の根拠は、複製しても他レイヤーが壊れないこと。
ELCは排他キーで接続先の組を共有でき、IDXは `order` の配列形式で番号を共有できる。
MAPは同じキーコードを2回書くことになるが、どれが同じ物理キーかは設計者が知っている。
位置で自動判定しようとしても、排他キーは位置もサイズも違ってよい(2UのBackspaceと分割後の1U)ので、
判定の基準にならない。
## 調べ方
VIAのキーボード定義1484件のうち、レイアウトオプションを持つ824件を対象にした。
VIAは1つのキーを1つの選択肢にしか置けないので、複数の選択肢で成立するキーは定義側で複製されている。
生ラベルの4番目(`"行,列"` の形)がオプション番号と選択肢番号で、これで選択肢ごとのキー集合を作り、
マトリクス位置と形状(x, y, w, h, r)が一致するキーが複数の選択肢に現れるものを探した。
3択以上のオプション415件のうち、該当するのは2件だけだった。
## LW67
ANSI/ISO と Backspace分割の2択の積で、4つの選択肢はその全組み合わせ。
ISO Enter は ISO 側の2つ、分割Backspaceのキーは分割側の2つに現れる。
QMKも `LAYOUT_ansi` / `LAYOUT_ansi_splitbs` / `LAYOUT_iso` / `LAYOUT_iso_splitbs` の4つを持つ。
VariantGroupを「ANSI/ISO」と「Backspace分割の有無」に分ければ、キーの複製は要らない。
## MACRO1
6行4列。VIAは3択、QMK (`laneware/macro1`) も `LAYOUT_numpad` / `LAYOUT_southpaw` / `LAYOUT_macropad` の
3つしか持たない。`LAYOUT_macropad` は全キー1Uで23キーあり、他の2つが使うフットプリントを全部含む。
配線はすべての位置に来ていて、組み合わせを3つに絞っているのはファームウェア側である。
| | 左列 (2,0 / 3,0 / 4,0) | 下段左 (5,0 / 5,1) | 右列 (2,3 / 3,3 / 4,3) | 下段右 (5,2 / 5,3) |
| --- | --- | --- | --- | --- |
| Numpad | 1U×3 | `5,0` が2U幅。`5,1` は潰れる | 2U縦×2 (`3,3` なし) | `5,2` 1U。`5,3` は `4,3` が潰す |
| Southpaw | 2U縦×2 (`3,0` なし) | `5,0` は `4,0` が潰す。`5,1` 1U | 1U×3 | `5,2` が2U幅。`5,3` は潰れる |
| Macropad | 1U×3 | 両方1U | 1U×3 | 両方1U |
共通キーは8個。`3,0` などの左列1Uが {Numpad, Macropad}、`5,1` と右列1Uが {Southpaw, Macropad} に現れる。
出現集合が交差するので入れ子では表せない。
左右の列だけを見ると独立な2択に見えるが、下段が絡むと分解できない。
「左列と下段左」を1つの選択、「右列と下段右」をもう1つの選択として2つのVariantGroupに割ると、
`4,0` が2U縦のまま `5,0` に1Uキーを置く組み合わせが作れる。これは提供されないどころか、
キャップが重なって物理的に成立しない。
## 捨てた見方
- 入れ子のVariantGroupで表す: 出現集合が交差するので不可能
- 独立したVariantGroupに分けて、成立しない組み合わせを禁止する仕組みを足す:
複製を許せば要らない。禁止の表現を足すほうが重い
- 同じ位置のキーに違う番号が振られたら警告する: 排他キーは位置もサイズも違ってよいので基準にならない
## この調査の限界
VIAの表現力に引きずられている。VIAは選択肢ごとのキー集合しか書けないので、
設計者が最初から「2つの独立した選択」として設計した基板は、VIAの定義でも2つのオプションに分かれ、
この検索には引っかからない。引っかかった2件は「1つのオプションに畳んでしまった」定義だけである。
## 検証スクリプト
`rawkle.js` — VIAのオプションタグを読むため、並べ替え前の生ラベルを残すデシリアライザ。
```js
// 生ラベル(並べ替え前)を保持する最小デシリアライザ。幾何情報は serial.js と同じ規則。
function deserializeRaw(rows){
const cur={x:0,y:0,w:1,h:1,x2:0,y2:0,w2:0,h2:0,r:0,rx:0,ry:0,d:false};
const keys=[];
for(const row of rows){
if(!Array.isArray(row)){ continue; }
for(const item of row){
if(typeof item==='string'){
keys.push({x:cur.x,y:cur.y,w:cur.w,h:cur.h,
w2:cur.w2===0?cur.w:cur.w2,h2:cur.h2===0?cur.h:cur.h2,x2:cur.x2,y2:cur.y2,
r:cur.r,rx:cur.rx,ry:cur.ry,d:cur.d,raw:item.split('\n')});
cur.x+=cur.w; cur.w=cur.h=1; cur.x2=cur.y2=cur.w2=cur.h2=0; cur.d=false;
} else {
if(item.r!=null) cur.r=item.r;
if(item.rx!=null){ cur.rx=item.rx; cur.x=cur.rx; cur.y=cur.ry; }
if(item.ry!=null){ cur.ry=item.ry; cur.x=cur.rx; cur.y=cur.ry; }
if(item.x) cur.x+=item.x;
if(item.y) cur.y+=item.y;
if(item.w){ cur.w=cur.w2=item.w; }
if(item.h){ cur.h=cur.h2=item.h; }
if(item.x2) cur.x2=item.x2;
if(item.y2) cur.y2=item.y2;
if(item.w2) cur.w2=item.w2;
if(item.h2) cur.h2=item.h2;
if(item.d) cur.d=item.d;
}
}
cur.y++; cur.x=cur.rx;
}
return keys;
}
module.exports={deserializeRaw};
```
```js
const fs=require('fs'), path=require('path');
const {deserializeRaw}=require('./rawkle.js');
function walk(d,out=[]){for(const e of fs.readdirSync(d,{withFileTypes:true})){const p=path.join(d,e.name); e.isDirectory()?walk(p,out):e.name.endsWith('.json')&&out.push(p);}return out;}
const OPT=/^(\d+),(\d+)$/;
const rows=[];
for(const f of walk('via/src')){
let d; try{ d=JSON.parse(fs.readFileSync(f,'utf8')); }catch(e){ continue; }
const lay=d.layouts; if(!lay||!Array.isArray(lay.keymap)||!lay.keymap.length) continue;
let keys; try{ keys=deserializeRaw(lay.keymap); }catch(e){ continue; }
const byOpt={};
for(const k of keys){
const tag=k.raw[3]; if(!tag||!OPT.test(tag)) continue;
const [o,c]=tag.split(',').map(Number);
const id=[k.raw[0],k.x,k.y,k.w,k.h,k.r].join('|'); // マトリクス位置+形状で「同じキー」とみなす
((byOpt[o]=byOpt[o]||{})[c]=byOpt[o][c]||new Set()).add(id);
}
for(const [o,ch] of Object.entries(byOpt)){
const cs=Object.keys(ch).map(Number).sort((a,b)=>a-b);
if(cs.length<3) continue;
// 各キーが現れる選択肢集合
const occ={};
for(const c of cs) for(const id of ch[c]) (occ[id]=occ[id]||[]).push(c);
const multi=Object.entries(occ).filter(([,v])=>v.length>1 && v.length<cs.length);
if(!multi.length) continue;
// 出現集合の族が laminar(入れ子で表せる)か
const sets=[...new Set(multi.map(([,v])=>v.join(',')))].map(s=>new Set(s.split(',').map(Number)));
let laminar=true;
for(let i=0;i<sets.length;i++) for(let j=i+1;j<sets.length;j++){
const inter=[...sets[i]].filter(x=>sets[j].has(x)).length;
if(inter>0 && inter!==Math.min(sets[i].size,sets[j].size)) laminar=false;
}
rows.push({f:path.relative('via/src',f),name:d.name,opt:+o,
label: Array.isArray(lay.labels&&lay.labels[o])? lay.labels[o] : (lay.labels?lay.labels[o]:null),
choices:cs.length, sharedKeys:multi.length,
patterns:[...new Set(multi.map(([,v])=>'{'+v.join(',')+'}'))].join(' '), laminar});
}
}
console.log('3択以上で「一部の選択肢だけに現れる共通キー」を持つオプション:',rows.length);
rows.forEach(r=>console.log(JSON.stringify(r)));
```