Files
Beyond-KLE/phase1-json-schema.md
T
ewdernaandClaude Opus 5 2eb2867c13 IDX: キー番号の正本としてIDXレイヤーを確定し、MAPの参照をPHYへ移す
キー番号はPHYの幾何からもELCの配線からも導出できない決めごとなので、IDXが正本を持つ。
公開した基板のキー番号は他人のキーマップが依存する公開インターフェースであり、
出力のたびに生成し直さず固定する。

「最も重要な抽象化層」「最も欠けている層」という位置づけは誤りだったので落とした。
QMKのLAYOUTマクロの引数順、ZMKのmatrix transformのmap、KMKのcoord_mappingとして
既に存在していて、形式化されていないだけである。

あわせて2点。MAPはキー番号ではなくPHYのキーIDを参照する(IDXへの挿入でMAPがずれないため)。
IDXの保存形式は order: string[] とし、ELCのLEDChain.orderと同じく並びの位置を番号とする。

phase1-json-schema.md の P-1 を解消。

Journal: journal/2026-09-21-idx-necessity.md

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 08:52:27 +09:00

596 lines
17 KiB
Markdown

# Phase 1: JSON Schema仕様策定
## レイヤー間データフロー
```txt
PHY (物理配置)
├─→ ELC (電気配線)
├─→ IDX (キー識別)
└─→ MAP (キーマップ)
```
ELC・IDX・MAPはどれもPHYを参照する。ELCはIDXを参照せず、MAPもIDXを参照しない。
ファームウェアやVIA/Remap向けの出力は、各レイヤーの情報を統合して生成する。
他レイヤーからキーや部品を指す参照には、PHYのノードIDを使う。
## 1. PHYレイヤー JSON Schema
各フィールドの意味は `PHYレイヤー.md` を正とする。
### 基本構造
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://beyond-kle.org/schemas/phy-layer.json",
"title": "PHY Layer - Physical Key Layout",
"description": "Physical positioning and sizing of keys",
"type": "object",
"required": ["version", "metadata", "profiles", "root"],
"properties": {
"version": {
"type": "string",
"pattern": "^\\d+\\.\\d+\\.\\d+$",
"description": "Schema version (semver)"
},
"metadata": {
"type": "object",
"properties": {
"name": { "type": "string" },
"author": { "type": "string" },
"description": { "type": "string" },
"created": { "type": "string", "format": "date-time" },
"modified": { "type": "string", "format": "date-time" }
}
},
"kleCompatibility": {
"type": "object",
"description": "File-level opaque KLE fields kept for round-trip output (switchMount, switchBrand, switchType, backcolor, radii, background, css). The owning layer is undecided; see P-6 in PHYレイヤー.md",
"additionalProperties": true
},
"profiles": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/$defs/profile"
}
},
"root": {
"type": "array",
"items": {
"oneOf": [
{ "$ref": "#/$defs/key" },
{ "$ref": "#/$defs/cluster" },
{ "$ref": "#/$defs/component" }
]
}
}
},
"$defs": {
"transform": {
"type": "object",
"description": "Placement relative to the parent: rotate by rotation around the local origin, then translate by (x, y)",
"required": ["x", "y", "rotation"],
"properties": {
"x": { "type": "number" },
"y": { "type": "number" },
"rotation": { "type": "number", "description": "Degrees" }
}
},
"profile": {
"type": "object",
"required": ["id", "name", "unitSize"],
"properties": {
"id": { "type": "string" },
"name": { "type": "string" },
"unitSize": {
"type": "object",
"required": ["x", "y"],
"properties": {
"x": { "type": "number", "minimum": 0 },
"y": { "type": "number", "minimum": 0 }
}
},
"visualOffset": {
"type": "object",
"properties": {
"x": { "type": "number" },
"y": { "type": "number" },
"radius": { "type": "number", "minimum": 0 }
}
}
}
},
"key": {
"type": "object",
"required": ["type", "id", "profile", "transform", "size"],
"properties": {
"type": {
"type": "string",
"const": "key"
},
"id": {
"type": "string",
"description": "Unique key identifier"
},
"profile": {
"type": "string",
"description": "Reference to profile ID"
},
"transform": { "$ref": "#/$defs/transform" },
"size": {
"type": "object",
"description": "Local size. Not affected by the parent transform",
"required": ["w", "h"],
"properties": {
"w": { "type": "number", "minimum": 0 },
"h": { "type": "number", "minimum": 0 },
"x2": { "type": "number", "description": "Second rectangle for non-rectangular keys, relative to the local origin" },
"y2": { "type": "number" },
"w2": { "type": "number", "minimum": 0 },
"h2": { "type": "number", "minimum": 0 }
}
},
"switchOffset": {
"type": "object",
"description": "Switch position, offset from the center of the (w, h) rectangle. Key local coordinates, unit U. Defaults to 0",
"required": ["x", "y"],
"properties": {
"x": { "type": "number" },
"y": { "type": "number" }
}
},
"partGroup": {
"type": "string",
"description": "Label grouping keys that use the same part. Not interpreted by Beyond KLE"
},
"orientation": {
"type": "integer",
"enum": [0, 90, 180, 270],
"default": 0,
"description": "Switch orientation in the key local coordinates"
},
"perKeyLED": {
"type": "boolean",
"default": false,
"description": "Whether a per-key LED is mounted"
},
"decal": { "type": "boolean", "default": false },
"ghost": { "type": "boolean", "default": false },
"labels": {
"type": "array",
"items": { "type": "string" },
"description": "Reference information. Used for matching on KLE JSON import"
},
"kleCompatibility": {
"type": "object",
"description": "Opaque KLE fields kept for round-trip output (c, t, p, a, f, f2, fa, l, n, sm, sb, st). The owning layer is undecided; see P-6 in PHYレイヤー.md",
"additionalProperties": true
}
}
},
"component": {
"type": "object",
"description": "Non-key part placed by the designer (LED, OLED, encoder, screw hole, ...)",
"required": ["type", "id", "transform"],
"properties": {
"type": {
"type": "string",
"const": "component"
},
"id": { "type": "string" },
"transform": { "$ref": "#/$defs/transform" },
"partGroup": {
"type": "string",
"description": "Names starting with @ are reserved for editor rendering"
}
}
},
"cluster": {
"type": "object",
"required": ["type", "id", "transform", "children"],
"properties": {
"type": {
"type": "string",
"const": "cluster"
},
"id": { "type": "string" },
"name": { "type": "string" },
"transform": { "$ref": "#/$defs/transform" },
"children": {
"type": "array",
"items": {
"oneOf": [
{ "$ref": "#/$defs/key" },
{ "$ref": "#/$defs/cluster" },
{ "$ref": "#/$defs/component" }
]
}
}
}
}
}
}
```
### 使用例
```json
{
"version": "1.0.0",
"metadata": {
"name": "Split Keyboard Layout",
"author": "John Doe",
"description": "Ergonomic split keyboard with rotated thumb clusters",
"created": "2025-01-01T00:00:00Z"
},
"profiles": [
{
"id": "standard",
"name": "Standard Cherry MX",
"unitSize": { "x": 19.05, "y": 19.05 },
"visualOffset": { "x": 1, "y": 1, "radius": 1 }
}
],
"root": [
{
"type": "cluster",
"id": "left_main",
"name": "Left Main Block",
"transform": { "x": 0, "y": 0, "rotation": 0 },
"children": [
{
"type": "key",
"id": "k00",
"profile": "standard",
"transform": { "x": 0, "y": 0, "rotation": 0 },
"size": { "w": 1, "h": 1 },
"partGroup": "main",
"perKeyLED": true,
"labels": ["Q"]
},
{
"type": "key",
"id": "k01",
"profile": "standard",
"transform": { "x": 1, "y": 0, "rotation": 0 },
"size": { "w": 1, "h": 1 },
"partGroup": "main",
"perKeyLED": true,
"labels": ["W"]
}
]
},
{
"type": "cluster",
"id": "left_thumb",
"name": "Left Thumb Cluster",
"transform": { "x": 2, "y": 3, "rotation": 15 },
"children": [
{
"type": "key",
"id": "k30",
"profile": "standard",
"transform": { "x": 0, "y": 0, "rotation": 0 },
"size": { "w": 1.25, "h": 1 },
"partGroup": "thumb",
"orientation": 180,
"labels": ["Space"]
},
{
"type": "key",
"id": "k31",
"profile": "standard",
"transform": { "x": 1.25, "y": 0, "rotation": 0 },
"size": { "w": 1, "h": 1 },
"partGroup": "thumb",
"orientation": 180,
"labels": ["Enter"]
},
{
"type": "component",
"id": "enc0",
"transform": { "x": 2.25, "y": 0.5, "rotation": 0 },
"partGroup": "encoder"
}
]
},
{
"type": "key",
"id": "k_iso_enter",
"profile": "standard",
"transform": { "x": 6, "y": 1, "rotation": 0 },
"size": { "w": 1.5, "h": 1, "x2": 0.25, "y2": 1, "w2": 1.25, "h2": 1 },
"switchOffset": { "x": 0, "y": 0.5 },
"labels": ["Enter"]
},
{
"type": "component",
"id": "screw0",
"transform": { "x": 0, "y": 4.5, "rotation": 0 },
"partGroup": "screw-m2"
}
]
}
```
## 2. IDXレイヤー JSON Schema
各フィールドの意味は `IDXレイヤー.md` を正とする。
番号を振るキーの並びだけを持つ。並びの位置がキー番号になるため、欠番・重複・番号の飛びは
構造として起こらない。
### 基本構造
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://beyond-kle.org/schemas/idx-layer.json",
"title": "IDX Layer - Key Identification",
"description": "Ordered list of PHY keys; the position in the list is the key number",
"type": "object",
"required": ["version", "phyReference", "order"],
"properties": {
"version": { "type": "string" },
"phyReference": {
"type": "object",
"required": ["source", "checksum"],
"properties": {
"source": { "type": "string" },
"checksum": { "type": "string" }
}
},
"order": {
"type": "array",
"uniqueItems": true,
"items": {
"type": "string",
"description": "Reference to PHY layer key ID"
}
}
}
}
```
## 3. ELCレイヤー JSON Schema
各フィールドの意味は `ELCレイヤー.md` を正とする。
### 基本構造
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://beyond-kle.org/schemas/elc-layer.json",
"title": "ELC Layer - Electrical Wiring",
"description": "Wiring between provider pins and key terminals",
"type": "object",
"required": ["version", "phyReference", "providers", "keys"],
"properties": {
"version": { "type": "string" },
"phyReference": {
"type": "object",
"required": ["source", "checksum"],
"properties": {
"source": { "type": "string" },
"checksum": { "type": "string" }
}
},
"providers": {
"type": "array",
"items": { "$ref": "#/$defs/provider" }
},
"keys": {
"type": "array",
"items": { "$ref": "#/$defs/keyWiring" }
},
"ledChains": {
"type": "array",
"items": { "$ref": "#/$defs/ledChain" }
}
},
"$defs": {
"pinRef": {
"type": "string",
"pattern": "^(VCC|GND|[^.]+\\.[^.]+)$",
"description": "\"<provider id>.<pin name>\", or the reserved power rails VCC / GND"
},
"provider": {
"oneOf": [
{ "$ref": "#/$defs/mcuProvider" },
{ "$ref": "#/$defs/analogMuxProvider" }
]
},
"mcuProvider": {
"type": "object",
"required": ["id", "kind", "part"],
"properties": {
"id": { "type": "string" },
"kind": { "const": "mcu" },
"part": {
"type": "string",
"description": "Part number. Used to interpret pin names"
}
}
},
"analogMuxProvider": {
"type": "object",
"required": ["id", "kind", "channels", "select", "common"],
"properties": {
"id": { "type": "string" },
"kind": { "const": "analog-mux" },
"channels": {
"type": "integer",
"minimum": 1,
"description": "Number of logical pins provided. Pin names are \"0\"..\"channels-1\""
},
"select": {
"type": "array",
"items": { "$ref": "#/$defs/pinRef" },
"description": "Select pins, least significant bit first"
},
"common": { "$ref": "#/$defs/pinRef" },
"enable": { "$ref": "#/$defs/pinRef" }
}
},
"keyWiring": {
"oneOf": [
{ "$ref": "#/$defs/contactKey" },
{ "$ref": "#/$defs/analogKey" }
]
},
"contactKey": {
"type": "object",
"required": ["key", "detect", "terminals"],
"properties": {
"key": {
"type": "string",
"description": "Reference to PHY layer key ID"
},
"detect": { "const": "contact" },
"terminals": {
"type": "object",
"description": "Non-polar. a and b carry no ordering meaning",
"required": ["a", "b"],
"properties": {
"a": { "$ref": "#/$defs/pinRef" },
"b": { "$ref": "#/$defs/pinRef" }
}
},
"diode": {
"type": "string",
"enum": ["a-to-b", "b-to-a"],
"description": "Anode side comes first. Omitted means no diode"
}
}
},
"analogKey": {
"type": "object",
"required": ["key", "detect", "terminals"],
"properties": {
"key": {
"type": "string",
"description": "Reference to PHY layer key ID"
},
"detect": { "const": "analog" },
"terminals": {
"type": "object",
"required": ["out", "vcc", "gnd"],
"properties": {
"out": { "$ref": "#/$defs/pinRef" },
"vcc": { "$ref": "#/$defs/pinRef" },
"gnd": { "$ref": "#/$defs/pinRef" }
}
}
}
},
"ledChain": {
"type": "object",
"required": ["id", "data", "order"],
"properties": {
"id": { "type": "string" },
"data": { "$ref": "#/$defs/pinRef" },
"order": {
"type": "array",
"items": { "type": "string" },
"description": "PHY node IDs. The position in the array is the LED number"
}
}
}
}
}
```
## 4. MAPレイヤー JSON Schema
キーの参照にはPHYのキーIDを使う。キー番号は参照しない。
配列としての並べ替えは、ファームウェア出力時にIDXの並び順で行う。
### 基本構造
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://beyond-kle.org/schemas/map-layer.json",
"title": "MAP Layer - Keymap Configuration",
"description": "Key code assignments and layers",
"type": "object",
"required": ["version", "phyReference", "layers"],
"properties": {
"version": { "type": "string" },
"phyReference": {
"type": "object",
"required": ["source", "checksum"],
"properties": {
"source": { "type": "string" },
"checksum": { "type": "string" }
}
},
"layers": {
"type": "array",
"items": {
"$ref": "#/$defs/layer"
}
}
},
"$defs": {
"layer": {
"type": "object",
"required": ["id", "name", "keymap"],
"properties": {
"id": { "type": "integer", "minimum": 0 },
"name": { "type": "string" },
"keymap": {
"type": "array",
"items": {
"$ref": "#/$defs/keyAssignment"
}
}
}
},
"keyAssignment": {
"type": "object",
"required": ["keyId", "keycode"],
"properties": {
"keyId": {
"type": "string",
"description": "Reference to PHY layer key ID"
},
"keycode": { "type": "string" }
}
}
}
}
```
## 変換フロー設計
### PHY → IDX変換
1. PHYレイヤーからキーのリストを取得
2. 番号を振るキーを選び、並び順を決める
### PHY → ELC変換
1. ピン提供者を置き、各提供者が消費する上位ピンを指定
2. 各キーの端子の接続先を指定
3. LEDチェーンの並びを指定
### IDX + ELC + MAP → ファームウェア出力
1. ELCの配線から、ターゲットが要求する row / col を割り当て
2. IDXの並び順に従って、MAPのキーコードを配列へ並べ替え
3. VIA/QMK形式への出力
## 次のステップ
1. **型定義の作成**(言語は `implementation-plan.md` で選定)
2. **変換ロジックのプロトタイプ実装**
3. **テストケース(60%、Split等)での検証**
4. **既存KLE JSONからの変換機能**
## 未確認事項
(なし)