# ゲームを完成させるための設計・実装パターン

この資料は現在の作者向け契約です。将来案や内部の運用手順を、利用可能なJSON仕様として扱いません。[登録表](reference-contract.md)、[データ仕様](game-data-spec.md)、[処理仕様](action-reference.md)、[サンプル目録](examples/README.md)と併せて使ってください。

## 定義・実行状態・画像の境界

配布JSONはルールと部品の定義です。実際の参加者ID、部屋ID、実行中のカード個体ID、入力待ち、確認要求、runtimeTemporary*、runtimeLoop*を埋め込みません。部品ID、カード定義ID、実カードの参照、プレイヤーIDは別物です。カード型の一時値は物理個体の参照を保持し、カード定義ID文字列で代用しません。移動後・待機後も正式実行器が個体を再解決します。

現行metadataはgenres配列とdurationRangeを使います。範囲は10-30 / 30-60 / 60-120 / 120-180 / 180-plus。genreとdurationMinutesは旧入力の互換項目で、新規生成では使いません。schemaVersion:20／operationSchemaVersion:4です。

画像は標準画像の許可パスかPNG/JPEG/WebP/GIFの埋め込み画像です。端末専用のidb-image:、blob:、任意の外部URLを別端末用JSONへ残しません。書き出しで画像の実体を回収できない場合は成功扱いにしません。標準SVGだけの例外は[画像仕様](game-data-spec.md)を参照してください。

画像を軽量化する場合、カードの分類ごとに共通ベースを用意し、`variableDefs`の画像変数と`cardTemplate.layers`の`type:"variable"`／`variableKey`で表示できます。同じ分類の`cardDefs[].variables`には同じ画像を設定し、名前・価格・効果文は別の文字変数レイヤーで重ねます。個別のスキャンを`front`にも残すと容量が減らないため、置換対象の表画像は外します。裏面・寸法・ID・枚数・処理用の変数は保持し、説明には原文と詳しい処理を残します。既存の画像重複除去と書出しを使う構成であり、新しいJSON項目はありません。

## 配札・選択して出す・整列

1. 全カードを初期位置へ回収し、裏向きでシャッフルする。
2. 全員へ同枚数ならmoveのsourceAmountMode:per-destination。全在庫を順に振り分けるならtotal。省略は旧来のtotalであり、UI新規作成の既定per-destinationとは異なる。
3. 移動先をhand、playersを明示し、handVisibility:ownerで本人限定にする。バッチ配布はループによる1枚ずつの配布と区別する。
4. 公開の札のsortではcardSortKeysで強さ・スート等の優先順を指定できる。本人限定手札の変数キー整列は、現行の共有操作への公開検査では停止する制約がある。秘密を公開して回避しない。配札から終局の例は変数キーを使わない既存の定義順sortを使用する。キー整列のためだけに既存のカード定義や画像を並べ替えない。
5. 手札のselect権限を許可し、手動ドラッグを止める場合はcanMove:falseまたは手札境界の権限で制限する。選択と移動は別で、canSelectという項目はない。
6. 「出す」の表示条件・活性条件に同じ合法条件を設定し、選択数・同じ強さ・場より強い・現在手番等を判定する。画面で非表示でも、実行時検証は省略しない。
7. 選択した札を先に表向きで場へ出し、公開後に公開の場の強さを更新する。秘密の数字を先に共有変数へ転記しない。

[選択と整列](examples/card-variable-sort.json)、[選択枚数条件](examples/selected-cards-button.json)、[人数別配布](examples/authoring-round-trip.json)を参照してください。

## 型付き式・配列・共通処理

一時値はvariableのsource:temporary:<ID>、temporaryDefinition、temporaryValueで定義・代入します。宣言は同じ操作内から登録されますが、非nullable単体は代入前に読めません。配列は空配列、nullable単体はnullから始まります。数値から文字へはunary/to-textが必要です。オブジェクトは通常ActionのtemporarySchemasへ型を宣言します（settings直下ではありません）。

| 式 | 入力・引数 | 結果と注意 |
| --- | --- | --- |
| literal | value:{type,collection,data,visibility} | 固定値。秘密ラベルを偽装してよい指定ではない |
| temporary | source:一時変数ID | 宣言型を維持 |
| game-value | referenceId | 登録済みのゲーム参照だけ |
| property | source, path:文字配列 | 単体参照または宣言済みオブジェクトの登録項目 |
| binary | operator,left,right | 算術+ - * / %、比較= != > >= < <=、and/or。型不一致・0除算は停止 |
| unary | operator,value | not / is-null / to-array / to-text |
| loop-item / loop-index | source:任意のループAction ID | 省略は対応する最内スコープ。indexは0始まり |
| array | operation,source,必要な引数 | 同型配列のみ。元配列を書き換えず新しい値を返す |

| 配列操作 | 追加引数 | 結果 |
| --- | --- | --- |
| append / concat | other:同型単体 / 同型配列 | 末尾へ追加した配列 |
| replace / remove | index、replaceではother:同型単体 | 指定位置を置換 / 削除した配列 |
| clear / reverse / unique | なし | 空 / 逆順 / 重複除去した配列 |
| slice | index,end（終端を含まない） | 範囲抽出。省略は先頭/末尾 |
| item / first / last | itemではindex（省略0） | 単体。空・範囲外は停止 |
| count / empty | なし | 数値 / 真偽値 |
| contains | other:同型単体 | 含むかの真偽値 |
| equal / intersection | other:同型配列 | 順序も同じか / 共通要素配列 |
| filter / all / any / match-count | predicate:各要素の真偽式 | 配列 / 真偽 / 真偽 / 数値。空のall=true、any=false |
| sort | key:比較キー、order:ascending/descending/random | 安定ソート。参照要素には比較可能な単体キーを指定。randomは確認・オンラインとも共通の正式乱数を使用 |
| sum / average / max / min | key:任意の数値式 | 数値。空はsum=0、ほかは停止 |
| frequency / group | schemaId、key:任意の単体キー | object配列。スキーマにkeyとcount:number / items:元型配列を宣言 |

共通処理の戻り値は定義のreturnTypeとreturnValueを対で設定し、呼出側commonCall.temporaryResultへ受け取る一時変数IDを指定します。commonProcessArguments.<引数ID>は宣言型に一致させます。終端・失敗では戻り値も後続も実行されません。

一時値は1操作の分岐・ループ・共通処理で共有し、入力・演出待ちをまたいで保持します。別ボタンへは引き継ぎません。次の操作にも必要な値は適切な公開範囲の永続変数へ明示反映します。初期・フェーズ・ターンイベントは別スコープです。

## 秘密情報を安全に扱う

プレイヤー変数はpublication:public（省略時）、owner（本人）、team（所属チーム）を設定できます。visibleは一覧表示のON/OFFで、秘密化ではありません。チーム変数はpublic以外では所属チーム内です。全体文字変数は共有値なので秘密の保存先にはしません。公開範囲のJSON指定は実行器の既存対応であり、全画面に公開範囲の切替UIがあることを意味しません。

秘密情報は値だけでなく、抽出・件数・並び順・添字・条件の結果にも制限が伝わります。秘密条件の「それ以外」や0回ループでも、その後の共有処理が停止することがあります。`game.parts` や `game.cards` が個人・チームの盤面を含む場合、取得後に共通盤面の部品へ絞っても元の制限は残ります。公開の在庫から選ぶ処理には、最初から対象のボックスなどの所在を指定してください。

- 人狼の役職・占い履歴、ヒット＆ブローの答えは本人限定変数へ。結果の表示対象も本人へ限定する。
- 処刑結果やHIT/BLOW数など、ルール上公開する結果だけを計算して永続変数へ明示反映する。temporaryPublish:trueはvariable/message/logだけ。元の秘密値をまとめて公開しない。
- noticeには明示公開フラグはない。必要なら先行するvariableで公開可能な結果だけを明示公開し、秘密の制御依存が残らない呼出元でnoticeを出す。
- 正式設計では共通処理の制御ラベルを呼出元へ復元する。現行実装では固定IDだけの呼出は事前展開され、秘密条件が後続へ残る場合がある。実行時の型付きcommonProcessIdバインドを使う呼出は制御境界を保持する。公開結果の例はこの登録済みの呼出形式で検証している。秘密の戻り値自体が公開値へ変わるわけではなく、公開先の検証は維持される。
- ショーダウンは対象のハンドをrevealで先に公開し、その後に公開カードの役判定→役名→勝者表示→配当→確認の順にする。フォールド札を巻き込まない。
- 手札の裏向き回収は内容に依存しない場所指定とcondition:{kind:all}を使う。色・役職等による選別を付けると秘密の条件結果も監査対象になる。
- hidden:trueの通常タブは倉庫用であり秘密権限ではない。倉庫の公開札はそのまま移動でき、公開フラグを付ける必要はない。

盤面ボタン・フェーズボタンの最後の2行に、数値の「変数を変更」でカード変数を直接読む処理と、同じカードを `reveal` の `all`（省略可）で公開する処理をこの順に置くと、条件を満たす2行を一体で反映します。公開を続けて取り消す処理がある場合は、この扱いを適用しません。両方のカード指定を「条件なし（`all`）」にし、所在は本人または全員の手札・チーム手札、または共通タブの同じ固定ボックスを指定します。選択数・並べ替え・動的な引数指定は使いません。得点を更新した状態とカード公開は同時に表示します。別の秘密条件が処理を左右する場合や、全員へカードの面を公開できない場合は従来の検査が働きます。対応する形式の例は [得点更新と手札公開](examples/score-and-reveal.json) を参照してください。式が別の秘密を含む場合や入力・演出を挟む場合は、必要なカードを先に公開し、その後の状態から得点を計算してください。

明確な公開矛盾は保存エラー、カードの状態や対象次第のものは警告です。公開設定を確認できるプレイヤー変数や、本人限定の場所への選択カードの移動は、条件が明確なら警告に出ません。カードを全員に公開してから同じ置き場のカードを得点計算などに使う場合は、公開順序を確認する警告に関連箇所がまとまります。実行時はその時点の公開範囲を検査します。非公開のプレイヤー／チームタブの部品一覧（`parts`）を絞り込んでカードの移動先を指定すると、読込時の警告に該当処理が表示されます。各プレイヤーに同じ定義の箱がある場合は、プレイヤーごとに繰り返し、移動先にタブとボックスを指定できます。警告0を目的に秘密を公開せず、想定する全分岐・対象者で実行を検証してください。[秘密値と公開結果の完全例](examples/private-derived-result.json)。

## ピース盤面・大量処理の組み方

カード上のピースマスは指定行列へmoveで配置でき、placementSuccess/Reason/Countで成否を受け取れます。成功した後だけ反転・得点・手番変更を行います。行列は0始まり。配置面と所属は成功時にまとめて更新します。

カードのマスを一時配列として列挙する機能は現在ありません。リバーシでは既存のcard-grid参照・座標計算・共通処理を使います。未登録のgame.cells等を作りません。

ヨットの振り直しはキープ以外の部品配列をdiceTargetsへ渡すと、独立抽選・演出1回になります。マンカラの種まきで1個ごとに演出をONにすると、その数だけ表示待ちが増えます。オンラインは入力・確認待ちまで一括計算するため、増えるのは表示の時間であり演出ごとのAPI・DB保存ではありません。一括化と演出の順序をルール・演出意図に合わせて選びます。

式深さ64と処理深さ64は別。全ループ1000回、実行10000ステップ、配列10000、変数128、式ノード20000、一時値概算2MB等は別々に適用されます。上限を無効にせず、共通化・一括処理・不要な探索の削減を行います。

## タイマー・確認・終局

- タイマー終了はtimer部品のactionsとtimerEndPhaseIds。自動処理には実行者がいないため、対象を明示する。3分の議論なら180秒のカウントダウンから投票開始を呼ぶ。
- 全員スキップと時間終了の両方から同じ共通処理を呼び、段階変数が議論中の場合だけ投票へ移す。二重進行を避け、先にタイマーを停止する。
- 読ませるだけならnotice/notify、全員確認を待つならnotice/acknowledge。人数表示は後者だけ。退出・切断は自動確認にしない。
- 選択を伴う投票はchoice/playersで名前を表示し、回答game.input.textsをplayer.idと照合する。同名でも名前文字列で対象を決めない。
- 結果確認で未確認者全員を手番にしたい場合はturn.multipleAllowed:trueで全員へ付与し、確認した本人からremoveする。手番設定と確認要求は別物。
- 最後は公開してよい結果だけをsettings.result.metricsへ設定し、gameEndを呼ぶ。後続は呼出元を含めて停止する。進行表示は事前に「ゲーム終了」へ更新する。

[全員入力](examples/answer-wait.json)、[通知・確認](examples/notification-confirmation.json)、[タイマー](examples/timer-end.json)、[最終結果](examples/result-screen.json)、[初期化から終了まで](examples/authoring-round-trip.json)。

## 完成時の検証

1. JSON Schemaで形式を検査する。続いて正式読込検証でID・型・公開範囲等を検査する。Schemaだけでゲームの安全性・ルールの正しさは証明できない。
2. 最少・最大人数、合法手・違法手、手番外、配置失敗、パス・同票・引き分け・全員確認・終局を通す。
3. 秘密値が他人・観戦者へ配信されないこと、公開結果だけが出ることを確認する。
4. 編集→保存→書き出し→再読込で画像・配置・処理・変数型が保たれることを確認する。
5. 実画面のPC・スマホ縦・横で重なり・操作領域・背景透け・長文・手札・ダイアログを確認する。画像へ焼き込んだ文字と動的部品の二重表示を避ける。
6. 実行器テスト、実画面、複数端末、本番反映を分けて報告する。必要なテスト下書きは既存データと区別し、許可された今回分だけ削除する。

## 公開資料の範囲

作者が使う項目・型・初期値・省略時挙動・処理順・権限・制限・失敗条件は設計書と同じ契約を公開します。認証情報、実ユーザー情報、秘密状態、非公開運用情報は公開しません。セキュリティ上の制約は説明を隠すのではなくサーバーで検証します。設計上の将来案を現行機能として記載せず、公開資料を読むために内部リポジトリへアクセスすることを前提にしません。

## 選んだ札だけを出す

choice/card-location の cardSelector で場所と条件を指定し、game.input.cards を move の cardSelector にバインドします。間に別の入力を入れる場合は先に通常の一時変数へ card 配列を保存します。公開回答は選んだ不透明な参照だけを公開し、未選択札は隠したままです。[完全な例](examples/physical-card-choice.json)。旧 source 形式の移行は不要です。

表示順：演出の有無にかかわらず、カード移動・変数表示・手番・ボタン・タブ・通知等は元の処理順に表示します。演出なしは先行演出の後に無音で即時反映します。連続する待ち時間0の変更は同じ描画境界へまとめます。選択・入力・確認画面は先行表示の完了後に操作可能になり、回答時間もその時点から確保します。確認画面とオンラインで共通です。JSON形式20／4と既定値は変更しません。

## 本人の所有カード内訳

所有カードの確認には山札の複製を作らず `settings.cardInventory` を使います。集計する通常の個人ボックスと、一時確認用のボックスを分けます。相手の札を一時確認する効果では、移動前に元の所有者をカード個体の変数へ記録し、ownerVariableで照合します。名前・枚数・説明だけを本人に示し、閲覧のために手番・タブ・カード状態を変更しません。[形式と移行](game-data-spec.md#本人の所有カード内訳)・[完全な例](examples/card-inventory.json)。
