From ea7bf01e7252d3b6d4d60daad7025e7dcb820aa4 Mon Sep 17 00:00:00 2001 From: EwdErna Date: Mon, 21 Sep 2026 19:00:46 +0900 Subject: [PATCH] =?UTF-8?q?phy:=20=E9=81=B8=E6=8A=9E=E8=82=A2=E3=82=92?= =?UTF-8?q?=E3=81=BE=E3=81=9F=E3=81=90=E5=85=B1=E9=80=9A=E3=82=AD=E3=83=BC?= =?UTF-8?q?=E3=81=AF=E7=8B=AC=E7=AB=8B=E3=81=97=E3=81=9FVariantGroup?= =?UTF-8?q?=E3=81=AB=E5=88=86=E3=81=91=E3=81=A6=E8=A1=A8=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VIAの定義824件を調べ、複数の選択肢で成立するキーを持つ基板を2件見つけた(LW67、MACRO1)。 どちらも独立した2つの選択を1つの選択肢リストに畳んだもので、VariantGroupを2つ置けば キーを複製せずに表せる。残るのは、積のうち一部しか提供しない基板の扱いだけになった。 Journal: journal/2026-09-21-variant-shared-key.md Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01246a5KFLmwcRXriZvX8fdJ --- PHYレイヤー.md | 13 ++- journal/2026-09-21-variant-shared-key.md | 136 +++++++++++++++++++++++ 2 files changed, 144 insertions(+), 5 deletions(-) create mode 100644 journal/2026-09-21-variant-shared-key.md diff --git a/PHYレイヤー.md b/PHYレイヤー.md index a834a5c..f8d5ecd 100644 --- a/PHYレイヤー.md +++ b/PHYレイヤー.md @@ -149,6 +149,8 @@ PHYは全バリアントのキーを同時に持つ。基板上のフットプ - 部品ノードもVariantの配下に置ける。1Uキーとロータリーエンコーダーの選択がこれにあたる - Variantの中に別のVariantGroupを置ける。あるバリアントを選んだときだけ現れる選択肢を表す - 選択箇所が複数ある場合はVariantGroupを複数置く。同じ親の下に並べてよい +- 複数の選択肢に共通して存在するキーは、選択を独立したVariantGroupに分けて表す。 + ANSI/ISOとBackspaceの分割のように、1つの選択肢リストに畳まれている基板があるため VariantGroupはクラスターと同じ変換を持つ。Variantは変換を持たない。 バリアントは同じ場所を占める代替物であり、選択によって配下の絶対配置が動く構造を作らないため。 @@ -405,9 +407,10 @@ Beyond KLEはスイッチの品種を持たないため、対応づける先が - 現状の認識: キーのローカル原点(左上角)・キー中心・境界矩形の角と中心が候補。相対グリッドとは別の機構になる - 格上げ条件: 原点移動を実装し、吸着なしで足りるか、足りない場合どの候補が要るかを確認する -### P-15 複数のバリアントで共通のキーをどう置くか +### P-15 提供されない組み合わせを禁じる必要があるか -- 位置づけ: 未解決の問い。本文「レイアウトバリアント」は1つのキーが1つのVariantにしか属さない形になっている -- 現状の認識: 3択のうち2つで存在し1つでは存在しないキーは、今の木では表せない。同じキーを2つのVariantに - 複製すると別IDになり、ELC・IDX・MAPの記述が二重になる。そもそもそういう構成が実在するかを確認していない -- 格上げ条件: 選択肢をまたいで共通のキーが要る実在の基板を探す。あれば表現方法を決める +- 位置づけ: 未解決の問い。本文「レイアウトバリアント」は複数のVariantGroupの組み合わせに制限を持たない +- 現状の認識: 選択肢をまたいで共通のキーを持つ基板は実在し、独立した選択に分けてVariantGroupを2つ置けば + キーを複製せずに表せる。ただし2つの選択の積のうち一部しか提供しない基板(MACRO1)があり、 + 今の木では提供されない組み合わせも選べてしまう +- 格上げ条件: 組み合わせの禁止を表現する必要があるかをユーザーが決める diff --git a/journal/2026-09-21-variant-shared-key.md b/journal/2026-09-21-variant-shared-key.md new file mode 100644 index 0000000..62f4ab5 --- /dev/null +++ b/journal/2026-09-21-variant-shared-key.md @@ -0,0 +1,136 @@ +# 複数のバリアントで共通のキーを持つ基板があるか + +PHY P-15 の検証。 + +## 結論 + +実在する。ただし稀で、どれも「独立した2つの選択」を1つの選択肢リストに畳んだ結果だった。 +VariantGroupを2つ並べれば、キーを複製せずに表せる。 + +残る問題は別のところにある。MACRO1 は2つの選択の積4通りのうち3通りしか提供していない。 +VariantGroupを2つ置くと、基板が提供しない4通り目も選べてしまう。 + +## 調べ方 + +VIAのキーボード定義1484件のうち、レイアウトオプションを持つ824件を対象にした。 +VIAは1つのキーを1つの選択肢にしか置けないので、複数の選択肢で成立するキーは定義側で複製されている。 +生ラベルの4番目(`"行,列"` の形)がオプション番号と選択肢番号で、これで選択肢ごとのキー集合を作り、 +マトリクス位置と形状(x, y, w, h, r)が一致するキーが複数の選択肢に現れるものを探した。 + +3択以上のオプション415件のうち、「一部の選択肢だけに現れる共通キー」を持つのは2件。 + +| ボード | 選択肢 | 共通キーの現れ方 | +| --- | --- | --- | +| LW67 | ANSI / ANSI Split Backspace / ISO / ISO Split Backspace | {ANSI,ANSI-split} {ISO,ISO-split} {ANSI,ISO} {ANSI-split,ISO-split} | +| MACRO1 | Numpad / Southpaw / Macropad | {Numpad,Macropad} {Southpaw,Macropad} | + +## 中身 + +LW67 は ANSI/ISO と Backspace分割の2択の積(2×2)で、4つの選択肢はその全組み合わせ。 +ISO Enter は ISO 側の2つ、分割Backspaceのキーは分割側の2つに現れる。 +VariantGroupを「ANSI/ISO」と「Backspace分割の有無」の2つに分ければ、キーの複製は要らない。 + +MACRO1 は左列と右列がそれぞれ「1U×2 か 2U縦 か」の2択で、 + +| 選択肢 | 左列 | 右列 | +| --- | --- | --- | +| Numpad | 1U | 2U縦 | +| Southpaw | 2U縦 | 1U | +| Macropad | 1U | 1U | + +「左右とも2U縦」は提供されていない。左列と右列を別のVariantGroupにすれば、 +`3,0@(0,3.5)1U`(Numpad と Macropad で共通)のようなキーを複製せずに置けるが、 +提供されない4通り目も表現できてしまう。 + +## 捨てた見方 + +入れ子のVariantGroupで表す案。MACRO1 は {Numpad,Macropad} と {Southpaw,Macropad} が交差するので、 +入れ子(片方がもう片方を含む形)では表せない。積として2つ並べる形でしか表せない。 + +## この調査の限界 + +VIAの表現力に引きずられている。VIAは選択肢ごとのキー集合しか書けないので、 +設計者が最初から「2つの独立した選択」として設計した基板は、VIAの定義でも2つのオプションに分かれ、 +この検索には引っかからない。実際、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.lengthv.join(',')))].map(s=>new Set(s.split(',').map(Number))); + let laminar=true; + for(let i=0;isets[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))); +```