From d7a0f99cb432f56eedd3745e846d0c44f26e1e15 Mon Sep 17 00:00:00 2001 From: EwdErna Date: Mon, 21 Sep 2026 19:01:00 +0900 Subject: [PATCH] =?UTF-8?q?idx:=20=E3=82=B3=E3=83=9F=E3=83=A5=E3=83=8B?= =?UTF-8?q?=E3=83=86=E3=82=A3=E3=83=AC=E3=82=A4=E3=82=A2=E3=82=A6=E3=83=88?= =?UTF-8?q?=E3=81=AE=E4=B8=A6=E3=81=B3=E3=81=8C=E5=8F=96=E6=8D=A8=E3=81=A0?= =?UTF-8?q?=E3=81=91=E3=81=A7=E3=81=AF=E5=BE=97=E3=82=89=E3=82=8C=E3=81=AA?= =?UTF-8?q?=E3=81=84=E5=A0=B4=E5=90=88=E3=82=92=E8=A8=98=E9=8C=B2=E3=81=99?= =?UTF-8?q?=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit QMKの実データで、コミュニティレイアウトとの173組のうち30組(すべてISO系)が `LAYOUT_all` の並びの部分列にならない。同じマトリクス位置がレイアウトによって 別の物理キーになり、残ったキーの読み順が変わるため。 埋め方は未定として未確認事項に残す。 Journal: journal/2026-09-21-community-layout-order.md Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01246a5KFLmwcRXriZvX8fdJ --- IDXレイヤー.md | 17 +-- journal/2026-09-21-community-layout-order.md | 111 +++++++++++++++++++ 2 files changed, 121 insertions(+), 7 deletions(-) create mode 100644 journal/2026-09-21-community-layout-order.md diff --git a/IDXレイヤー.md b/IDXレイヤー.md index ec4a792..9b33321 100644 --- a/IDXレイヤー.md +++ b/IDXレイヤー.md @@ -53,7 +53,10 @@ row / col 自体がELCの正本ではなく出力時の派生情報である。 PHYがレイアウトバリアントを持つ場合も、キー番号の空間は1つとする。バリアントごとにIDXを分けない。 キー番号は公開インターフェースであり、どの空間のものかを断らずに解釈できなければならない。 -特定のレイアウト向けの並びは、選んだバリアントに属さないキーを落として得られる派生情報として扱う [推測:P-1]。 +特定のレイアウト向けの並びは、選んだバリアントに属さないキーを落として得る。 +ただしこれだけでは足りない場合がある。QMKのコミュニティレイアウトでは、同じマトリクス位置が +レイアウトによって別の物理キーになり(ANSIのバックスラッシュとISOの `#~`)、残ったキーの読み順が変わる。 +ISO系で引数順が一致しない。 排他なキー同士は、同じキー番号を共有できる。`order` の要素にキーIDの配列を書くと、 その位置の番号を配列内のキーで共有する。 @@ -109,10 +112,10 @@ type IDX = { ## 未確認事項 -### P-1 特定レイアウト向けの並びが既存の慣習と一致するか +### P-1 一致しないコミュニティレイアウトの並びをどう与えるか -- 位置づけ: 本文「レイアウトバリアント」が前提にしている -- 現状の認識: `order` からキーを落として得た並びが、QMKのコミュニティレイアウト(`LAYOUT_60_ansi` など)が - 要求する引数順と一致するかを確認していない。一致しないなら、レイアウトごとの並びを別に持つ必要が出る -- 格上げ条件: 実在のキーボードの `LAYOUT_all` とコミュニティレイアウトのマクロを読み、 - 前者から後者がキーの取捨だけで得られるかを確認する +- 位置づけ: 本文「レイアウトバリアント」の残余。一致しない場合があることは確認済み +- 現状の認識: QMKの実データで、コミュニティレイアウトとの173組のうち30組(すべてISO系)が + `order` からの取捨では得られない。並べ替えの規則を与えるか、レイアウトごとの並びを持つかが未定。 + 後者は「レイアウトごとに別のIDXを持つ」として一度落としている +- 格上げ条件: 埋め方をユーザーが決める diff --git a/journal/2026-09-21-community-layout-order.md b/journal/2026-09-21-community-layout-order.md new file mode 100644 index 0000000..86ebe1f --- /dev/null +++ b/journal/2026-09-21-community-layout-order.md @@ -0,0 +1,111 @@ +# コミュニティレイアウトの引数順がキーの取捨だけで得られるか + +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']) +```