実装日: 2026-09-18。ユーザーが操作して評価できる、会場LAN向けの単一Next.jsアプリ。 要求は 要件定義、試用手順と自主判断は 試作提案 を参照。 従来のノードグラフ・PostgreSQLを中心とする詳細設計は technical-roadmap.md に保存した。 現在の実装を説明する文書は本書であり、将来設計の実装完了を意味しない。
| 配置 | 責務 |
|---|---|
packages/shared/src/control.ts |
状態・コマンド・表示ラベル・Zod検証 |
packages/scoring/src/match.ts、standings.ts |
各戦・2戦合計の判定と予選順位。I/Oを持たない |
chros/lib/tournament.ts |
総当たり・本戦の生成、入力保護、再試合、監査記録を含む状態遷移 |
chros/lib/store.ts |
JSON永続化、更新番号検証、保存後のSSE通知 |
chros/lib/use-control.ts |
ブラウザーでのSSE購読、コマンド送信、通信状態とエラー表示 |
chros/components/ConsoleApp.tsx |
進行管理と画面選択 |
chros/components/MatchEditor.tsx |
先攻登録、各戦の結果入力・訂正・再試合 |
chros/components/management/ |
参加者、順位、本戦作成、大会設定、エクスポート、履歴 |
chros/components/Rundown.tsx |
表示順の編集と手動進行 |
chros/components/Board.tsx |
会場掲示とプレビューの共通表示 |
既存のNext.js 15 / React 19 / TypeScript / pnpm workspaceを継続し、新たな実行時依存は加えていない。 外部フォント・外部画像・CDNを必須にしない。旧ダミー計算と未接続の画面を置き換えた。
ControlState は大会ID、状態リビジョン、保存時刻、計算方式と規則版 2026-09-18.1、
参加者、試合、各戦の結果、過去の無効試合、操作履歴、進行区分、掲示画面・対象試合・ページ、
表示順、ロゴを持つ。1プロセスにつき1つの大会が現在の操作対象。
- 参加者は人間のみ。IDは得点とは独立し、表示名の重複を防ぐ。
Match.a/bは先攻側ではなく固定の参加者枠。先攻はfirstCoolに記録し、第2戦は反転する。gamesは番号1・2、A/Bの素点、勝因、特殊勝者、残りターン数を保持する。- 試合の状態と勝者は保存データから同じ計算関数で導出する。状態と集計の二重管理をしない。
previousAttemptsに無効化した両戦・先攻・理由・時刻を保持し、現在の順位計算から除く。- 本戦の
sourcesは各枠の元になる試合ID。確定勝者をラウンド順に伝播する。 - 操作履歴に更新内容を残す。結果訂正では変更前の入力値と訂正理由も記録する。
evaluateGame → evaluateMatch → standings の順に純粋関数で計算する。
方式を切り替えても素点・勝因の意味が変わらないようにする。
| 方式 | 各戦の扱い | 2戦の比較 | 予選順位(試作判断) |
|---|---|---|---|
kushiro |
得点比較は特殊Pなし。それ以外の入力済み勝因は勝者に1特殊P | 特殊P合計、同値なら得点合計 | 試合勝数、特殊P、得点 |
asahikawa |
勝数を数え、敗因に応じて0または負の残ターンへ得点を換算。素点は残す | ゲーム勝数、同値なら換算得点合計 | 試合勝数、換算得点 |
両方式とも2戦終了前は試合勝者を確定しない。完全同値は再試合待ちとし、順位に算入しない。 予選順位が同じ場合、表示上の名前順を順位差とみなさず同順位で示す。 本戦生成時に同順位のシード順・進出者を確定する理由を要求する。 下位者への無条件の繰上げや進出者の重複は拒否する。
シードの初期候補は各組1位→各組2位の順。一般的なシード配置に展開する。 2組×2名なら A1-B2 / B1-A2。2の累乗に不足する枠は上位シード側の不戦進出。 予選対戦順は組・参加者の登録順からの全ペア列挙。コート割当・連戦回避の最適化はしない。
通信切断は、審判が「その参加者に起因する敗北」と裁定した場合に選ぶ。 応答タイムアウト・サーバー故障などの再試合判断は「両戦を仕切り直す」で理由を記録する。 サーバー事象の自動判別・同時事象の優先順位判定は行わない。
| API | 用途 |
|---|---|
GET /api/state |
現在の保存済み全状態 |
POST /api/state |
{ expectedRevision, command } を検証して適用 |
GET /api/stream |
snapshot SSEイベントで全状態を配信 |
GET /api/export |
状態・履歴を含むJSONダウンロード |
GET /api/export?format=csv |
各戦の対戦者・先後・素点・勝因・勝者・無効理由をCSV出力 |
GET /api/screen |
現在の画面・進行区分・更新番号。旧POSTは410で新APIへ案内 |
Zodでコマンドの形・得点範囲・文字数などを検証し、状態遷移側で対戦者・進行順・訂正条件を検証する。 更新番号が不一致なら409と最新状態を返す。クライアントは確認を促して自動再送しない。 別サイトからのOriginを伴う書込を拒否する。認証の代わりではない。
保存後にEventEmitterから全状態を送る。再接続時も必ず全状態を送り直すため、 イベント差分の再送履歴・サーバー再起動時の連番リセットに依存しない。 15秒おきにSSEのコメントを送り、切断時は購読とタイマーを解除する。 接続が切れた掲示は最終受信値を残し、再接続中であることを表示する。
データディレクトリは CHROS_DATA_DIR。未指定ならNext.jsプロセスの作業ディレクトリにある .chros。
通常の起動では chros/.chros となる。GitとDockerのビルドコンテキストから除外する。
state.jsonが現行状態。state.previous.jsonは直前の変更前状態。- 大会切替時は
archive-<時刻>-<revision>.jsonに大会を退避する。 - 一時ファイルへ書き、ファイルをfsyncした後にrenameして現行ファイルを置き換える。
- 読取・更新検証・保存・通知を単一プロセス内で同期的に行う。
- 保存ファイルが壊れている場合、サンプルを生成して上書きしない。
復旧する場合はサーバーを停止し、現行ファイルを別名で保管したうえで、正常な退避JSONまたは
エクスポートJSONを state.json にコピーして再起動する。復元画面は未実装。
ファイル保存は今回の試作判断であり、PostgreSQL設計の完成を意味しない。
複数Nodeプロセス・共有ディスクからの同時書込は非対応。これらが必要になればDBへ移行する。
掲示は16:9。コンテナー単位でプレビューと会場表示を同じ比率にする。 予選順位は1カード6名・1ページ2カード。本戦が8名以下なら全体図、それ以上は各ラウンド4試合ずつ。 ページ番号は状態に保存し、運営と会場で一致させる。 長い名前の一部は会場カード上で省略表示となるため、登録名は会場で読める長さにする。
ロゴはPNG/JPEG/WebP・1 MBまで。外部URLは使用せず、画像データを状態に含める。 画面選択・表示順再生は進行区分と画面をまとめて保存する。 現在の試合は独立して選択し、得点保存で意図せず画面を切り替えない。
判定、旭川換算、左右入替の対称性、訂正、再試合、予選順位、進出、同順位裁定、不戦枠、
開始後の修正保護を packages/scoring/src/index.test.ts で検証する。
実施済みの検証と既知の制約は 試作提案 に記録する。