Files
Beyond-KLE/journal/2026-09-21-community-layout-order.md
T
ewdernaandClaude Opus 5 d7a0f99cb4 idx: コミュニティレイアウトの並びが取捨だけでは得られない場合を記録する
QMKの実データで、コミュニティレイアウトとの173組のうち30組(すべてISO系)が
`LAYOUT_all` の並びの部分列にならない。同じマトリクス位置がレイアウトによって
別の物理キーになり、残ったキーの読み順が変わるため。
埋め方は未定として未確認事項に残す。

Journal: journal/2026-09-21-community-layout-order.md

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

112 lines
5.5 KiB
Markdown
Raw 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.
# コミュニティレイアウトの引数順がキーの取捨だけで得られるか
IDX P-1 の検証。
## 結論
得られない場合がある。ISO系のコミュニティレイアウトで、`LAYOUT_all` の並びからキーを落としただけでは
引数順が合わない。QMKの実データで 173組中30組。全部ISO系だった。
原因は、同じマトリクス位置が、レイアウトによって別の物理キーになるため。
| レイアウト | matrix (2,12) の位置とサイズ |
| --- | --- |
| `LAYOUT_all` | (13.5, 1) 1.5U — ANSIのバックスラッシュ |
| `LAYOUT_60_iso` | (12.75, 2) 1U — ISOの `#~` |
キーが物理的に別の場所へ移るので、読み順での位置も変わる。`LAYOUT_all` では2行目に、
ISOでは3行目に現れる。取捨では並べ替えられない。
PHYの用語では、この2つは別のキーノードである(サイズや形が変わるキーは別のIDを持つ)。
「選んだバリアントに属さないキーを落とす」だけでは、残ったキーの並びが変わらないので、
コミュニティレイアウトが要求する順にならない。
## 調べ方
QMKのキーボード一覧3759件から400件を無作為抽出し、`keyboards.qmk.fm` の info.json を取得した。
`LAYOUT_all`(無ければ `LAYOUT`)を基準に、同じボードの他のレイアウトと比較した。
| 対象 | 件数 |
| --- | --- |
| 取得したボード | 400 |
| 複数のレイアウトを持つボード | 146 |
| community_layouts に載っているレイアウトとの組 | 173 |
| うち基準の並びの部分列になっている | 143 |
| うち部分列にならない | 30 (すべてISO系) |
| うち同じマトリクス位置で形状が違うキーを含む | 172 |
部分列にならない例: alf/dc60, argyle, artifact/stratum, cannonkeys/brutalv2_65, era/linx3/n87,
kapcave/gskt00, karlb/kbic65, kbdfans/kbd67/rev2, lucid/phantom_solder, phdesign/ph60/multi,
rmi_kb/tkl_ff/v2 の各ISOレイアウト。
era/linx3/n87 は `LAYOUT_tkl_ansi` に `LAYOUT_all` へ無いマトリクス位置が1つある。
QMKの `LAYOUT_all` は全バリアントの重ね合わせではなく、それ自体が1つの選択でしかない
(1つのマトリクス位置を2回書けないため)。PHYが全バリアントを同時に持つのとは別物である。
## 172/173 が「形状違い」を含むことについて
ANSI系のレイアウトでも、Backspaceが2Uか1U×2か、右Shiftの幅、Tsanganの下段など、
同じマトリクス位置のキーの形が変わる組がほとんどである。これらは並び順は保たれるが、
PHY上は別のキーノードになる。IDXが「キーIDの並び」で番号を持つ以上、
落とすだけでなく差し替えが要ることは変わらない。
## 検証スクリプト
```py
# QMKのinfo.jsonを無作為抽出で取得する。qmk_list.json は
# curl -o qmk_list.json https://keyboards.qmk.fm/v1/keyboard_list.json
import json, random, urllib.request, concurrent.futures, os
kbs=json.load(open('qmk_list.json'))['keyboards']
random.seed(42)
os.makedirs('qmk', exist_ok=True)
def fetch(kb):
p='qmk/'+kb.replace('/','__')+'.json'
if os.path.exists(p): return True
try:
with urllib.request.urlopen(f'https://keyboards.qmk.fm/v1/keyboards/{kb}/info.json', timeout=30) as r:
open(p,'wb').write(r.read())
return True
except Exception as e:
return str(e)[:60]
with concurrent.futures.ThreadPoolExecutor(8) as ex:
res=list(ex.map(fetch, random.sample(kbs, 400)))
print('fetched', sum(1 for r in res if r is True), 'failed', sum(1 for r in res if r is not True))
```
```py
# LAYOUT_all の並びから、他のレイアウトがキーの取捨だけで得られるか
import json, glob, collections
st=collections.Counter(); rows=[]
for f in glob.glob('qmk/*.json'):
d=json.load(open(f,encoding='utf-8'))
for kb,v in (d.get('keyboards') or {}).items():
lays=v.get('layouts') or {}
if not lays: continue
st['boards']+=1
if len(lays)>1: st['multi-layout boards']+=1
base=next((n for n in ('LAYOUT_all','LAYOUT') if n in lays), None)
if base is None or len(lays)<2: continue
shape=lambda k:(k.get('x'),k.get('y'),k.get('w',1),k.get('h',1),k.get('r',0))
shA={}
for k in lays[base]['layout']: shA.setdefault(tuple(k['matrix']),[]).append(shape(k))
A=[tuple(k['matrix']) for k in lays[base]['layout']]
for name,l in lays.items():
if name==base: continue
B=[tuple(k['matrix']) for k in l['layout']]
it=iter(A); subseq=all(any(a==b for a in it) for b in B) # 部分列か
rows.append(dict(kb=kb, name=name, subseq=subseq,
missing=sum(1 for m in B if m not in set(A)),
shapediff=sum(1 for k in l['layout']
if tuple(k['matrix']) in shA and shape(k) not in shA[tuple(k['matrix'])]),
community=name.replace('LAYOUT_','') in (v.get('community_layouts') or [])))
print(st)
comm=[r for r in rows if r['community']]
print('community layout pairs:', len(comm))
print(' 部分列になる:', sum(1 for r in comm if r['subseq']))
print(' 部分列にならない:', sum(1 for r in comm if not r['subseq']))
print(' baseに無いキーを含む:', sum(1 for r in comm if r['missing']))
print(' 同じmatrixで形状が違うキーを含む:', sum(1 for r in comm if r['shapediff']))
for r in comm:
if not r['subseq']: print(' NG', r['kb'], r['name'])
```