{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://asobilab.party/docs/ai-game-authoring/schema/game.schema.json",
  "title": "ASOBI LAB Game",
  "x-operation-target-validation": "Permission buttons may reference authored board buttons or settings.actionButtons by their existing ID. Missing IDs and wrong component kinds remain invalid. Dynamic source/destination expressions are type checked; equal static validation witnesses do not establish equal evaluated locations. Bound runtime actions retain actual value, permission and privacy validation. Schema 20/4 and field shapes unchanged.",
  "x-holder-overlap-permission": "Existing holderAllowToggle defaults to allowed when omitted. Overlap is supported only by one-row holders. Row-first hides both authoring overlap fields and the runtime toggle, rejects toggle commands and preserves stored one-row preferences without migration. Switching holderOverlap requires box-view permission and an accessible active holder, independent of box movement or turn ownership. Existing WebSocket operation, shared state, privacy projection and pending/presentation/end gates are unchanged. The controls share hover/focus/tap reveal state with holder expansion. No new authoring field or schema version.",
  "x-internal-storage": "Single-JSON import/export and schema 20/4 are unchanged. Definition and editor project are each limited to 8 MiB UTF-8; room logical storage including fixed definition is 16 MiB. Internal DB manifests/parts are server-only, never authored fields. Image, temporary execution and presentation budgets are separate.",
  "x-default-trump-layout": "New built-in trump uses item.width=97.7,height=148.3 and the front/back crop in examples/trump-default-layout.json. This is an authoring preset, not a schema default or migration; existing saved layouts and arbitrary card sizes remain valid. Schema 20/4 unchanged.",
  "x-common-call-source": "commonCall resolves commonProcessId ?? source. A configured dedicated ID is sufficient at save and execution validation; no duplicate legacy source is required. Empty IDs, unresolved process IDs and invalid arguments remain rejected. Schema 20 / operation schema 4 are unchanged. See examples/common-call-save.json.",
  "x-authoring-data-tree": "The editor projects existing player/team/tab/part/variable/marker/phase definitions as a game-rooted navigation tree. The tree is not serialized. Use-site types prune unsupported paths; disabled teams are hidden. References use existing PlayerTarget, CardSelector, CardLocation and value kinds. Schema 20 / operation schema 4 integrate typed temporary definitions and bindings into ordinary actions; the dedicated temporaryProgram action is retired.",
  "x-card-layout-size": "Card layout size inputs edit existing item.width/height in px (UI minimum 1, step 0.1). No new fields or schema version; layer percentages, font sizes and card definitions are preserved.",
  "x-editor-save-repair": "Required-selection checks apply only to authored action arrays and condition references, not cardTemplate.layers or physical box/trash objects. Repair navigation is UI-only structured path/action ID metadata. New reset UI authors game-wide reset only (mode omitted/game); existing scoped modes remain read/runtime-compatible and require explicit conversion. No schema version change.",
  "x-editor-partial-settings": "Incomplete grouped fields remain editor-local and cannot be saved. Message duration/severity without display uses runtime toast default; canonical output includes messageConfig.display. Player names and mixed text use existing player-name/value/text parts. Drag ordering preserves action IDs. No persisted schema change.",
  "x-rule-navigation-shortcuts": "Intrinsic singleton targets commit as existing actor/turn/marker-owner forms; order/count are only needed for multiple candidates. New conditions are editor-only unselected nodes until complete. Row copy renews action/branch IDs but keeps referenced IDs, unknown retained fields, and nested branches. Row reorder/history preserve identities. No schema version change.",
  "x-editor-condition-navigation": "UI uses reference navigation and marker possession choices; existing condition JSON remains valid. source ordering is legacy/internal, not a new UI choice. Incomplete required targets are rejected by package creation and publication. Literal text reading 未選択 is allowed. No schema version change.",
  "x-runtime-targeting": "Player/team tab buttons resolve instantiated IDs and actions at runtime. Piece box sources include pieces attached to physical cards currently in that box, including buried cards, and detach pieces on movement. Trash and other-zone cards are excluded. No new fields or schema version change.",
  "x-template-workspace": "cardTemplate/diceTemplate layers use frame-relative percentages. Negative x/y and coordinates/sizes above 100 are supported, widths/heights >= 1. Editor completion drops wholly outside rectangles, retaining partial overlaps. Import, list switching and runtime do not prune. Runtime/list previews still clip to the frame. No schema version change.",
  "x-box-entry-visibility": "Owned box visibility is an entry default, not a render-time override. Manual changes survive reordering and reconnects. Leaving preserves the current face/privacy; re-entry reapplies defaults. entryVisibilityBoxId is runtime-only snapshot metadata, not an authoring field. No schema version change.",
  "description": "Standard images retain exact server paths through import, save and export. /assets/dice-face-1.svg through dice-face-6.svg and /assets/numbered-dice/7.svg through 500.svg are standard dice references. Only complete matching builtin SVG content is canonicalized; arbitrary SVG remains unsupported. A marker action with source=turn and mode=move (including omitted mode) completes end/start events and updates the turn revision even for the same player. Snapshot synchronization does not retrigger events. See examples/same-player-turn.json; no new authoring field is required.",
  "$comment": "Editor fit-to-card/piece writes a box width and height of reference dimensions + 10 (5 on every side). This does not add a persisted fitting flag or change numeric bounds. Card suit and rank are optional legacy metadata; new trump presets use text variables suit and rank, which export to CSV as var:text:suit and var:text:rank. A new dice item stores image-type diceVariableDefs.image and each diceFaces[].variables.image; diceTemplate's front variable layer reads image. The editor and runtime render only this template, without an extra pip overlay. Legacy sides-only dice derive equally weighted faces and default images without changing this schema. Each duplicated player box family uses a new slot-1 ID as playerBoxGroupId for all maximum-player slots. Connected labels and inherited visibility badges are display-only, with no new persisted fields.",
  "type": "object",
  "required": [
    "schemaVersion",
    "operationSchemaVersion",
    "name",
    "version",
    "metadata",
    "tabs",
    "items",
    "background",
    "settings"
  ],
  "properties": {
    "projectLCheckpoint": false,
    "projectL": false,
    "actionFlowCheckpoint": false,
    "actionFlow": false,
    "schemaVersion": {
      "const": 20
    },
    "operationSchemaVersion": {
      "const": 4
    },
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 120
    },
    "version": {
      "type": "integer",
      "minimum": 1,
      "description": "Editor revision, distinct from the server publication version. Preserved in published definitions and exports. Legacy missing editor revisions are repaired to 1 on load."
    },
    "metadata": {
      "type": "object",
      "required": [
        "description",
        "minPlayers",
        "maxPlayers",
        "genres",
        "durationRange"
      ],
      "properties": {
        "thumbnail": {
          "type": "string"
        },
        "thumbnailOriginal": {
          "type": "string",
          "description": "復元用のサムネイル原画像参照。公開一覧には配信しない。他の画像と同じ所有権・保存管理を適用する。"
        },
        "thumbnailEncoding": {
          "const": "jpeg-960-q85-v1",
          "description": "長辺960px以内・品質85・白背景のJPEG変換済み。省略した旧データも読み込める。"
        },
        "description": {
          "type": "string",
          "maxLength": 300
        },
        "minPlayers": {
          "type": "integer",
          "minimum": 1,
          "maximum": 20
        },
        "maxPlayers": {
          "type": "integer",
          "minimum": 1,
          "maximum": 20
        },
        "genre": {
          "type": "string",
          "description": "旧入力の互換項目。現行の書き出しはgenresを使用する。"
        },
        "genres": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "uniqueItems": true
        },
        "durationMinutes": {
          "type": "integer",
          "minimum": 1
        },
        "durationRange": {
          "enum": [
            "10-30",
            "30-60",
            "60-120",
            "120-180",
            "180-plus"
          ]
        },
        "rules": {
          "type": "array",
          "maxItems": 20,
          "items": {
            "$ref": "#/$defs/rule"
          }
        }
      },
      "additionalProperties": true
    },
    "tabs": {
      "type": "array",
      "minItems": 1,
      "items": {
        "$ref": "#/$defs/tab"
      }
    },
    "items": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/item"
      }
    },
    "background": {
      "$ref": "#/$defs/background"
    },
    "settings": {
      "type": "object",
      "properties": {
        "actionFlow": false,
        "projectL": false,
        "commonProcesses": {
          "type": "array",
          "maxItems": 100,
          "items": {
            "$ref": "#/$defs/commonProcess"
          }
        },
        "activityLogEnabled": {
          "type": "boolean",
          "default": false,
          "description": "プレイヤー一覧の右へ同じ高さの動作ログを表示する。ログ本文は制作JSONに含めない。"
        },
        "sharedCursor": {
          "type": "boolean",
          "description": "falseでもカード・ピース・トークンドラッグの匿名ゴーストと同時操作ロックは有効。trueならプレイヤーのポインターも表示します。"
        },
        "sharedCursorAnonymous": {
          "type": "boolean",
          "deprecated": true,
          "description": "旧データ互換用。現在の表示には影響しません。"
        },
        "showOtherHands": {
          "type": "boolean",
          "description": "他プレイヤーの手札ウィンドウを開けるか"
        },
        "playerColors": {
          "type": "object",
          "required": [
            "colors"
          ],
          "properties": {
            "automatic": {
              "type": "boolean",
              "default": false
            },
            "colors": {
              "type": "array",
              "maxItems": 20,
              "items": {
                "type": "string",
                "pattern": "^#[0-9a-fA-F]{6}$"
              }
            }
          },
          "additionalProperties": false,
          "description": "色の自動設定は省略false。OFFではゲーム開始時のプレイヤー順（0始まりの配列）で手動色を使用。未設定スロットは標準パレット。ONでは標準パレットを使用し手動配列を保持。開始後の手番変更・再接続で参加者の色を変更しない。同じ色も指定可能。"
        },
        "playerVariables": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/playerVariable"
          }
        },
        "globalVariables": {
          "type": "array",
          "items": {
            "type": "object",
            "required": [
              "id",
              "name",
              "defaultValue"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "type": {
                "enum": [
                  "number",
                  "text"
                ],
                "default": "number"
              },
              "defaultValue": {
                "type": [
                  "string",
                  "number"
                ]
              },
              "choices": {
                "type": "array",
                "minItems": 1,
                "maxItems": 100,
                "items": {
                  "type": "object",
                  "required": [
                    "label",
                    "value"
                  ],
                  "properties": {
                    "label": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 200
                    },
                    "value": {
                      "type": "string",
                      "maxLength": 10000
                    }
                  },
                  "additionalProperties": false
                },
                "description": "準備画面のオプション。一番上が初期選択。全体変数として参照できる。数値型のvalueは有限数値の文字列。値・変数名・IDの重複は禁止。"
              }
            },
            "allOf": [
              {
                "if": {
                  "required": [
                    "type"
                  ],
                  "properties": {
                    "type": {
                      "const": "text"
                    }
                  }
                },
                "then": {
                  "properties": {
                    "defaultValue": {
                      "type": "string",
                      "maxLength": 10000
                    }
                  }
                }
              }
            ]
          }
        },
        "teams": {
          "$ref": "#/$defs/teamSettings"
        },
        "result": {
          "$ref": "#/$defs/resultSettings"
        },
        "markers": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/marker"
          }
        },
        "turn": {
          "type": "object",
          "properties": {
            "enabled": {
              "type": "boolean",
              "default": true
            },
            "playerOnly": {
              "type": "boolean",
              "default": false
            },
            "multipleAllowed": {
              "type": "boolean",
              "default": false
            },
            "allowOwnHand": {
              "type": "boolean",
              "default": true
            },
            "allowOwnTab": {
              "type": "boolean",
              "default": true
            }
          },
          "description": "Turn rules are separate from custom markers. Missing fields fall back to legacy turn marker configuration or defaults. Personal-zone exceptions never bypass explicit denials, visibility or pending-answer barriers."
        },
        "actionButtons": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/actionButton"
          }
        },
        "phaseMessages": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/phaseMessage"
          }
        },
        "cardInventory": {
          "type": "object",
          "required": [
            "title",
            "boxIds"
          ],
          "additionalProperties": false,
          "properties": {
            "enabled": {
              "type": "boolean",
              "default": true
            },
            "title": {
              "type": "string",
              "minLength": 1,
              "maxLength": 80
            },
            "boxIds": {
              "type": "array",
              "maxItems": 64,
              "uniqueItems": true,
              "items": {
                "type": "string",
                "minLength": 1
              }
            },
            "includeHand": {
              "type": "boolean",
              "default": false
            },
            "temporaryBoxIds": {
              "type": "array",
              "maxItems": 64,
              "uniqueItems": true,
              "items": {
                "type": "string",
                "minLength": 1
              }
            },
            "ownerVariable": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            },
            "ownerPlayerVariable": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100,
              "description": "所有者変数と比較する一意なプレイヤー変数。省略は参加者ID。同値が複数いる場合は一時保管を数えない。"
            }
          },
          "description": "本人の所有カードの種類と枚数を、手番や回答待ちに依存せず閲覧。個人ボックスのみ指定し、順序・実体ID・他人の内訳は配信しない。"
        }
      },
      "additionalProperties": true
    },
    "savepoints": false,
    "privateSavepoints": false
  },
  "$defs": {
    "resultValue": {
      "allOf": [
        {
          "$ref": "#/$defs/numericValue"
        },
        {
          "if": {
            "required": [
              "variableId"
            ]
          },
          "then": {
            "properties": {
              "variableId": {
                "type": "string",
                "minLength": 1
              }
            }
          }
        }
      ],
      "description": "New result UI selects named variables; existing numeric expressions remain readable. An empty variableId is an unselected field and cannot be saved."
    },
    "resultSettings": {
      "type": "object",
      "description": "Optional result configuration. gameEnd freezes results; manual abort never shows results. Only metrics explicitly listed here are public score columns. Ranking keys are independent. Current schemaVersion is 20.",
      "required": [
        "mode",
        "ranking",
        "metrics",
        "successTitle",
        "failureTitle",
        "objectives"
      ],
      "allOf": [
        {
          "if": {
            "properties": {
              "mode": {
                "const": "cooperative"
              }
            },
            "required": [
              "mode"
            ]
          },
          "then": {
            "required": [
              "successCondition"
            ]
          }
        }
      ],
      "properties": {
        "mode": {
          "enum": [
            "individual",
            "team",
            "cooperative"
          ]
        },
        "ranking": {
          "type": "array",
          "maxItems": 8,
          "items": {
            "type": "object",
            "required": [
              "value",
              "order"
            ],
            "properties": {
              "value": {
                "$ref": "#/$defs/resultValue"
              },
              "order": {
                "enum": [
                  "ascending",
                  "descending"
                ]
              }
            },
            "additionalProperties": false
          }
        },
        "metrics": {
          "type": "array",
          "maxItems": 8,
          "items": {
            "type": "object",
            "required": [
              "id",
              "label",
              "value",
              "unit"
            ],
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1
              },
              "label": {
                "type": "string",
                "minLength": 1,
                "maxLength": 80
              },
              "value": {
                "$ref": "#/$defs/resultValue"
              },
              "unit": {
                "type": "string",
                "maxLength": 20
              }
            },
            "additionalProperties": false
          }
        },
        "successCondition": {
          "$ref": "#/$defs/commonCondition"
        },
        "successTitle": {
          "type": "string",
          "minLength": 1,
          "maxLength": 100
        },
        "failureTitle": {
          "type": "string",
          "minLength": 1,
          "maxLength": 100
        },
        "messageParts": {
          "type": "array",
          "minItems": 1,
          "maxItems": 30,
          "items": {
            "$ref": "#/$defs/logMessagePart"
          }
        },
        "objectives": {
          "type": "array",
          "maxItems": 12,
          "items": {
            "type": "object",
            "required": [
              "id",
              "label",
              "condition"
            ],
            "properties": {
              "id": {
                "type": "string",
                "minLength": 1
              },
              "label": {
                "type": "string",
                "minLength": 1,
                "maxLength": 80
              },
              "condition": {
                "$ref": "#/$defs/commonCondition"
              },
              "valueParts": {
                "type": "array",
                "minItems": 1,
                "maxItems": 30,
                "items": {
                  "$ref": "#/$defs/logMessagePart"
                }
              }
            },
            "additionalProperties": false
          }
        }
      },
      "additionalProperties": false
    },
    "playerReferenceExpression": {
      "type": "object",
      "required": [
        "kind"
      ],
      "properties": {
        "kind": {
          "type": "string"
        },
        "players": {
          "$ref": "#/$defs/playerTarget"
        }
      },
      "allOf": [
        {
          "if": {
            "required": [
              "players"
            ]
          },
          "then": {
            "properties": {
              "kind": {
                "enum": [
                  "hand",
                  "player"
                ]
              }
            }
          }
        },
        {
          "if": {
            "properties": {
              "kind": {
                "const": "cardGrid"
              }
            },
            "required": [
              "kind"
            ]
          },
          "then": {
            "$ref": "#/$defs/cardGridReference"
          }
        }
      ],
      "additionalProperties": true,
      "description": "Expr hand/player may set players independently. Missing players preserves the branch target or legacy numeric action recipient. CardGrid reads either a legacy source or exactly one physical card selected through cards; runtime rejects zero/multiple cards and inaccessible grids."
    },
    "cardGridCardLocation": {
      "type": "object",
      "required": [
        "kind"
      ],
      "properties": {
        "kind": {
          "enum": [
            "all",
            "multiple",
            "board",
            "initial-cards",
            "hand",
            "team-hand",
            "player-tab",
            "team-tab",
            "box",
            "trash"
          ]
        },
        "tabId": {
          "type": "string",
          "minLength": 1,
          "maxLength": 500
        },
        "cardSetId": {
          "type": "string",
          "minLength": 1,
          "maxLength": 500
        },
        "boxId": {
          "type": "string",
          "minLength": 1,
          "maxLength": 500
        },
        "trashId": {
          "type": "string",
          "minLength": 1,
          "maxLength": 500
        },
        "players": {
          "$ref": "#/$defs/playerTarget"
        },
        "boxIds": {
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          }
        },
        "selection": {
          "$ref": "#/$defs/targetSelection"
        },
        "locations": {
          "type": "array",
          "minItems": 1,
          "maxItems": 100,
          "items": {
            "$ref": "#/$defs/cardGridCardLocation"
          }
        }
      },
      "allOf": [
        {
          "if": {
            "properties": {
              "kind": {
                "const": "multiple"
              }
            }
          },
          "then": {
            "required": [
              "locations"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "kind": {
                "enum": [
                  "board",
                  "player-tab",
                  "team-tab"
                ]
              }
            }
          },
          "then": {
            "required": [
              "tabId"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "kind": {
                "const": "initial-cards"
              }
            }
          },
          "then": {
            "required": [
              "cardSetId"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "kind": {
                "const": "trash"
              }
            }
          },
          "then": {
            "required": [
              "trashId"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "kind": {
                "const": "box"
              }
            }
          },
          "then": {
            "required": [
              "boxId"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "kind": {
                "enum": [
                  "hand",
                  "team-hand",
                  "player-tab",
                  "team-tab"
                ]
              }
            }
          },
          "then": {
            "required": [
              "players"
            ]
          }
        }
      ],
      "additionalProperties": false
    },
    "cardGridReference": {
      "type": "object",
      "required": [
        "kind"
      ],
      "properties": {
        "kind": {
          "enum": [
            "cardGrid",
            "card-grid"
          ]
        },
        "source": {
          "type": "string",
          "minLength": 1,
          "maxLength": 500
        },
        "cardSetId": {
          "type": "string",
          "minLength": 1,
          "maxLength": 500
        },
        "cards": {
          "allOf": [
            {
              "$ref": "#/$defs/cardSelector"
            },
            {
              "properties": {
                "location": {
                  "$ref": "#/$defs/cardGridCardLocation"
                },
                "targetKind": {
                  "const": "card"
                },
                "piecePartId": false
              }
            }
          ]
        },
        "gridId": {
          "type": "string",
          "minLength": 1,
          "maxLength": 500
        },
        "metric": {
          "enum": [
            "filled",
            "occupied",
            "cell-occupied",
            "variable-sum"
          ]
        },
        "row": {
          "type": "number"
        },
        "column": {
          "type": "number"
        },
        "key": {
          "type": "string",
          "minLength": 1,
          "maxLength": 500
        },
        "variableKey": {
          "type": "string",
          "minLength": 1,
          "maxLength": 500
        }
      },
      "allOf": [
        {
          "if": {
            "required": [
              "cards"
            ]
          },
          "then": {
            "properties": {
              "row": {
                "type": "integer",
                "minimum": 0
              },
              "column": {
                "type": "integer",
                "minimum": 0
              }
            }
          }
        },
        {
          "if": {
            "properties": {
              "kind": {
                "const": "cardGrid"
              }
            },
            "required": [
              "kind"
            ]
          },
          "then": {
            "properties": {
              "cardSetId": false,
              "variableKey": false
            },
            "oneOf": [
              {
                "required": [
                  "source"
                ]
              },
              {
                "required": [
                  "cards"
                ]
              }
            ]
          },
          "else": {
            "required": [
              "gridId"
            ],
            "properties": {
              "source": false,
              "key": false
            },
            "oneOf": [
              {
                "required": [
                  "cardSetId"
                ]
              },
              {
                "required": [
                  "cards"
                ]
              }
            ]
          }
        }
      ],
      "additionalProperties": false,
      "description": "Exactly one legacy fixed-part ID or cards selector. Cards must resolve to one physical card at runtime; piece targets, missing targets and ambiguous routes are rejected. Existing fixed references retain their interpretation."
    },
    "resetAction": {
      "type": "object",
      "description": "Reset preserves room membership and orders. game replaces the old continuation with initial actions and phase-1 start; other modes restore only source and continue. player/team select owners with playerTarget; source is an unprefixed definition id.",
      "properties": {
        "mode": {
          "enum": [
            "game",
            "part",
            "global",
            "player",
            "team",
            "marker"
          ],
          "default": "game"
        },
        "source": {
          "type": "string",
          "minLength": 1
        }
      },
      "if": {
        "required": [
          "mode"
        ],
        "properties": {
          "mode": {
            "enum": [
              "part",
              "global",
              "player",
              "team",
              "marker"
            ]
          }
        }
      },
      "then": {
        "required": [
          "source"
        ]
      }
    },
    "background": {
      "type": "object",
      "required": [
        "color",
        "size"
      ],
      "properties": {
        "color": {
          "type": "string"
        },
        "size": {
          "enum": [
            "landscape",
            "square",
            "portrait"
          ]
        },
        "width": {
          "type": "integer",
          "minimum": 100,
          "maximum": 10000,
          "description": "Logical board width. Omitted preserves preset width."
        },
        "height": {
          "type": "integer",
          "minimum": 100,
          "maximum": 10000,
          "description": "Logical board height. Omitted preserves preset height."
        },
        "initialViews": {
          "$comment": "When omitted: portrait starts at x0/y0, full board width and height100; desktop/landscape fits the full board. Explicit rectangles take precedence. No fields are inserted into existing data.",
          "type": "object",
          "properties": {
            "desktop": {
              "$ref": "#/$defs/boardViewRect"
            },
            "portrait": {
              "$ref": "#/$defs/boardViewRect"
            }
          },
          "additionalProperties": false,
          "description": "Local scroll layout only. Landscape phones use desktop. Rectangles must fit inside the logical board. Editor height shrinking clamps each existing view height and y inside the new board; x and width stay unchanged. Imports still reject invalid rectangles. Omitted keeps the existing start view."
        },
        "preset": {
          "enum": [
            "wood",
            "mat",
            "custom",
            "none"
          ]
        },
        "image": {
          "type": "string",
          "description": "Board background image: width 100%, proportional height, repeated from top left. Taller boards do not stretch the image vertically."
        },
        "grid": {
          "type": "boolean"
        }
      },
      "additionalProperties": true
    },
    "boardViewRect": {
      "type": "object",
      "required": [
        "x",
        "y",
        "width",
        "height"
      ],
      "properties": {
        "x": {
          "type": "number",
          "minimum": 0
        },
        "y": {
          "type": "number",
          "minimum": 0
        },
        "width": {
          "type": "number",
          "minimum": 20
        },
        "height": {
          "type": "number",
          "minimum": 20
        }
      },
      "additionalProperties": false
    },
    "tab": {
      "type": "object",
      "required": [
        "id",
        "name",
        "hidden",
        "type"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "name": {
          "type": "string"
        },
        "hidden": {
          "type": "boolean",
          "description": "ゲーム中のタブ切替を隠す倉庫用の表示フラグ。秘密権限ではなく、公開カードの処理による参照・検索・移動は許可される。カード自体や所有者付きタブの公開範囲は別に検証する。"
        },
        "type": {
          "enum": [
            "standard",
            "player",
            "team"
          ]
        },
        "otherPlayersCanView": {
          "type": "boolean"
        },
        "viewpoint": {
          "$ref": "#/$defs/boardViewpoint"
        },
        "background": {
          "$ref": "#/$defs/background"
        }
      },
      "additionalProperties": true
    },
    "boardViewpoint": {
      "type": "object",
      "required": [
        "mode"
      ],
      "properties": {
        "mode": {
          "enum": [
            "off",
            "player",
            "team"
          ],
          "default": "off"
        },
        "players": {
          "type": "array",
          "maxItems": 20,
          "items": {
            "enum": [
              "bottom",
              "right",
              "left",
              "top"
            ]
          },
          "description": "準備時に確定した座席順。未指定の座席はbottom。現在のターン順ではない。"
        },
        "teams": {
          "type": "object",
          "propertyNames": {
            "minLength": 1,
            "not": {
              "enum": [
                "__proto__",
                "prototype",
                "constructor"
              ]
            }
          },
          "additionalProperties": {
            "enum": [
              "bottom",
              "right",
              "left",
              "top"
            ]
          },
          "description": "チームIDをキーとする。チーム無効時は使用せず保持する。"
        }
      },
      "additionalProperties": false
    },
    "rule": {
      "type": "object",
      "required": [
        "id",
        "title",
        "body"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "title": {
          "type": "string",
          "maxLength": 80
        },
        "image": {
          "type": "string"
        },
        "body": {
          "type": "string",
          "maxLength": 5000
        }
      },
      "additionalProperties": false
    },
    "playerVariable": {
      "type": "object",
      "required": [
        "id",
        "name",
        "type",
        "defaultValue",
        "visible"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "name": {
          "type": "string"
        },
        "type": {
          "enum": [
            "number",
            "text"
          ]
        },
        "defaultValue": {
          "type": "string"
        },
        "visible": {
          "type": "boolean"
        },
        "publication": {
          "enum": [
            "public",
            "owner",
            "team"
          ],
          "default": "public",
          "description": "一時値による参照と参加者別投影の公開範囲。visibleとは独立。省略は全員公開。"
        },
        "image": {
          "type": "string"
        }
      },
      "allOf": [
        {
          "if": {
            "properties": {
              "visible": {
                "const": false
              }
            },
            "required": [
              "visible"
            ]
          },
          "then": {
            "not": {
              "required": [
                "image"
              ]
            }
          }
        }
      ],
      "additionalProperties": true
    },
    "marker": {
      "type": "object",
      "required": [
        "id",
        "name",
        "orderEnabled"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "name": {
          "type": "string"
        },
        "orderEnabled": {
          "type": "boolean"
        },
        "turnPlayerOnly": {
          "type": "boolean",
          "default": false,
          "description": "All current turn owners may operate by default. An explicit empty group permits no default mutations; own-hand reordering and explicit operation permissions retain their exceptions."
        },
        "multipleAllowed": {
          "type": "boolean",
          "default": false,
          "description": "Applies to the built-in turn marker too. move replaces all owners, give adds, remove removes selected owners. An empty turn group means no turn; team simultaneity selects all member players."
        },
        "ownerScope": {
          "enum": [
            "player",
            "team"
          ],
          "description": "チームの場合、選んだプレイヤーの所属チームがマーカーを所有する。turnには指定しない。"
        }
      },
      "additionalProperties": true
    },
    "teamSettings": {
      "type": "object",
      "required": [
        "enabled",
        "teams",
        "compositions"
      ],
      "properties": {
        "enabled": {
          "type": "boolean"
        },
        "automaticColors": {
          "type": "boolean",
          "default": false,
          "description": "ONは定義上のチーム順に標準パレットを解決する。team.colorの手動色は上書きせず保持。ゲーム中のチーム手番順を変えても色は変えない。"
        },
        "teams": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/teamDefinition"
          }
        },
        "compositions": {
          "type": "array",
          "description": "Counts with no authored composition use balanced capacities in team definition order. Invalid authored entries never fall back.",
          "items": {
            "$ref": "#/$defs/teamComposition"
          }
        },
        "teammatesCanViewHands": {
          "type": "boolean"
        },
        "teammatesCanViewPlayerTabs": {
          "type": "boolean"
        },
        "variables": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/playerVariable"
          }
        }
      },
      "additionalProperties": false
    },
    "teamDefinition": {
      "type": "object",
      "required": [
        "id",
        "name",
        "color"
      ],
      "properties": {
        "id": {
          "type": "string",
          "minLength": 1
        },
        "name": {
          "type": "string",
          "minLength": 1
        },
        "icon": {
          "type": "string",
          "description": "Bundled /assets/teams/default-1.svg through default-8.svg are exact trusted references retained during export. Arbitrary SVG uploads, external copies and query variants are not trusted."
        },
        "color": {
          "type": "string",
          "pattern": "^#[0-9a-fA-F]{6}$"
        }
      },
      "additionalProperties": false
    },
    "teamComposition": {
      "type": "object",
      "required": [
        "id",
        "name",
        "playerCount",
        "capacities"
      ],
      "properties": {
        "id": {
          "type": "string",
          "minLength": 1
        },
        "name": {
          "type": "string",
          "minLength": 1
        },
        "playerCount": {
          "type": "integer",
          "minimum": 1
        },
        "default": {
          "type": "boolean"
        },
        "capacities": {
          "type": "array",
          "items": {
            "type": "object",
            "required": [
              "teamId",
              "count"
            ],
            "properties": {
              "teamId": {
                "type": "string",
                "minLength": 1
              },
              "count": {
                "type": "integer",
                "minimum": 1
              }
            },
            "additionalProperties": false
          }
        }
      },
      "additionalProperties": false
    },
    "textFormat": {
      "type": "object",
      "properties": {
        "font": {
          "enum": [
            "sans",
            "serif",
            "rounded",
            "mono"
          ]
        },
        "bold": {
          "type": "boolean"
        },
        "underline": {
          "type": "boolean"
        }
      },
      "additionalProperties": false
    },
    "textRun": {
      "type": "object",
      "required": [
        "text"
      ],
      "properties": {
        "text": {
          "type": "string"
        },
        "font": {
          "enum": [
            "sans",
            "serif",
            "rounded",
            "mono"
          ]
        },
        "bold": {
          "type": "boolean"
        },
        "underline": {
          "type": "boolean"
        }
      },
      "additionalProperties": false
    },
    "formattedText": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/textRun"
      },
      "description": "文字範囲ごとの表示書式。連結した text が元の文字列と一致する場合に適用します。"
    },
    "actionButton": {
      "type": "object",
      "required": [
        "id",
        "phaseId",
        "name"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "phaseId": {
          "type": "string"
        },
        "name": {
          "type": "string"
        },
        "formattedName": {
          "$ref": "#/$defs/formattedText"
        },
        "displayCommonCondition": {
          "$ref": "#/$defs/commonCondition"
        },
        "activeCommonCondition": {
          "$ref": "#/$defs/commonCondition"
        },
        "actions": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/action"
          }
        }
      },
      "additionalProperties": true
    },
    "action": {
      "type": "object",
      "required": [
        "id",
        "type"
      ],
      "allOf": [
        {
          "if": {
            "required": [
              "type",
              "topOnly"
            ],
            "properties": {
              "type": {
                "const": "choice"
              },
              "topOnly": {
                "const": true
              }
            }
          },
          "then": {
            "required": [
              "cardSelector",
              "choice"
            ],
            "properties": {
              "targetKind": {
                "not": {
                  "const": "piece"
                }
              },
              "cardSelector": {
                "required": [
                  "condition"
                ],
                "properties": {
                  "condition": {
                    "properties": {
                      "kind": {
                        "const": "all"
                      }
                    },
                    "required": [
                      "kind"
                    ]
                  },
                  "selection": false,
                  "targetKind": {
                    "not": {
                      "const": "piece"
                    }
                  }
                }
              },
              "choice": {
                "required": [
                  "sourceKind"
                ],
                "properties": {
                  "sourceKind": {
                    "const": "card-location"
                  },
                  "candidateConditions": {
                    "maxItems": 0
                  }
                }
              }
            }
          }
        },
        {
          "if": {
            "required": [
              "cardSortKeys"
            ]
          },
          "then": {
            "properties": {
              "type": {
                "const": "sort"
              }
            }
          }
        },
        {
          "if": {
            "required": [
              "choice"
            ],
            "properties": {
              "choice": {
                "required": [
                  "display"
                ],
                "properties": {
                  "display": {
                    "const": "surface"
                  }
                }
              }
            }
          },
          "then": {
            "required": [
              "type",
              "cardSelector"
            ],
            "properties": {
              "type": {
                "const": "choice"
              },
              "targetKind": {
                "const": "card"
              },
              "cardSelector": {
                "properties": {
                  "targetKind": {
                    "const": "card"
                  }
                }
              },
              "choice": {
                "required": [
                  "sourceKind"
                ],
                "properties": {
                  "sourceKind": {
                    "const": "card-location"
                  }
                }
              }
            }
          }
        },
        {
          "if": {
            "required": [
              "choice"
            ],
            "properties": {
              "choice": {
                "anyOf": [
                  {
                    "required": [
                      "candidatePerspective"
                    ]
                  },
                  {
                    "required": [
                      "shortageBehavior"
                    ]
                  }
                ]
              }
            }
          },
          "then": {
            "required": [
              "type",
              "cardSelector"
            ],
            "properties": {
              "type": {
                "const": "choice"
              },
              "choice": {
                "required": [
                  "sourceKind"
                ],
                "properties": {
                  "sourceKind": {
                    "const": "card-location"
                  }
                }
              }
            }
          }
        },
        {
          "if": {
            "required": [
              "type"
            ],
            "properties": {
              "type": {
                "const": "openTab"
              }
            }
          },
          "then": {
            "required": [
              "tabTarget"
            ],
            "anyOf": [
              {
                "required": [
                  "playerTarget"
                ]
              },
              {
                "required": [
                  "temporaryBindings"
                ],
                "properties": {
                  "temporaryBindings": {
                    "required": [
                      "playerTarget"
                    ]
                  }
                }
              }
            ]
          }
        },
        {
          "if": {
            "required": [
              "type"
            ],
            "properties": {
              "type": {
                "const": "notice"
              }
            }
          },
          "then": {
            "required": [
              "notice",
              "playerTarget"
            ],
            "properties": {
              "choice": false,
              "inputWindow": false,
              "messageConfig": false
            },
            "anyOf": [
              {
                "required": [
                  "message"
                ]
              },
              {
                "required": [
                  "messageParts"
                ]
              },
              {
                "required": [
                  "temporaryBindings"
                ],
                "properties": {
                  "temporaryBindings": {
                    "required": [
                      "messageParts"
                    ]
                  }
                }
              }
            ]
          }
        },
        {
          "if": {
            "required": [
              "turnTeamId"
            ]
          },
          "then": {
            "required": [
              "type",
              "source"
            ],
            "properties": {
              "type": {
                "const": "marker"
              },
              "source": {
                "const": "turn"
              }
            }
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "reveal"
              }
            }
          },
          "then": {
            "required": [
              "cardSelector"
            ],
            "properties": {
              "mode": {
                "enum": [
                  "all",
                  "owner",
                  "others",
                  "team",
                  "non-team"
                ]
              },
              "cardSelector": {
                "properties": {
                  "targetKind": {
                    "const": "card"
                  }
                }
              }
            },
            "allOf": [
              {
                "if": {
                  "required": [
                    "mode"
                  ],
                  "properties": {
                    "mode": {
                      "enum": [
                        "owner",
                        "others",
                        "team",
                        "non-team"
                      ]
                    }
                  }
                },
                "then": {
                  "required": [
                    "playerTarget"
                  ]
                }
              }
            ]
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "reset"
              }
            }
          },
          "then": {
            "$ref": "#/$defs/resetAction"
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "log"
              }
            }
          },
          "then": {
            "required": [
              "logParts"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "temporary"
              }
            }
          },
          "then": false
        },
        {
          "if": {
            "required": [
              "type"
            ],
            "properties": {
              "type": {
                "const": "savepoint"
              }
            }
          },
          "then": {
            "required": [
              "savepoint"
            ]
          }
        },
        {
          "if": {
            "required": [
              "type"
            ],
            "properties": {
              "type": {
                "const": "stateCheck"
              }
            }
          },
          "then": {
            "required": [
              "stateCheck"
            ]
          }
        },
        {
          "if": {
            "required": [
              "type"
            ],
            "properties": {
              "type": {
                "const": "showCard"
              }
            }
          },
          "then": {
            "allOf": [
              {
                "anyOf": [
                  {
                    "required": [
                      "cardSelector"
                    ]
                  },
                  {
                    "required": [
                      "temporaryBindings"
                    ],
                    "properties": {
                      "temporaryBindings": {
                        "required": [
                          "cardSelector"
                        ]
                      }
                    }
                  }
                ]
              },
              {
                "anyOf": [
                  {
                    "required": [
                      "playerTarget"
                    ]
                  },
                  {
                    "required": [
                      "temporaryBindings"
                    ],
                    "properties": {
                      "temporaryBindings": {
                        "required": [
                          "playerTarget"
                        ]
                      }
                    }
                  }
                ]
              }
            ],
            "properties": {
              "cardSelector": {
                "properties": {
                  "targetKind": {
                    "const": "card"
                  }
                }
              },
              "notice": false,
              "choice": false,
              "inputWindow": false,
              "temporaryPublish": false
            }
          }
        }
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "type": {
          "type": "string",
          "description": "revealは実カードを移動せずmodeで公開範囲変更（省略all）。限定公開はplayerTargetに基準1人。cardTextChange.messagePartsは文字・変数・計算値を組み合わせた入力内容。gameEndは引数なしの終端処理。settings.resultで結果を確定し、以降の処理を実行しない。 showCardは実カード1枚を指定対象の拡大画面へ表示する。cardSelector/playerTarget必須（型付きbinding可）、message/messageParts任意。公開範囲は変更せず後続処理を待たない。"
        },
        "tabTarget": {
          "type": "object",
          "required": [
            "kind",
            "tabId"
          ],
          "properties": {
            "kind": {
              "enum": [
                "board",
                "player-tab",
                "team-tab"
              ]
            },
            "tabId": {
              "type": "string",
              "minLength": 1
            },
            "owner": {
              "$ref": "#/$defs/playerTarget"
            },
            "teamId": {
              "type": "string",
              "minLength": 1
            }
          },
          "additionalProperties": false,
          "description": "開くタブの定義ID。player/teamはowner省略で各対象者本人/所属チーム。owner指定は1人に確定させる。チームタブはteamIdでもチームを指定可能（ownerと排他）。非表示タブと閲覧権限外は拒否。"
        },
        "amount": {
          "$ref": "#/$defs/playerReferenceExpression"
        },
        "turnTeamId": {
          "type": "string",
          "minLength": 1,
          "description": "Optional progress scope for marker actions with source=turn only. Registered enabled team ID; requires turn multipleAllowed=true. Other teams keep their owners. Relative next/previous/offset uses that team's member order and last nonempty turn position. Omit for global scope. Positions are runtime-only, not authored definitions."
        },
        "runtimeTurnTeamId": false,
        "runtimeTurnTransition": false,
        "messageParts": {
          "type": "array",
          "minItems": 1,
          "maxItems": 30,
          "items": {
            "$ref": "#/$defs/logMessagePart"
          }
        },
        "messageConfig": {
          "type": "object",
          "properties": {
            "severity": {
              "enum": [
                "error",
                "warning",
                "message"
              ]
            },
            "display": {
              "enum": [
                "toast",
                "dialog",
                "log"
              ]
            },
            "durationSeconds": {
              "type": "number",
              "minimum": 1
            }
          },
          "additionalProperties": true
        },
        "notice": {
          "type": "object",
          "required": [
            "mode",
            "title"
          ],
          "properties": {
            "mode": {
              "enum": [
                "notify",
                "acknowledge"
              ]
            },
            "title": {
              "type": "string",
              "minLength": 1,
              "maxLength": 1000
            },
            "buttonLabel": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            }
          },
          "additionalProperties": false,
          "description": "Choice-free notification or confirmation. Notify continues immediately; acknowledge freezes recipients and resumes only after each presses one button. Closing is not confirmation; no timeout or automatic departure confirmation. Body uses message/messageParts and typed temporary bindings. Not written to activity logs."
        },
        "loop": {
          "type": "object",
          "required": [
            "mode"
          ],
          "properties": {
            "mode": {
              "enum": [
                "count",
                "while",
                "players",
                "array"
              ]
            },
            "count": {
              "$ref": "#/$defs/numericValue"
            },
            "condition": {
              "$ref": "#/$defs/commonCondition"
            },
            "array": {
              "$ref": "#/$defs/temporaryExpression"
            }
          },
          "additionalProperties": false,
          "allOf": [
            {
              "if": {
                "properties": {
                  "mode": {
                    "const": "array"
                  }
                }
              },
              "then": {
                "required": [
                  "array"
                ]
              }
            }
          ]
        },
        "branches": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "conditionMode": {
                "enum": [
                  "and",
                  "or"
                ]
              },
              "additionalConditions": {
                "type": "array",
                "maxItems": 4,
                "items": {
                  "$ref": "#/$defs/commonCondition"
                }
              },
              "left": {
                "$ref": "#/$defs/playerReferenceExpression"
              },
              "right": {
                "$ref": "#/$defs/playerReferenceExpression"
              },
              "actions": {
                "type": "array",
                "items": {
                  "$ref": "#/$defs/action"
                }
              }
            },
            "additionalProperties": true
          }
        },
        "elseActions": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/action"
          }
        },
        "logParts": {
          "type": "array",
          "minItems": 1,
          "maxItems": 30,
          "items": {
            "$ref": "#/$defs/logMessagePart"
          },
          "description": "Public log text and variable tokens, resolved once at execution; output is limited to 1000 characters. Explicit values are public to all participants."
        },
        "showAnimation": {
          "type": "boolean",
          "default": false,
          "description": "Move/flip/shuffle/dice/token (set or recall): online rules compute to the next input/confirmation boundary or end in one transaction, returning final state and recipient-redacted visual frames. No animation-completion API is required. Final permitted outcomes may arrive early; unauthorized secrets remain excluded. Local preview uses the same compute-first model with local canonical state and display-only playback; no animation reply resumes rules. Animated move (card/piece/token), flip, shuffle and dice play up to three staggered cues based on distinct visible target count (90ms apart, or 40ms for flip); animation off remains silent and applies at its ordered position after any preceding animation. Display checkpoints include immediate mutations, variables, turns and navigation; input/choice/confirmation becomes interactive only after preceding display steps. Schema 20/4 and author fields are unchanged. Viewing and scrolling remain available while game-state mutations are blocked."
        },
        "variableValue": {
          "$ref": "#/$defs/variableAssignmentValue"
        },
        "handVisibility": {
          "enum": [
            "all",
            "owner",
            "others",
            "team",
            "non-team"
          ]
        },
        "playerTarget": {
          "$ref": "#/$defs/playerTarget"
        },
        "choice": {
          "type": "object",
          "properties": {
            "display": {
              "enum": [
                "list",
                "buttons",
                "surface"
              ],
              "default": "list",
              "description": "surface requires physical card-location with cardSelector (cards only): select actual hand/holder/board cards and confirm in the top bar. No candidate modal."
            },
            "candidatePerspective": {
              "enum": [
                "executor",
                "respondent"
              ],
              "default": "executor",
              "description": "Physical card-location plus cardSelector only. Respondent binds selector actor to each responding player and sends only their private candidate list; the rule executor does not change."
            },
            "shortageBehavior": {
              "enum": [
                "available",
                "skip",
                "error"
              ],
              "default": "available",
              "description": "Physical card-location plus cardSelector only. Available clamps min/max to actual candidates and auto-completes empty lists. Skip returns an empty selection when below minimum. Error stops with a located reason."
            },
            "options": {
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "cardPreview": false
                },
                "additionalProperties": true
              }
            }
          },
          "additionalProperties": true
        },
        "sourceAmountMode": {
          "enum": [
            "total",
            "per-location",
            "per-destination"
          ],
          "description": "New card moves explicitly use per-destination. Omitted legacy values retain total semantics."
        },
        "cardSelector": {
          "description": "Typed selector. UI paths are not stored as strings. Predicate disclosure audits the location's candidates, not unrelated stock sharing a card definition. Hidden reserves may be explicitly moved without exposing the tab; holder transfers set the destination tab. For shuffle/sort with condition all, each physical hand, holder, pile or trash is reordered independently.",
          "type": "object",
          "properties": {
            "location": {
              "$ref": "#/$defs/cardLocation"
            },
            "condition": {
              "$ref": "#/$defs/cardCondition"
            },
            "selection": {
              "$ref": "#/$defs/targetSelection"
            }
          },
          "additionalProperties": true
        },
        "destinationLocations": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/cardLocation"
          }
        },
        "mode": {
          "type": "string"
        },
        "temporaryProgram": false,
        "temporaryDefinition": {
          "$ref": "#/$defs/temporaryDefinition"
        },
        "temporaryValue": {
          "$ref": "#/$defs/temporaryExpression"
        },
        "temporarySchemas": {
          "type": "array",
          "maxItems": 128,
          "items": {
            "$ref": "#/$defs/temporaryObjectSchema"
          }
        },
        "temporaryResult": {
          "type": "string",
          "minLength": 1
        },
        "diceTargets": {
          "type": "array",
          "uniqueItems": true,
          "items": {
            "type": "string",
            "minLength": 1
          },
          "description": "One dice action targets the entire list concurrently. Empty means no roll or wait; omitted retains source."
        },
        "cardSortKeys": {
          "type": "array",
          "maxItems": 64,
          "description": "Sort only. Priority order. Omitted/empty preserves legacy definition order/mode. Missing numeric/invalid numeric values and unset text trail in both directions; text uses deterministic code-unit order. Equal keys retain original physical order. Derived order is subject to disclosure protection.",
          "items": {
            "type": "object",
            "required": [
              "variableKey",
              "type",
              "direction"
            ],
            "properties": {
              "variableKey": {
                "type": "string",
                "minLength": 1
              },
              "type": {
                "enum": [
                  "number",
                  "text"
                ]
              },
              "direction": {
                "enum": [
                  "ascending",
                  "descending"
                ]
              }
            },
            "additionalProperties": false
          }
        },
        "destinationGridId": {
          "type": "string"
        },
        "destinationGridRow": {
          "type": "integer",
          "minimum": 0
        },
        "destinationGridColumn": {
          "type": "integer",
          "minimum": 0
        },
        "placementSuccess": {
          "type": "string",
          "minLength": 1,
          "description": "Existing boolean single temporary output ID for piece grid moves."
        },
        "placementReason": {
          "type": "string",
          "minLength": 1,
          "description": "Existing text single temporary output ID. Empty on success; otherwise first failure code."
        },
        "placementCount": {
          "type": "string",
          "minLength": 1,
          "description": "Existing number single temporary output ID. Successful pieces in sequential partial batch."
        },
        "runtimePlacementBatch": false,
        "temporaryBindings": {
          "type": "object",
          "maxProperties": 128,
          "propertyNames": {
            "pattern": "^[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*$"
          },
          "additionalProperties": {
            "$ref": "#/$defs/temporaryExpression"
          },
          "description": "Registered formal Action argument path -> typed expression. Includes choice.candidateConditions.N.value (N=0..7, existing condition only), text single; use to-text for numeric values. Authoring and runtime additionally enforce method-specific path, value type and collection. Never arbitrary writes into game data."
        },
        "temporaryPublish": {
          "type": "boolean",
          "description": "Explicit author-approved publication for variable/message/log only. Other Action types reject this field. Cross-field validation rejects definite restricted-to-public dependencies at save/read boundaries; uncertain recipients/card visibility produce saveable warnings and remain runtime-checked. Never auto-enable this flag to silence an error."
        },
        "runtimeTemporaryCheckpoint": false,
        "runtimeTemporaryScope": false,
        "cardStates": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "variables": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                },
                "description": "Physical-card variable overrides. Omitted values fall back to cardDefs variables; preserve alongside runtimeCardId when saving a running state."
              }
            },
            "additionalProperties": true
          }
        },
        "runtimeTemporaryReturnType": false,
        "savepoint": {
          "$ref": "#/$defs/savepoint"
        },
        "stateCheck": {
          "$ref": "#/$defs/stateCheck"
        },
        "topOnly": {
          "type": "boolean",
          "default": false,
          "description": "For physical card-location choices, only the first card of each source is eligible, including blind back-only selection. Requires condition all; selector selection and candidateConditions are prohibited. Existing movement/flip meanings are unchanged."
        }
      },
      "patternProperties": {
        "^runtimeTemporary": false
      },
      "additionalProperties": true
    },
    "temporaryDefinition": {
      "type": "object",
      "required": [
        "id",
        "name",
        "type",
        "collection"
      ],
      "additionalProperties": false,
      "properties": {
        "id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128
        },
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 100
        },
        "type": {
          "enum": [
            "number",
            "text",
            "boolean",
            "card",
            "player",
            "team",
            "marker",
            "part",
            "object"
          ]
        },
        "collection": {
          "enum": [
            "single",
            "array"
          ]
        },
        "nullable": {
          "type": "boolean"
        },
        "schemaId": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128
        }
      }
    },
    "commonProcess": {
      "type": "object",
      "required": [
        "id",
        "name",
        "parameters",
        "actions"
      ],
      "additionalProperties": true,
      "properties": {
        "id": {
          "type": "string",
          "minLength": 1
        },
        "name": {
          "type": "string",
          "minLength": 1
        },
        "parameters": {
          "type": "array",
          "items": {
            "type": "object"
          }
        },
        "actions": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/action"
          }
        },
        "returnType": {
          "$ref": "#/$defs/temporaryReturnType"
        },
        "returnValue": {
          "$ref": "#/$defs/temporaryExpression"
        }
      },
      "dependentRequired": {
        "returnType": [
          "returnValue"
        ],
        "returnValue": [
          "returnType"
        ]
      },
      "description": "An optional typed value is evaluated after the complete call, including input and presentation waits. A temporary operation common:<id> can assign it to resultTarget in its invocation scope. Secret visibility labels are preserved."
    },
    "temporaryReturnType": {
      "type": "object",
      "required": [
        "type",
        "collection"
      ],
      "additionalProperties": false,
      "properties": {
        "type": {
          "enum": [
            "number",
            "text",
            "boolean",
            "card",
            "player",
            "team",
            "marker",
            "part",
            "object"
          ]
        },
        "collection": {
          "enum": [
            "single",
            "array"
          ]
        },
        "nullable": {
          "type": "boolean"
        },
        "schemaId": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128
        }
      }
    },
    "temporaryObjectSchema": {
      "type": "object",
      "required": [
        "id",
        "fields"
      ],
      "additionalProperties": false,
      "properties": {
        "id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128
        },
        "fields": {
          "type": "object",
          "maxProperties": 64,
          "additionalProperties": {
            "$ref": "#/$defs/temporaryField"
          }
        }
      }
    },
    "temporaryField": {
      "type": "object",
      "required": [
        "type",
        "collection"
      ],
      "additionalProperties": false,
      "properties": {
        "type": {
          "$ref": "#/$defs/temporaryDefinition/properties/type"
        },
        "collection": {
          "enum": [
            "single",
            "array"
          ]
        },
        "nullable": {
          "type": "boolean"
        },
        "schemaId": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128
        }
      }
    },
    "temporaryValue": {
      "type": "object",
      "required": [
        "type",
        "collection",
        "data",
        "visibility"
      ],
      "additionalProperties": false,
      "properties": {
        "type": {
          "$ref": "#/$defs/temporaryDefinition/properties/type"
        },
        "collection": {
          "enum": [
            "single",
            "array"
          ]
        },
        "data": {
          "oneOf": [
            {
              "type": [
                "number",
                "string",
                "boolean",
                "null"
              ]
            },
            {
              "$ref": "#/$defs/temporaryReference"
            },
            {
              "$ref": "#/$defs/temporaryObject"
            },
            {
              "type": "array",
              "items": {
                "oneOf": [
                  {
                    "type": [
                      "number",
                      "string",
                      "boolean",
                      "null"
                    ]
                  },
                  {
                    "$ref": "#/$defs/temporaryReference"
                  },
                  {
                    "$ref": "#/$defs/temporaryObject"
                  }
                ]
              }
            }
          ]
        },
        "visibility": {
          "$ref": "#/$defs/temporaryVisibility"
        }
      }
    },
    "temporaryVisibility": {
      "type": "object",
      "required": [
        "kind"
      ],
      "properties": {
        "kind": {
          "enum": [
            "public",
            "restricted"
          ]
        },
        "viewerIds": {
          "type": "array",
          "maxItems": 1000,
          "items": {
            "type": "string",
            "minLength": 1
          }
        }
      },
      "additionalProperties": false
    },
    "temporaryReference": {
      "type": "object",
      "required": [
        "entity",
        "id"
      ],
      "properties": {
        "entity": {
          "enum": [
            "card",
            "player",
            "team",
            "marker",
            "part"
          ]
        },
        "id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128
        }
      },
      "additionalProperties": false
    },
    "temporaryObject": {
      "type": "object",
      "required": [
        "schemaId",
        "fields"
      ],
      "properties": {
        "schemaId": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128
        },
        "fields": {
          "type": "object",
          "maxProperties": 64,
          "additionalProperties": {
            "$ref": "#/$defs/temporaryValue"
          }
        }
      },
      "additionalProperties": false
    },
    "temporaryExpression": {
      "description": "Registered entity properties only. For part, property path [id] returns the runtime part ID. For player, [id] returns the existing Player.id used by choice answers (authenticated members may use their account ID); no separate account fields are exposed. Use with game.input.texts to resolve a player choice. Both retain reference/answer visibility. Card id properties remain unavailable. game.input.cards returns validated physical card references from a card-location choice with cardSelector; public answers expose selected opaque identities only, never face properties or unchosen options.",
      "oneOf": [
        {
          "type": "object",
          "required": [
            "kind",
            "value"
          ],
          "properties": {
            "kind": {
              "const": "literal"
            },
            "value": {
              "$ref": "#/$defs/temporaryValue"
            }
          },
          "additionalProperties": false
        },
        {
          "type": "object",
          "required": [
            "kind",
            "source"
          ],
          "properties": {
            "kind": {
              "const": "temporary"
            },
            "source": {
              "type": "string"
            }
          },
          "additionalProperties": false
        },
        {
          "type": "object",
          "required": [
            "kind"
          ],
          "properties": {
            "kind": {
              "enum": [
                "loop-item",
                "loop-index"
              ]
            },
            "source": {
              "type": "string",
              "description": "Optional outer loop ID. Omitted selects the nearest typed array item; save/runtime validation still requires the corresponding loop or predicate scope."
            }
          },
          "additionalProperties": false
        },
        {
          "type": "object",
          "required": [
            "kind",
            "referenceId"
          ],
          "properties": {
            "kind": {
              "const": "game-value"
            },
            "referenceId": {
              "type": "string",
              "minLength": 1,
              "maxLength": 256,
              "pattern": "^game\\.(players|actor|turn-player|turn-players|parts|cards|markers|phase|playerCount|teams|input\\.(number|text|texts|cards)|global\\..+)$",
              "description": "Registered game roots only. turn-players returns all current owners; turn-player retains the first owner or null for existing scalar references. Teams require enabled teams; global IDs must exist in this game. Not a property path or code."
            }
          },
          "additionalProperties": false
        },
        {
          "type": "object",
          "required": [
            "kind",
            "source",
            "path"
          ],
          "properties": {
            "kind": {
              "const": "property"
            },
            "source": {
              "$ref": "#/$defs/temporaryExpression"
            },
            "path": {
              "type": "array",
              "minItems": 1,
              "maxItems": 12,
              "items": {
                "type": "string",
                "minLength": 1,
                "maxLength": 128
              },
              "description": "Registered properties of the source entity type only, e.g. player hand.cards/name/variables.<ID>. Runtime rejects unregistered or unavailable fields."
            }
          },
          "additionalProperties": false
        },
        {
          "type": "object",
          "required": [
            "kind",
            "operator",
            "left",
            "right"
          ],
          "properties": {
            "kind": {
              "const": "binary"
            },
            "operator": {
              "enum": [
                "+",
                "-",
                "*",
                "/",
                "%",
                "=",
                "!=",
                ">",
                ">=",
                "<",
                "<=",
                "and",
                "or"
              ]
            },
            "left": {
              "$ref": "#/$defs/temporaryExpression"
            },
            "right": {
              "$ref": "#/$defs/temporaryExpression"
            }
          },
          "additionalProperties": false
        },
        {
          "type": "object",
          "required": [
            "kind",
            "operator",
            "value"
          ],
          "properties": {
            "kind": {
              "const": "unary"
            },
            "operator": {
              "enum": [
                "not",
                "is-null",
                "to-array",
                "to-text"
              ]
            },
            "value": {
              "$ref": "#/$defs/temporaryExpression"
            }
          },
          "additionalProperties": false
        },
        {
          "type": "object",
          "required": [
            "kind",
            "operation",
            "source"
          ],
          "properties": {
            "kind": {
              "const": "array"
            },
            "operation": {
              "enum": [
                "append",
                "concat",
                "replace",
                "remove",
                "clear",
                "filter",
                "sort",
                "unique",
                "reverse",
                "slice",
                "item",
                "first",
                "last",
                "count",
                "empty",
                "contains",
                "max",
                "min",
                "sum",
                "average",
                "all",
                "any",
                "match-count",
                "intersection",
                "equal",
                "frequency",
                "group"
              ]
            },
            "source": {
              "$ref": "#/$defs/temporaryExpression"
            },
            "other": {
              "$ref": "#/$defs/temporaryExpression"
            },
            "index": {
              "$ref": "#/$defs/temporaryExpression"
            },
            "end": {
              "$ref": "#/$defs/temporaryExpression"
            },
            "predicate": {
              "$ref": "#/$defs/temporaryExpression"
            },
            "key": {
              "$ref": "#/$defs/temporaryExpression"
            },
            "order": {
              "enum": [
                "ascending",
                "descending",
                "random"
              ]
            },
            "schemaId": {
              "type": "string"
            }
          },
          "additionalProperties": false
        }
      ]
    },
    "logMessageValue": {
      "description": "Composed text/message/log/card-input values support bounded numeric formulas using the existing operand grammar. Runtime input is not directly supported; use its typed binding or a persisted variable.",
      "allOf": [
        {
          "$ref": "#/$defs/variableAssignmentValue"
        },
        {
          "properties": {
            "kind": {
              "not": {
                "enum": [
                  "runtime-input"
                ]
              }
            }
          }
        }
      ]
    },
    "logMessagePart": {
      "oneOf": [
        {
          "type": "object",
          "required": [
            "kind",
            "text"
          ],
          "properties": {
            "kind": {
              "const": "text"
            },
            "text": {
              "type": "string",
              "maxLength": 500
            }
          },
          "additionalProperties": false
        },
        {
          "type": "object",
          "required": [
            "kind",
            "players"
          ],
          "properties": {
            "kind": {
              "const": "player-name"
            },
            "players": {
              "$ref": "#/$defs/playerTarget"
            }
          },
          "additionalProperties": false
        },
        {
          "type": "object",
          "required": [
            "kind",
            "value"
          ],
          "properties": {
            "kind": {
              "const": "value"
            },
            "value": {
              "$ref": "#/$defs/logMessageValue"
            }
          },
          "additionalProperties": false
        }
      ]
    },
    "cardLocation": {
      "type": "object",
      "required": [
        "kind"
      ],
      "properties": {
        "kind": {
          "enum": [
            "all",
            "multiple",
            "board",
            "initial-cards",
            "hand",
            "team-hand",
            "player-tab",
            "team-tab",
            "box",
            "trash"
          ]
        },
        "boxId": {
          "type": "string",
          "description": "For a player- or team-box family, use the slot-1 shared item ID. An existing individual ID without players still addresses that box."
        },
        "players": {
          "$ref": "#/$defs/playerTarget",
          "description": "Required for hand, team-hand, owned box, player-tab, or team-tab locations. Team targets map selected players to distinct teams. assignment-target resolves separately for each destination player."
        },
        "tabId": {
          "type": "string"
        },
        "cardSetId": {
          "type": "string"
        },
        "trashId": {
          "type": "string"
        },
        "boxIds": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "selection": {
          "$ref": "#/$defs/targetSelection",
          "description": "Optional ordering and limit for a multiple location list."
        },
        "locations": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/cardLocation"
          }
        }
      },
      "allOf": [
        {
          "if": {
            "properties": {
              "kind": {
                "const": "box"
              }
            },
            "required": [
              "kind"
            ]
          },
          "then": {
            "required": [
              "boxId"
            ]
          }
        }
      ],
      "additionalProperties": true
    },
    "targetSelection": {
      "type": "object",
      "required": [
        "order",
        "count"
      ],
      "properties": {
        "order": {
          "enum": [
            "source",
            "ascending",
            "descending",
            "random"
          ]
        },
        "key": {
          "type": "string",
          "minLength": 1
        },
        "count": {
          "oneOf": [
            {
              "const": "all"
            },
            {
              "type": "integer",
              "minimum": 1
            }
          ]
        }
      },
      "allOf": [
        {
          "if": {
            "properties": {
              "order": {
                "enum": [
                  "ascending",
                  "descending"
                ]
              }
            },
            "required": [
              "order"
            ]
          },
          "then": {
            "required": [
              "key"
            ]
          }
        }
      ],
      "additionalProperties": false
    },
    "playerTarget": {
      "type": "object",
      "required": [
        "kind"
      ],
      "properties": {
        "kind": {
          "enum": [
            "ids",
            "multiple",
            "actor",
            "loop-player",
            "button-owner",
            "part-owner",
            "assignment-target",
            "all",
            "player-variable",
            "marker-owner",
            "turn",
            "team-turn",
            "select",
            "and",
            "or",
            "team"
          ]
        },
        "targets": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/playerTarget"
          }
        },
        "operator": {
          "enum": [
            ">",
            ">=",
            "=",
            "!=",
            "<=",
            "<",
            "in-right",
            "max",
            "min"
          ]
        },
        "condition": {
          "$ref": "#/$defs/playerTarget"
        },
        "selection": {
          "$ref": "#/$defs/targetSelection"
        },
        "basis": {
          "enum": [
            "current",
            "order"
          ]
        },
        "offset": {
          "type": "integer"
        },
        "index": {
          "type": "integer"
        },
        "teamId": {
          "type": "string",
          "minLength": 1
        },
        "teamPosition": {
          "enum": [
            "current",
            "next",
            "previous",
            "offset"
          ]
        },
        "teamOffset": {
          "type": "integer"
        },
        "memberPosition": {
          "enum": [
            "first",
            "current",
            "next",
            "previous",
            "offset"
          ]
        },
        "memberOffset": {
          "type": "integer"
        }
      },
      "allOf": [
        {
          "if": {
            "properties": {
              "kind": {
                "const": "select"
              }
            },
            "required": [
              "kind"
            ]
          },
          "then": {
            "required": [
              "condition",
              "selection"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "kind": {
                "enum": [
                  "and",
                  "or"
                ]
              }
            },
            "required": [
              "kind"
            ]
          },
          "then": {
            "required": [
              "targets"
            ],
            "properties": {
              "targets": {
                "minItems": 1,
                "maxItems": 100
              }
            }
          }
        },
        {
          "if": {
            "properties": {
              "kind": {
                "const": "team"
              }
            },
            "required": [
              "kind"
            ]
          },
          "then": {
            "anyOf": [
              {
                "required": [
                  "teamId"
                ]
              },
              {
                "required": [
                  "basis",
                  "index"
                ]
              }
            ]
          }
        }
      ],
      "additionalProperties": true
    },
    "numericValue": {
      "allOf": [
        {
          "$ref": "#/$defs/variableAssignmentValue"
        },
        {
          "properties": {
            "kind": {
              "not": {
                "enum": [
                  "fixed-text",
                  "runtime-input"
                ]
              }
            }
          }
        }
      ]
    },
    "numericFormula": {
      "type": "object",
      "required": [
        "kind",
        "tokens"
      ],
      "properties": {
        "kind": {
          "const": "formula"
        },
        "rounding": {
          "enum": [
            "none",
            "floor",
            "round",
            "ceil"
          ]
        },
        "tokens": {
          "type": "array",
          "minItems": 1,
          "maxItems": 60,
          "items": {
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "kind",
                  "value"
                ],
                "properties": {
                  "kind": {
                    "const": "value"
                  },
                  "value": {
                    "allOf": [
                      {
                        "$ref": "#/$defs/variableAssignmentValue"
                      },
                      {
                        "properties": {
                          "kind": {
                            "not": {
                              "enum": [
                                "formula",
                                "fixed-text",
                                "runtime-input"
                              ]
                            }
                          }
                        }
                      }
                    ]
                  }
                },
                "additionalProperties": false
              },
              {
                "type": "object",
                "required": [
                  "kind",
                  "operator"
                ],
                "properties": {
                  "kind": {
                    "const": "operator"
                  },
                  "operator": {
                    "enum": [
                      "+",
                      "-",
                      "*",
                      "/"
                    ]
                  }
                },
                "additionalProperties": false
              },
              {
                "type": "object",
                "required": [
                  "kind",
                  "value"
                ],
                "properties": {
                  "kind": {
                    "const": "parenthesis"
                  },
                  "value": {
                    "enum": [
                      "(",
                      ")"
                    ]
                  }
                },
                "additionalProperties": false
              }
            ]
          }
        }
      },
      "additionalProperties": false
    },
    "variableAssignmentValue": {
      "oneOf": [
        {
          "allOf": [
            {
              "$ref": "#/$defs/cardGridReference"
            },
            {
              "properties": {
                "kind": {
                  "const": "card-grid"
                }
              }
            }
          ]
        },
        {
          "$ref": "#/$defs/numericFormula"
        },
        {
          "type": "object",
          "required": [
            "kind",
            "value"
          ],
          "properties": {
            "kind": {
              "const": "fixed"
            },
            "value": {
              "type": "number"
            }
          },
          "additionalProperties": false
        },
        {
          "type": "object",
          "required": [
            "kind",
            "value"
          ],
          "properties": {
            "kind": {
              "const": "fixed-text"
            },
            "value": {
              "type": "string",
              "maxLength": 500
            }
          },
          "additionalProperties": false
        },
        {
          "type": "object",
          "required": [
            "kind"
          ],
          "properties": {
            "kind": {
              "const": "participant-count"
            }
          },
          "additionalProperties": false
        },
        {
          "type": "object",
          "required": [
            "kind",
            "variableId"
          ],
          "properties": {
            "kind": {
              "enum": [
                "global-variable",
                "player-count-variable",
                "player-variable",
                "team-variable"
              ]
            },
            "variableId": {
              "type": "string"
            },
            "players": {
              "type": "object"
            }
          },
          "additionalProperties": true
        },
        {
          "type": "object",
          "required": [
            "kind",
            "cardSetId",
            "variableKey",
            "cards",
            "aggregate"
          ],
          "properties": {
            "kind": {
              "const": "card-variable"
            },
            "cardSetId": {
              "type": "string"
            },
            "variableKey": {
              "type": "string"
            },
            "cards": {
              "type": "object"
            },
            "aggregate": {
              "enum": [
                "top",
                "sum",
                "minimum",
                "maximum",
                "average"
              ]
            }
          },
          "additionalProperties": true
        },
        {
          "type": "object",
          "required": [
            "kind",
            "partId",
            "variableKey"
          ],
          "properties": {
            "kind": {
              "const": "dice-variable"
            },
            "partId": {
              "type": "string"
            },
            "variableKey": {
              "type": "string"
            }
          },
          "additionalProperties": false
        },
        {
          "type": "object",
          "required": [
            "kind"
          ],
          "properties": {
            "kind": {
              "enum": [
                "counter",
                "dice",
                "timer-seconds",
                "card-count",
                "hand-count"
              ]
            }
          },
          "additionalProperties": true
        }
      ]
    },
    "cardCondition": {
      "oneOf": [
        {
          "type": "object",
          "required": [
            "kind"
          ],
          "properties": {
            "kind": {
              "enum": [
                "all",
                "selected-by-actor"
              ]
            }
          },
          "additionalProperties": false
        },
        {
          "type": "object",
          "required": [
            "kind",
            "cardSetId"
          ],
          "properties": {
            "kind": {
              "const": "card-type"
            },
            "cardSetId": {
              "type": "string",
              "minLength": 1,
              "maxLength": 500
            },
            "cardDefinitionIds": {
              "type": "array",
              "minItems": 1,
              "maxItems": 1000,
              "uniqueItems": true,
              "items": {
                "type": "string",
                "minLength": 1,
                "maxLength": 500
              }
            }
          },
          "additionalProperties": false
        },
        {
          "type": "object",
          "required": [
            "kind",
            "conditions"
          ],
          "properties": {
            "kind": {
              "enum": [
                "and",
                "or"
              ]
            },
            "conditions": {
              "type": "array",
              "minItems": 1,
              "maxItems": 100,
              "items": {
                "$ref": "#/$defs/cardCondition"
              }
            }
          },
          "additionalProperties": false
        },
        {
          "type": "object",
          "required": [
            "kind",
            "cardSetId",
            "variableKey",
            "operator",
            "value"
          ],
          "properties": {
            "kind": {
              "const": "card-variable"
            },
            "cardSetId": {
              "type": "string"
            },
            "variableKey": {
              "type": "string"
            },
            "operator": {
              "enum": [
                "=",
                "!=",
                ">",
                "<",
                ">=",
                "<=",
                "in-right"
              ]
            },
            "value": {
              "$ref": "#/$defs/variableAssignmentValue"
            }
          },
          "additionalProperties": false
        }
      ]
    },
    "cardSelector": {
      "type": "object",
      "required": [
        "location",
        "condition"
      ],
      "properties": {
        "location": {
          "type": "object"
        },
        "condition": {
          "$ref": "#/$defs/cardCondition"
        },
        "selection": {
          "$ref": "#/$defs/targetSelection"
        },
        "targetKind": {
          "enum": [
            "card",
            "piece"
          ]
        },
        "piecePartId": {
          "type": "string"
        }
      },
      "additionalProperties": false
    },
    "buttonStateCondition": {
      "type": "object",
      "required": [
        "kind",
        "predicate"
      ],
      "properties": {
        "kind": {
          "const": "button-state"
        },
        "players": {
          "$ref": "#/$defs/playerTarget",
          "description": "Optional. Defaults to actor. Supported on non-selection hand, variable, and marker predicates. Every selected player must satisfy the condition; empty selection is false."
        },
        "predicate": {
          "enum": [
            "always",
            "never",
            "marker-owner",
            "marker-not-owner",
            "hand-selected",
            "holder-selected",
            "hand-count",
            "player-variable",
            "timer-seconds",
            "selected-hand-variable",
            "selected-hand-card",
            "hand-card",
            "hand-card-variable"
          ]
        },
        "cardSetId": {
          "type": "string"
        },
        "cardDefinitionId": {
          "type": "string"
        },
        "cardDefinitionIds": {
          "type": "array",
          "minItems": 1,
          "maxItems": 1000,
          "uniqueItems": true,
          "items": {
            "type": "string",
            "minLength": 1
          }
        },
        "cardPresence": {
          "enum": [
            "has",
            "not"
          ]
        },
        "cardVariableKey": {
          "type": "string"
        },
        "selectionMode": {
          "enum": [
            "any",
            "all",
            "sum",
            "count"
          ]
        },
        "comparisonValueKind": {
          "enum": [
            "number",
            "text"
          ]
        },
        "value": {
          "type": "number"
        },
        "textValue": {
          "type": "string",
          "maxLength": 500
        },
        "comparisonValues": {
          "type": "array",
          "maxItems": 19,
          "items": {
            "oneOf": [
              {
                "type": "number"
              },
              {
                "type": "string",
                "maxLength": 500
              }
            ]
          }
        },
        "operator": {
          "enum": [
            "=",
            "!=",
            ">",
            ">=",
            "<",
            "<=",
            "in-right",
            "one-of"
          ]
        }
      },
      "allOf": [
        {
          "if": {
            "properties": {
              "predicate": {
                "enum": [
                  "selected-hand-card",
                  "hand-card"
                ]
              }
            },
            "required": [
              "predicate"
            ]
          },
          "then": {
            "anyOf": [
              {
                "required": [
                  "cardDefinitionIds"
                ]
              },
              {
                "required": [
                  "cardDefinitionId"
                ]
              }
            ]
          }
        }
      ],
      "additionalProperties": true
    },
    "commonCondition": {
      "oneOf": [
        {
          "$ref": "#/$defs/buttonStateCondition"
        },
        {
          "type": "object",
          "required": [
            "kind",
            "phaseId"
          ],
          "properties": {
            "kind": {
              "const": "current-phase"
            },
            "phaseId": {
              "type": "string",
              "minLength": 1
            }
          },
          "additionalProperties": false
        },
        {
          "type": "object",
          "required": [
            "kind",
            "left",
            "operator",
            "right"
          ],
          "properties": {
            "kind": {
              "const": "comparison"
            },
            "left": {
              "type": "object",
              "allOf": [
                {
                  "if": {
                    "required": [
                      "kind"
                    ],
                    "properties": {
                      "kind": {
                        "const": "card-count"
                      }
                    }
                  },
                  "then": {
                    "required": [
                      "cards"
                    ],
                    "properties": {
                      "cards": {
                        "$ref": "#/$defs/cardSelector"
                      }
                    }
                  }
                }
              ]
            },
            "operator": {
              "enum": [
                "=",
                "!=",
                ">",
                ">=",
                "<",
                "<=",
                "in-right",
                "one-of"
              ]
            },
            "right": {
              "type": "object"
            },
            "rightValues": {
              "type": "array",
              "maxItems": 19,
              "items": {
                "type": "object"
              }
            }
          },
          "additionalProperties": true
        },
        {
          "type": "object",
          "required": [
            "kind",
            "conditions"
          ],
          "properties": {
            "kind": {
              "enum": [
                "and",
                "or"
              ]
            },
            "conditions": {
              "type": "array",
              "minItems": 1,
              "items": {
                "$ref": "#/$defs/commonCondition"
              }
            }
          },
          "additionalProperties": true
        },
        {
          "type": "object",
          "required": [
            "kind",
            "condition"
          ],
          "properties": {
            "kind": {
              "const": "not"
            },
            "condition": {
              "$ref": "#/$defs/commonCondition"
            }
          },
          "additionalProperties": true
        }
      ]
    },
    "phaseMessage": {
      "type": "object",
      "required": [
        "id",
        "phaseId",
        "audience",
        "parts"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "phaseId": {
          "type": "string"
        },
        "audience": {
          "type": "object"
        },
        "displayCommonCondition": {
          "$ref": "#/$defs/commonCondition"
        },
        "parts": {
          "type": "array",
          "maxItems": 30,
          "items": {
            "$ref": "#/$defs/phaseMessagePart"
          }
        }
      },
      "additionalProperties": false
    },
    "phaseMessagePart": {
      "oneOf": [
        {
          "type": "object",
          "required": [
            "kind",
            "text"
          ],
          "properties": {
            "kind": {
              "const": "text"
            },
            "text": {
              "type": "string",
              "maxLength": 500
            },
            "formattedText": {
              "$ref": "#/$defs/formattedText"
            }
          },
          "additionalProperties": false
        },
        {
          "type": "object",
          "required": [
            "kind"
          ],
          "properties": {
            "kind": {
              "const": "participant-count"
            },
            "format": {
              "$ref": "#/$defs/textFormat"
            }
          },
          "additionalProperties": false
        },
        {
          "type": "object",
          "required": [
            "kind",
            "id"
          ],
          "properties": {
            "kind": {
              "enum": [
                "global-variable",
                "player-variable",
                "counter",
                "timer"
              ]
            },
            "id": {
              "type": "string"
            },
            "format": {
              "$ref": "#/$defs/textFormat"
            }
          },
          "additionalProperties": false
        },
        {
          "type": "object",
          "required": [
            "kind",
            "id",
            "variableKey"
          ],
          "properties": {
            "kind": {
              "const": "card-variable"
            },
            "id": {
              "type": "string"
            },
            "variableKey": {
              "type": "string"
            },
            "format": {
              "$ref": "#/$defs/textFormat"
            }
          },
          "additionalProperties": false
        }
      ]
    },
    "item": {
      "type": "object",
      "patternProperties": {
        "^runtimeTemporary": false
      },
      "required": [
        "id",
        "kind",
        "tab",
        "name",
        "x",
        "y",
        "width",
        "height"
      ],
      "allOf": [
        {
          "if": {
            "required": [
              "textParts"
            ]
          },
          "then": {
            "properties": {
              "kind": {
                "const": "text"
              }
            }
          }
        },
        {
          "if": {
            "required": [
              "textContent"
            ]
          },
          "then": {
            "properties": {
              "kind": {
                "const": "text"
              }
            }
          }
        },
        {
          "if": {
            "required": [
              "timerEndPhaseIds"
            ]
          },
          "then": {
            "properties": {
              "kind": {
                "const": "timer"
              }
            }
          }
        },
        {
          "if": {
            "required": [
              "kind",
              "actions"
            ],
            "properties": {
              "kind": {
                "const": "timer"
              },
              "actions": {
                "type": "array",
                "minItems": 1
              }
            }
          },
          "then": {
            "required": [
              "timerMode"
            ],
            "properties": {
              "timerMode": {
                "const": "down"
              }
            }
          }
        }
      ],
      "properties": {
        "runtimeTemporaryScope": false,
        "runtimeTemporaryReturnType": false,
        "textParts": {
          "type": "array",
          "minItems": 1,
          "maxItems": 30,
          "items": {
            "$ref": "#/$defs/logMessagePart"
          },
          "description": "Text component only. Live concatenation of text/player names/values; actor means viewer. Does not overwrite item.name."
        },
        "textContent": {
          "type": "string",
          "description": "Text component fixed display content. item.name is the editable component/reference name. Omitted textContent falls back to name for old games; an empty string deliberately displays no text. textParts takes precedence at runtime."
        },
        "textOutline": {
          "type": "boolean",
          "default": false,
          "description": "Draw an outline around text glyphs, not an outer component border. Also used by counters."
        },
        "textOutlineColor": {
          "type": "string",
          "pattern": "^#[0-9a-fA-F]{6}$",
          "default": "#ffffff"
        },
        "textOutlineWidth": {
          "type": "number",
          "minimum": 0.5,
          "maximum": 5,
          "default": 1,
          "description": "Text component glyph stroke width in board pixels."
        },
        "id": {
          "type": "string"
        },
        "kind": {
          "enum": [
            "card",
            "box",
            "board",
            "button",
            "text",
            "dice",
            "counter",
            "timer",
            "trash",
            "piece",
            "token"
          ]
        },
        "tab": {
          "type": "string"
        },
        "name": {
          "type": "string"
        },
        "formattedText": {
          "$ref": "#/$defs/formattedText"
        },
        "x": {
          "type": "number"
        },
        "y": {
          "type": "number"
        },
        "width": {
          "type": "number",
          "exclusiveMinimum": 0
        },
        "height": {
          "type": "number",
          "exclusiveMinimum": 0
        },
        "rotation": {
          "type": "number",
          "default": 0,
          "description": "回転角（度）。省略時は0。読み込み・描画・座標計算で共通。"
        },
        "handExitFace": {
          "enum": [
            "front",
            "back",
            "choose"
          ],
          "description": "Shared manual hand placement face for this card part. Missing means back. A non-all owned box overrides this choice."
        },
        "cardDefs": {
          "type": "array",
          "maxItems": 500,
          "items": {
            "type": "object",
            "not": {
              "required": [
                "handExitFace"
              ]
            },
            "properties": {
              "description": {
                "type": "string",
                "description": "Plain-text multiline card details. Missing or empty means no description. Only viewers allowed to see the card front may inspect it."
              }
            },
            "additionalProperties": true
          }
        },
        "diceFaces": {
          "type": "array",
          "description": "Edited through the shared card/dice list. CSV item-key is the face ID; copy-mode must be fixed. Reordering preserves the currently selected face ID.",
          "minItems": 1,
          "maxItems": 500,
          "items": {
            "$ref": "#/$defs/diceFace"
          }
        },
        "sides": {
          "type": "integer",
          "minimum": 1,
          "maximum": 500
        },
        "diceVariableDefs": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/diceVariableDef"
          }
        },
        "diceTemplate": {
          "type": "object",
          "description": "Shared front-side template for all dice faces. Back, input, drawing, and piece-grid layers are unsupported.",
          "additionalProperties": true
        },
        "diceRollId": {
          "type": "string",
          "minLength": 1,
          "maxLength": 80,
          "description": "Runtime-only roll marker for repeated identical results; omit from authoring JSON."
        },
        "tokenAffiliation": {
          "enum": [
            "none",
            "player",
            "team"
          ],
          "description": "player creates participating-player tokens. On a team tab only members of that tab's team are instantiated; authored slots retain global participant order. Existing standard-tab behavior is unchanged."
        },
        "tokenMovement": {
          "description": "Manual movement uses tokens:move. Own player-affiliated tokens may move within the same own-team tab outside turn; explicit denies, ownership, pending and completed gates remain.",
          "enum": [
            "none",
            "owner",
            "anyone"
          ]
        },
        "tokenAnonymous": {
          "type": "boolean"
        },
        "tokenCount": {
          "type": "integer",
          "minimum": 1,
          "maximum": 99
        },
        "tokenGroupId": {
          "type": "string",
          "minLength": 1
        },
        "tokenSlot": {
          "type": "integer",
          "minimum": 1,
          "maximum": 99
        },
        "tokenOwnerId": {
          "type": "string",
          "description": "Runtime only; omit from authoring data"
        },
        "counterControlsEnabled": {
          "type": "boolean",
          "default": true,
          "description": "false hides the counter +/- controls and blocks direct player changes; automation actions remain available. Omission preserves enabled controls."
        },
        "diceCanRoll": {
          "type": "boolean",
          "description": "false blocks manual player rolling but not automation actions"
        },
        "holderHorizontalAlign": {
          "enum": [
            "left",
            "center",
            "right"
          ],
          "default": "center",
          "description": "Holder viewport horizontal alignment; omission preserves center. Does not change card order or visibility."
        },
        "holderVerticalAlign": {
          "enum": [
            "top",
            "center",
            "bottom"
          ],
          "default": "center",
          "description": "Holder viewport vertical alignment; omission preserves center. Applies to one row and wrapped rows."
        },
        "boxShufflePosition": {
          "enum": [
            "outside-top",
            "outside-right",
            "outside-left",
            "outside-bottom"
          ]
        },
        "timerShowDuringCardEdit": {
          "type": "boolean"
        },
        "timerMode": {
          "enum": [
            "up",
            "down"
          ]
        },
        "timerInitial": {
          "type": "number",
          "minimum": 0
        },
        "timerEndPhaseIds": {
          "type": "array",
          "uniqueItems": true,
          "items": {
            "type": "string",
            "minLength": 1
          },
          "description": "Countdown only. Empty/omitted means all phases. Checked at expiry and immediately before actions; mismatches are consumed, not deferred. Timer actions are ordinary item.actions. Automatic events have no participant actor."
        },
        "buttonShape": {
          "enum": [
            "circle",
            "square"
          ]
        },
        "backgroundEnabled": {
          "type": "boolean",
          "description": "For board buttons, omission/true shows the background. false hides the color, border and shadow but retains text/image and interaction. A blue dashed guide is shown only in the editor. Also used by board, text and box appearance with their respective defaults."
        },
        "buttonText": {
          "type": "string",
          "maxLength": 120
        },
        "buttonFormattedText": {
          "$ref": "#/$defs/formattedText"
        },
        "buttonTextColor": {
          "type": "string",
          "pattern": "^#[0-9a-fA-F]{6}$"
        },
        "buttonFontSize": {
          "type": "integer",
          "minimum": 1,
          "maximum": 200
        },
        "displayCommonCondition": {
          "$ref": "#/$defs/commonCondition"
        },
        "activeCommonCondition": {
          "$ref": "#/$defs/commonCondition"
        },
        "visibilityMode": {
          "enum": [
            "all",
            "owner",
            "others",
            "team",
            "non-team"
          ]
        },
        "playerBoxGroupId": {
          "type": "string",
          "description": "Player-owned family root ID for every part kind; historical key retained for compatibility."
        },
        "playerBoxShowName": {
          "type": "boolean",
          "default": true,
          "description": "Per individual player-owned part seat of any kind. Omission is visible; new slots inherit their base setting. Shared definition synchronization preserves each seat's own value; hidden seats cannot own the connected group's label. Does not affect card visibility badges."
        },
        "playerBoxSlot": {
          "type": "integer",
          "minimum": 1,
          "maximum": 99,
          "description": "Player-owned copies of any part kind belong to standard tabs. Slot 1 owns shared defaults, each slot retains position and optional name-display override; only participating slots run."
        },
        "playerBoxInitialVisibility": {
          "enum": [
            "all",
            "owner",
            "others"
          ],
          "description": "Also supported by ordinary boxes in player tabs. Omission preserves legacy card faces and holderPrivate. Does not grant tab access."
        },
        "teamBoxGroupId": {
          "type": "string"
        },
        "teamBoxSlot": {
          "type": "integer",
          "minimum": 1,
          "maximum": 99,
          "description": "Team boxes belong only to standard tabs. Slot 1 owns shared settings; sibling slots retain independent x, y and teamBoxShowName."
        },
        "teamBoxShowName": {
          "type": "boolean",
          "default": true,
          "description": "Per-team-box name and icon display. Omission means visible; independent of shared box settings and card visibility."
        },
        "teamBoxInitialVisibility": {
          "enum": [
            "all",
            "team",
            "non-team"
          ],
          "description": "Also supported by ordinary boxes in team tabs, without teamBoxSlot. Omission preserves legacy display."
        }
      },
      "additionalProperties": true
    },
    "diceFace": {
      "type": "object",
      "required": [
        "id",
        "name",
        "copies",
        "variables"
      ],
      "properties": {
        "id": {
          "type": "string",
          "minLength": 1
        },
        "name": {
          "type": "string"
        },
        "copies": {
          "type": "integer",
          "minimum": 1,
          "maximum": 10000
        },
        "variables": {
          "type": "object",
          "additionalProperties": {
            "type": "string"
          }
        }
      },
      "additionalProperties": false
    },
    "diceVariableDef": {
      "type": "object",
      "required": [
        "name",
        "type"
      ],
      "properties": {
        "name": {
          "type": "string",
          "minLength": 1
        },
        "type": {
          "enum": [
            "text",
            "number",
            "image"
          ]
        }
      },
      "additionalProperties": true
    },
    "placementRules": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "minAddedPieces": {
          "type": "integer",
          "minimum": 0,
          "maximum": 10000
        },
        "maxAddedPieces": {
          "type": "integer",
          "minimum": 0,
          "maximum": 10000
        },
        "maxPerCard": {
          "type": "integer",
          "minimum": 0,
          "maximum": 10000
        },
        "preserveExistingPlacements": {
          "type": "boolean"
        },
        "onlyStartingPieces": {
          "type": "boolean"
        },
        "startingPieceSelector": {
          "$ref": "#/$defs/cardSelector"
        },
        "targetCardSelector": {
          "$ref": "#/$defs/cardSelector"
        }
      }
    },
    "operationGuard": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "buttonIds",
        "manual"
      ],
      "properties": {
        "buttonIds": {
          "type": "array",
          "maxItems": 100,
          "items": {
            "type": "string",
            "minLength": 1,
            "maxLength": 512
          }
        },
        "manual": {
          "enum": [
            "none",
            "pieces",
            "all"
          ]
        },
        "placement": {
          "$ref": "#/$defs/placementRules"
        }
      }
    },
    "savepoint": {
      "description": "全員分のゲーム状態を保存し、同じ評価で作成後に到達した選択・入力・確認待ちと私有継続も保存する。ownerは復元権限だけを指定し、実行者や回答者の一致は不要。復元時は要求IDを更新し、新規秘密情報による失効を維持する。",
      "type": "object",
      "additionalProperties": false,
      "required": [
        "key",
        "mode"
      ],
      "properties": {
        "key": {
          "type": "string",
          "pattern": "^[-a-zA-Z0-9_]{1,64}$",
          "not": {
            "enum": [
              "__proto__",
              "prototype",
              "constructor"
            ]
          }
        },
        "mode": {
          "enum": [
            "create",
            "restore",
            "discard",
            "guard"
          ]
        },
        "owner": {
          "enum": [
            "actor",
            "turn"
          ]
        },
        "informationPolicy": {
          "enum": [
            "invalidate",
            "allow"
          ]
        },
        "guard": {
          "$ref": "#/$defs/operationGuard"
        },
        "restoreGuard": {
          "$ref": "#/$defs/operationGuard"
        }
      },
      "allOf": [
        {
          "if": {
            "properties": {
              "mode": {
                "enum": [
                  "restore",
                  "discard"
                ]
              }
            }
          },
          "then": {
            "properties": {
              "owner": false,
              "informationPolicy": false,
              "guard": false,
              "restoreGuard": false
            }
          }
        },
        {
          "if": {
            "properties": {
              "mode": {
                "const": "guard"
              }
            }
          },
          "then": {
            "properties": {
              "owner": false,
              "informationPolicy": false,
              "restoreGuard": false
            }
          }
        }
      ]
    },
    "stateCheck": {
      "type": "object",
      "additionalProperties": false,
      "anyOf": [
        {
          "required": [
            "key"
          ]
        },
        {
          "required": [
            "condition"
          ]
        }
      ],
      "properties": {
        "key": {
          "type": "string",
          "pattern": "^[-a-zA-Z0-9_]{1,64}$",
          "not": {
            "enum": [
              "__proto__",
              "prototype",
              "constructor"
            ]
          }
        },
        "condition": {
          "$ref": "#/$defs/commonCondition"
        },
        "message": {
          "type": "string",
          "maxLength": 1000
        }
      }
    }
  },
  "additionalProperties": true
}
