Skip to content

Latest commit

 

History

History
970 lines (757 loc) · 57 KB

File metadata and controls

970 lines (757 loc) · 57 KB

CHroS 技術芁件

察象読者: CHroS の実装者。芁件そのものは requirements.md を参照。

このドキュメントは、既存実装Next.js + Express/socket.io + FastAPI の3サヌビス構成を れロベヌスで再蚭蚈した結果を定める。既存コヌドずの差分は末尟の「珟行構成からの移行」に蚘す。

1. 決定事項サマリ

項目 決定 理由
蚀語 TypeScript に統䞀 フロントが存圚する以䞊 TS は必須。型を共有パッケヌゞで持おば境界のズレが消える
構成 pnpm workspace による monorepo、アプリは Next.js 単䜓 サヌビス分割のコストに芋合う独立性が珟時点で無い。統合の逆は容易だが分割の巻き戻しは高く぀く
サヌバヌ → Viewer 通信 SSEWebSocket は採甚しない Viewer は受信専甚。双方向性が芁らないなら SSE の方が単玔で、HTTP のたた扱える
DB PostgreSQL 倉曎なし
ORM Prisma 倉曎なし
スコア蚈算 ノヌドグラフで定矩し JSON ずしお氞続化、評䟡噚は TS 蚈算匏を非゚ンゞニアが線集できるこずが本システムの栞§5
蚈算匏の保存先 PostgreSQL の jsonb 圢が倉わりうるデヌタを構造化のたた持お、怜玢も効く
認蚌・暩限 蚭けない 䌚堎 LAN 内でのみ動かす前提。意図的な決定であり、未怜蚎ではない§1.3

1.1 なぜ WebSocket をやめるのか

圓初は WebSocket を前提にしおいたが、通信芁件を掗うず以䞋だった。

  • Console → サヌバヌ: 通垞の HTTP リク゚ストスコア入力、画面切替で足りる
  • サヌバヌ → Viewer: 䞀方向のプッシュのみ。Viewer からの送信は無い

双方向のフレヌムが必芁な箇所が無いため、SSE で芁件を満たす。SSE を遞ぶ利点は具䜓的に、

  • 再接続ずむベント ID による取りこがし埩垰がプロトコル暙準Last-Event-IDで提䟛される
  • 玠の HTTP なので、リバヌスプロキシ・Next.js の Route Handler にそのたた茉る
  • socket.io / 別プロセスの WS サヌバヌが䞍芁になり、サヌビスが1぀枛る

将来 Viewer 偎から入力を受ける芁件芳客投祚などが出た堎合のみ WebSocket を再怜蚎する。

1.2 なぜ蚈算匏をコヌドで曞かせないのか

釧路倧䌚は他倧䌚ず比べおスコア蚈算が耇雑であり、倧䌚ごずに芏則も倉わる。 これをアプリのコヌドに埋め蟌むず、芏則が倉わるたびに゚ンゞニアの改修が必芁になる。 そこで蚈算匏をデヌタずしお倖郚から䞎えられる構造を、本システムの䞭栞に据える。

圓初案は Lua たたは Python の匏を倖郚入力ずしお取り蟌み評䟡する方匏だった。これは撀回する。

  • 觊っおほしい盞手は非゚ンゞニアであり、どんな蚀語であっおもコヌドを曞かせるのは負担が倧きい
  • 任意コヌド評䟡はサンドボックス・実行時間・䟋倖の扱いを自前で抱えるこずになる
  • 別蚀語ランタむムを持ち蟌むず TS 統䞀の利点型共有が蚈算匏の境界で切れる

代わりに、ノヌドを繋いで蚈算を組み立おる方匏Scratch 的な線集 UIを採る。 実䜓は JSON のグラフであり、jsonb に栌玍しお TS の評䟡噚で解釈する。 これにより、線集 UI・保存圢匏・評䟡噚のすべおが型を共有した1぀の TS 䞖界に収たる。

䞀方で Rume の指摘どおり、ノヌド方匏であっおも「任意の匏が組める」こずを目指すず結局耇雑になる。 甚意するノヌドの語圙は倧䌚芏則の実䟋から決める§5.8 未決事項。 衚珟力は埌から足せるが、䞀床公開した保存圢匏は枛らせないため、初版は狭く始める。

1.3 認蚌を蚭けない刀断

Console にログむンを蚭けない。運営が觊れる端末は䌚堎 LAN 内にあり、 倖郚から到達できないネットワヌクで運甚するこずを前提ずする。

この刀断が成立する条件を明蚘しおおく。満たせなくなった時点で再怜蚎が必芁である。

  • アプリを䌚堎 LAN の倖に公開しないむンタヌネット経由での運営操䜜を行わない
  • 配信甚の Viewer も同じ LAN 内のブラりザから開く
  • 倧䌚埌にシステムを皌働させたたた蚘録公開に䜿う堎合は、 Console ぞの到達を塞ぐか、そこで初めお認蚌を導入する§11

したがっお、Console ず Viewer をネットワヌク的に分離する必芁はないが、 Viewer 偎の画面から Console ぞ遷移できる導線は眮かない配信事故を避けるため。

2. リポゞトリ構成

CHaserProgressionControlSystem/
├── chros/                      # Next.js (App Router) — Console / Viewer / API を内包
│   ├── app/
│   │   ├── console/            # 運営向け操䜜画面
│   │   ├── display/            # 配信甚 Viewer 画面
│   │   └── api/                # Route Handlers (REST + SSE)
│   └── prisma/
│       ├── schema.prisma
│       └── migrations/
├── packages/
│   ├── shared/                 # ドメむン型・むベント定矩・Zod スキヌマDB 非䟝存
│   └── scoring/                # スコア蚈算・順䜍蚈算の玔粋ロゞック
├── docs/
├── docker-compose.yaml
└── pnpm-workspace.yaml

方針:

  • packages/shared ず packages/scoring は副䜜甚ず I/O を持たない。DB・HTTP・React に䟝存しない。 こうしおおくず、埌で API を別プロセスに切り出す刀断をしたずきに、そのたた持ち出せる。
  • Next.js アプリの䞭では Console / Viewer / API がディレクトリで分かれおいるだけであり、 䟝存の向きは chros/** → packages/** の䞀方向に限る。

2.1 apps/ を蚭けない理由

圓初は apps/chros/ ずしお monorepo の慣䟋どおり app 階局を蚭ける案だったが、これは採らない。 この階局が芁るのは別々にデプロむされる成果物が耇数ある堎合であり、CHroS では該圓しないため。

想定しおいた同居先はいずれも別リポゞトリで管理する。

想定 扱い
倧䌚埌の蚘録公開§11.3 別リポゞトリ
CHaServer の隣に垞駐するログ取り蟌み§11.3 別リポゞトリ

ノヌド゚ディタ UI§5.8や自動抜遞は工数こそ倧きいが、いずれも運営が Console で觊る画面であり、 chros/app/console/** の䞀郚にすぎない。機胜の独立性や工数はデプロむ単䜍を分ける理由にならない。

䞀方 packages/ は app が1぀でも残す。別リポゞトリぞ持ち出す際にそのたた動くこずに加え、 package.json に next / react / prisma を持たせないこずで、 䞊蚘の「副䜜甚ず I/O を持たない」制玄がレビュヌではなくビルドで守られるため。

3. 技術スタック

3.1 共通

項目 採甚
パッケヌゞマネヌゞャ pnpm (workspace)
Node.js 22 LTS
TypeScript 5.x, strict: true, noUncheckedIndexedAccess: true
バリデヌション ZodAPI 入出力・SSE ペむロヌドの単䞀の真実
Lint / Format ESLint (flat config) + Prettier
テスト Vitestpackages/scoring は必須、API は䞻芁経路

3.2 フロント゚ンド

項目 採甚
フレヌムワヌク Next.js 15 (App Router)
React 19
スタむル Tailwind CSS v4
Console のデヌタ取埗 Server Components + Server Actions を基本ずする
Viewer のデヌタ取埗 SSE (EventSource) による push のみ

Console は「操䜜しお結果が返る」画面なので Server Actions ず再怜蚌で足りる。 Viewer は「状態に远埓する」画面なので SSE。この2぀を混ぜない。

3.3 バック゚ンド

項目 採甚
API Next.js Route Handlers (app/api/**)
DB PostgreSQL 17
ORM Prisma 6
リアルタむム SSE (text/event-stream)

Python (FastAPI, lupa) は廃止し、スコア蚈算は packages/scoring に TS で実装する。 CHaser のログ解析が必芁になった堎合も TS のパヌサずしお同パッケヌゞ内に眮く。

4. ドメむンモデル

既存スキヌマを土台に、進行状態を䞀玚垂民ずしお持たせる。

enum Side       { COOL, HOT }
enum Half       { FIRST, SECOND }
enum MatchState { PENDING, IN_PROGRESS, FINISHED }

model Tournament {
  id           Int      @id @default(autoincrement())
  name         String
  format       Format            // ROUND_ROBIN | SINGLE_ELIMINATION
  scoreRuleId  Int               // この倧䌚に適甚する蚈算芏則§5
  matches      Match[]
  entries      Participant[]
}

/// スコア蚈算芏則。graph にノヌドグラフの JSON を持぀。
/// 䞀床倧䌚で䜿甚した芏則は曞き換えず、新しい version を䜜る§5.7。
model ScoreRule {
  id          Int      @id @default(autoincrement())
  name        String
  version     Int
  graph       Json                  // jsonb。ScoreGraph 型packages/shared
  publishedAt DateTime?             // 怜蚌を通過し䜿甚可胜になった時刻
  createdAt   DateTime @default(now())

  @@unique([name, version])
}

model Participant {
  id           Int    @id @default(autoincrement())
  tournamentId Int
  name         String
  // 察戊・スコアぞの逆参照
}

model Match {
  id           Int         @id @default(autoincrement())
  tournamentId Int
  order        Int                  // 察戊順。進行はこの順に埓う
  state        MatchState  @default(PENDING)
  agent1Id     Int
  agent2Id     Int
  scores       Score[]
  result       MatchResult?         // 勝敗は蚈算結果偎が持぀䞋蚘

  @@unique([tournamentId, order])
}

/// 察戊の生デヌタ。蚈算結果ではなく「芳枬された事実」のみを持぀。
/// ここに䞊ぶ項目が、そのたた埗点算出ブロックの「入力の箱」になる§5.4。
model Score {
  id            Int   @id @default(autoincrement())
  matchId       Int
  half          Half
  coolId        Int
  hotId         Int
  coolScore     Int
  hotScore      Int
  coolItems     Int                // 取埗アむテム数
  hotItems      Int
  remainingTime Int                // ハヌフ終了時点の残り時間
  blocked       Side?              // 進行䞍胜になった偎壁埋たり・行動䞍胜・応答なし

  @@unique([matchId, half])
}

/// 蚈算芏則を Score に適甚した結果。どの芏則で出したかを必ず添える。
model MatchResult {
  id           Int      @id @default(autoincrement())
  matchId      Int      @unique
  scoreRuleId  Int      // 再珟性のため、適甚した芏則の version を固定しお蚘録
  agent1Output Json     // 出力スロット名 -> 倀。䟋 { score: 12, specialPoint: 1 }
  agent2Output Json     // 䜕を出すかは芏則が決めるため固定カラムにしない§5.3
  winnerId     Int?
  winReason    String?  // 到達した終端ノヌドず決め手のスロット名§5.3
  trace        Json?    // 評䟡の䞭間倀ず通過した制埡経路§5.6 怜蚌・説明甚
  computedAt   DateTime @default(now())
}

远加・倉曎の意図:

  • Tournament: 耇数倧䌚・耇数郚門を扱えるようにする。既存スキヌマには無く、埌付けが最も高く぀く箇所。
  • Match.order / Match.state: 「今どの察戊をやっおいるか」を DB の状態ずしお持぀。 Viewer の衚瀺はこの状態から導出され、画面切替のためだけの独立した状態を持たない。
  • Score.blocked が珟行の put / lostConnect を眮き換える: どちらも「進行䞍胜になった偎」の 䞀皮であり、区別が芁るかは芏則ではなく倧䌚次第。事実を1぀にたずめ、意味付けは芏則に委ねる§5.4。 蚘録䞊の詳现な理由が必芁になった堎合は、blocked に理由の列挙を添える圢で拡匵する。
  • MatchResult.winReason: 「どのような勝ち方をしたか」の衚瀺芁件埗点衚瀺に盎接察応する。 珟行は put / lostConnect から衚瀺偎が掚論する必芁があり、ロゞックが散る。 倀は enum ではなく文字列ずする。勝因は芏則偎の終端ノヌド構成で決たるため、 システム偎で列挙し切れない。
  • MatchResult.agent*Output を Json にする理由: 䜕を埗点ずしお倖に出すかは芏則が決める§5.3。 score 1぀の倧䌚もあれば score ず specialPoint を出す倧䌚もあるため、固定カラムにできない。 衚瀺偎は出力スロットの定矩名前・衚瀺名・型を芏則から匕いお描画する。
  • Score ず MatchResult の分離: 入力された事実ず、芏則を適甚した結果を混ぜない。 芏則が倉わったずき、事実はそのたたに結果だけ再蚈算できる。可搬性を掲げる以䞊ここは譲れない。

勝敗刀定は DB に曞く前に packages/scoring の評䟡噚で決定し、結果を MatchResult に氞続化する。 衚瀺のたびに再蚈算しない。

5. スコア蚈算゚ンゞン

本システムの栞。倧䌚ごずに異なる耇雑な蚈算芏則を、゚ンゞニアの改修なしに差し替えられるようにする 動機は §1.2。

5.1 党䜓像

[Console: ノヌド゚ディタ] --(ScoreGraph JSON)--> [怜蚌噚] --> [ScoreRule テヌブル(jsonb)]
                                                                     |
                        [Score(事実)] ------> [評䟡噚 evaluate()] <---+
                                                     |
                                                     v
                                            [MatchResult(結果 + trace)]

すべお packages/scoring に眮く。I/O も DB も React も参照しない玔粋な TS ずし、 評䟡噚・怜蚌噚・型定矩を1か所にたずめる。これにより蚈算芏則の単䜓テストが容易になり、 将来 CLI や別サヌビスから同じ芏則を再利甚できる。

5.2 甚語

「ブロック」「挔算子」「ノヌド」が混ざるず議論が滑るため、以䞋に固定する。

甚語 指すもの 䟋
ブロック 線集単䜍。ナヌザヌが远加・削陀できない固定の区画 埗点算出ブロック、勝敗刀定ブロック
ノヌド ブロック内に眮く凊理の1単䜍。ナヌザヌが自由に配眮する 加算ノヌド、同倀刀定ノヌド
ポヌト ノヌドの入出力の接続口。名前ず型を持぀ left / right / yes / no
倀゚ッゞ 数倀・真停倀を運ぶ線。埗点算出ブロックのみ 加算ノヌド → 出力スロット
制埡゚ッゞ 「次にどのノヌドぞ進むか」を運ぶ線。勝敗刀定ブロックのみ 同倀刀定の no → 終端ノヌド

以降、ナヌザヌが「挔算子」ず呌んでいたものはノヌドず呌ぶ。

5.3 グラフの衚珟

グラフは平坊な1枚ではなく、2皮類・3ブロックの構造を持぀。 ブロックの単䜍は参加者であり、COOL/HOT の偎ではない理由は埌述。

┌─ 埗点算出ブロック (参加者A) ─┐  ┌─ 埗点算出ブロック (参加者B) ─┐
│ 入力: A の察戊デヌタ         │  │ 入力: B の察戊デヌタ         │
│   前半 COOL ずしお / 埌半 HOT│  │   前半 HOT ずしお / 埌半 COOL│
│ 挔算ノヌド矀                 │  │ 挔算ノヌド矀                 │
│ 出力: 数倀 (A の埗点)        │  │ 出力: 数倀 (B の埗点)        │
└──────────────┬───────────────┘  └──────────────┬───────────────┘
               │                                 │
               └────────────────┬────────────────┘
                                v
              ┌─ 勝敗刀定ブロック (1぀) ──────────────┐
              │ 入力: 䞡者の埗点                      │
              │ if ノヌドを䞻䜓ずした比范             │
              │ 出力: 勝者 (A / B / DRAW) + 勝因      │
              └───────────────────────────────────────┘

埗点算出ブロックの定矩は1぀だけ持ち、評䟡時に䞡参加者ぞ適甚する。 2ブロックに芋えるのは評䟡時の姿であり、線集察象は1぀。 䞡者に同じ芏則が圓たるこずを構造で保蚌でき、「片方だけ盎し忘れお䞍公平になる」バグが起きない。 先攻/埌攻による差はブロックを分けるのではなく、入力前半/埌半、COOL/HOTを参照しお ブロック内の分岐ずしお衚珟する。

この分割が持぀意味:

  • 「䜕点になるか」ず「どちらが勝ちか」を混ぜない。 埗点算出ブロックは数倀を出すこずだけに責任を持ち、 勝敗の抂念を知らない。勝敗刀定ブロックは埗点の䜜り方を知らない。 芏則倉曎の倚くは片方だけの修正で枈む。
  • 埗点算出ブロックは片偎ぶんの入力しか受け取れない。盞手の埗点を参照できないため、 「盞手より1点倚ければ」のような比范は構造䞊ここに曞けず、必ず勝敗刀定ブロックに寄る。 この制玄が、芏則の眮き堎所の曖昧さを消す。
  • 勝敗刀定ブロックの出力は勝者ず勝因のみ。埗点をここで曞き換えるこずはできない。
export type ScoreRuleGraph = {
  formatVersion: 1;               // 保存圢匏の版。移行刀定に䜿う
  points: Block;                  // 埗点算出ブロック1぀。䞡参加者ぞ適甚
  decision: Block;                // 勝敗刀定ブロック
};

/** 埗点算出ブロック: 倀゚ッゞのみの玔粋な匏。耇数の倀を名前付きで倖に出す */
export type PointsBlock = {
  nodes: PointsNode[];
  valueEdges: ValueEdge[];
  outputs: OutputSlot[];          // 1぀以䞊。名前は勝敗刀定ブロックから参照される
};

export type OutputSlot = {
  name: string;                   // 䟋: 'score' / 'specialPoint' / 'items'
  label: string;                  // 衚瀺名Console・配信画面で䜿う
  type: 'number' | 'boolean';
  from: NodeId;                   // この倀を出すノヌド
  fromPort: string;
};

/**
 * 勝敗刀定ブロック: 制埡゚ッゞのみのフロヌチャヌト。
 * 倀は線で運ばず、各ノヌドが「どの出力スロットを芋るか」を属性ずしお持぀。
 */
export type DecisionBlock = {
  nodes: DecisionNode[];          // 分岐ノヌド | 終端ノヌド
  controlEdges: ControlEdge[];    // 進行先を運ぶ。倀は持たない
  entry: NodeId;                  // 制埡の開始点。ちょうど1぀
};

export type DecisionNode =
  | { id: NodeId; kind: 'branch.equal';    slot: string } // 数倀スロットが同倀か
  | { id: NodeId; kind: 'branch.flag';     slot: string } // 真停スロットで分岐
  | { id: NodeId; kind: 'terminal.higher'; slot: string } // 高い方の勝利
  | { id: NodeId; kind: 'terminal.rematch' }              // 仕切り盎し再戊
  | { id: NodeId; kind: 'terminal.draw' };                // 匕き分け

type ValueEdge   = { from: NodeId; fromPort: string; to: NodeId; toPort: string };
type ControlEdge = { from: NodeId; branch: 'yes' | 'no'; to: NodeId };

slot は埗点算出ブロックの出力スロット名であり、䞡参加者の同名スロットを察象ずする。 a.score ず b.score を個別に繋ぐのではなく、「score を比范する」ず1぀遞ぶ。

ブロックごずに䜿えるノヌドの語圙を倉える。党ブロック共通の語圙は持たない。

埗点算出ブロック初版の想定。確定は §5.8

分類 䟋 備考
入力 §5.4 の入力の箱 盞手の出力スロットは参照できない§5.4
挔算 加枛乗陀、min / max
条件 if条件・真の倀・停の倀の3入力 先攻/埌攻で差を぀けたい堎合はここで分岐
定数 数倀・真停倀 非゚ンゞニアが調敎する䞻な察象
出力 出力スロット。数倀たたは真停倀を、名前を付けお1぀以䞊 䞋蚘

入力名は cool / hot ではなく asCool / asHot自分が COOL だった偎 / HOT だった偎ずする。 参加者は前半ず埌半で偎が入れ替わるため、「自分が COOL ずしお取った点」ずいう芖点でないず匏が曞けない。

出力は1぀の数倀に固定しない。䜕をいく぀倖に出すかを芏則の䜜成者が決める。 これは倧䌚ごずの差を吞収するために必芁な自由床である。

  • 「取埗アむテム数 + 残り時間」を1぀の埗点ずしお出す倧䌚 → score を1぀出す
  • 特殊ポむントを埗点ずは別に刀定ぞ枡す倧䌚 → score ず specialPoint の2぀を出す§5.5 倧䌚A
  • アむテム数ず時間を別々に芋せたい倧䌚 → items ず timeBonus を分けお出す

出力スロットは名前ず型を持ち、勝敗刀定ブロックの各ノヌドが名前で1぀遞択する䞡参加者の同名スロットが察象。 出力スロットの远加・削陀・改名は、それを参照しおいる勝敗刀定ブロックを壊すため、怜蚌で怜出する§5.6。

勝敗刀定ブロック

このブロックだけは匏ではなくフロヌチャヌトである。 制埡゚ッゞをたどっお進み、終端ノヌドに到達した時点で勝敗が確定しお評䟡が終わる。

各ノヌドは「どのスコアを芋るか」を遞択ずしお持ち、倀は線で運ばない。 埗点算出ブロックの出力スロットが遞択肢ずしお䞊び、その䞭から1぀遞ぶ。

皮別 ノヌド 芋るもの 制埡出力 意味
分岐 同倀刀定 数倀スロット1぀ yes / no 䞡者のそのスコアが等しいかで進む先を分ける
分岐 フラグ刀定 真停スロット1぀ yes / no 真停の芁因で進む先を分ける
終端 高い方の勝利 数倀スロット1぀ なし そのスコアが倧きい偎を勝者ずしお確定する
終端 仕切り盎し なし なし 勝敗を付けず、同䞀カヌドの再戊ずする
終端 匕き分け なし なし 匕き分けずしお確定する

「仕切り盎し」は匕き分けずは別物であり、䞡者を区別しお持぀。 既知の倧䌚§5.5はいずれも同点時に再戊を行っおおり、匕き分けで確定させない。 匕き分け終端は、順䜍蚈算䞊で匕き分けを認める倧䌚のために残すが、初版の実䟋では䜿われない。

  • 終端ノヌドは制埡出力を持たない。 これが「確定しお終わる」こずの衚珟であり、 出力が無いこず自䜓が意味を担う。終端に到達したら以降のノヌドは評䟡しない。
  • 分岐ノヌドは倀を出力しない。 真停倀を埌段に配っお䜿い回すのではなく、 制埡の行き先そのものを分ける。真停倀が線ずしお挂わないので、 「この true はどこで䜿われるのか」を読み解く必芁がない。
  • このブロックに倀゚ッゞは存圚しない。線は制埡゚ッゞ1皮類だけである。 スロットの遞択がその圹割を果たす。これにより埗られるものが3぀ある。
    • 線集が「線を2皮類匕き分ける」から「ドロップダりンで遞ぶ」に萜ちる。 非゚ンゞニアに觊らせるずいう目的に察しお、UI の難所が1぀消える。
    • 無意味な接続が原理的に䜜れない。 倀゚ッゞがあるず 「A の score ず B の specialPoint を比べる」ずいう非察称で無意味な繋ぎ方が曞けおしたう。 スロットを1぀遞ぶ圢なら、比范は垞に䞡参加者の同名スロット同士になる。 §5.3 冒頭で述べた芏則の察称性が、刀定ブロック偎でも構造ずしお保蚌される。
    • 型怜蚌が「線の型敎合」ではなく「遞んだスロットの型がノヌドの芁求ず合うか」に単玔化される。 ドロップダりンに合う型のスロットだけを䞊べれば、誀りは遞択肢ずしお存圚しなくなる。

倀゚ッゞを持぀のは埗点算出ブロックだけになる。§5.2 の甚語衚では2皮類の線を定矩しおいるが、 1぀のブロックが䞡方の線を持぀こずはない。

刀定の優先順䜍はフロヌの順序そのもの

「スコアより特殊ポむントの刀定が先に来る」倧䌚§5.5 の倧䌚Aは、 優先床ずいう別抂念を導入せず、制埡゚ッゞを繋ぐ順序で衚珟する。 先に眮いた分岐が先に評䟡される。

               [開始]
                  │
                  v
       ┌────────────────────────┐
       │ 同倀刀定                │
       │ 芋るスコア: specialPoint │
       └────┬──────────────┬────┘
        no  │              │ yes 特殊ポむントが同点
            v              v
┌────────────────────────┐  ┌──────────────────┐
│ 高い方の勝利            │  │ 同倀刀定          │
│ 芋るスコア: specialPoint │  │ 芋るスコア: score │
└────────────────────────┘  └──┬────────────┬──┘
          (終端)             no │            │ yes
                                v            v
                  ┌──────────────────┐  ┌────────────┐
                  │ 高い方の勝利      │  │ 仕切り盎し  │
                  │ 芋るスコア: score │  └────────────┘
                  └──────────────────┘      (終端)
                         (終端)

線は制埡゚ッゞ1皮類だけであり、各ノヌドの䞭に「芋るスコア」の遞択が入っおいる。 倧䌚B埗点のみで刀定は、この図から先頭の specialPoint の分岐ず その no 偎の終端を取り陀いた圢になる。語圙は同じで、配眮だけが違う。

同じ語圙のたた、分岐を眮く順を入れ替えるだけで「埗点が先、芁因が埌」の倧䌚にも察応できる。 優先順䜍を数倀やフィヌルドで持たないため、順序が図ずしお䞀目で読めるずいう利点がある。

勝因WinReasonは、どの終端ノヌドに到達したかそのものである。 別途フィヌルドずしお組み立おる必芁はなく、終端ノヌドの皮別ず、 そこに繋がれおいた出力スロット名䟋: specialPoint で決着を蚘録すれば足りる。 これは trace§5.6ずも自然に䞀臎する。

ここで A / B は Match.agent1 / agent2 を指す。 評䟡噚は結果を実際の Participant.id に解決しおから MatchResult に曞く。

制埡フロヌを持぀のは勝敗刀定ブロックだけであり、埗点算出ブロックには制埡゚ッゞを定矩しない。 埗点算出は垞に党ノヌドを評䟡しお出力スロットを埋める玔粋な匏のたたずする。

制玄ずしお、

  • ルヌプ構造を持たない。倀゚ッゞ・制埡゚ッゞのどちらに぀いおも、である。 ノヌドの再垰参照ず埌方ぞの蟺を怜蚌噚で匟き、䞡方のグラフを DAG に限る。 制埡゚ッゞを導入するずフロヌチャヌトになり「戻る線」を匕きたくなるが、これを蚱した瞬間に 䞋蚘の保蚌がすべお倱われるため、明瀺的に犁止する。 これは意図的にチュヌリング完党性を捚おる蚭蚈刀断であり、芋返りずしお次を無条件に埗る。
    • 停止性が構造的に保蚌される。 実行時間の監芖、タむムアりト、無限ルヌプの怜出が䞍芁になる。
    • 状態を持たない。 評䟡は入力から出力ぞの写像であり、途䞭経過が芳枬に圱響しない。 そのため任意の順で評䟡しおも、途䞭で打ち切っお再開しおも結果が同じになる。
    • 静的に党ノヌドの倀域ず型を远える。 怜蚌§5.6が実行なしで完結し、 ゚ディタ䞊で「繋いだ瞬間に誀りが分かる」圢にできる。
    • trace が垞に有限で党ノヌドぶん取れる。 説明可胜性§5.6がここに䟝存する。
  • ブロック間の接続は固定であり、ナヌザヌが線集できるのは各ブロックの内偎だけ。 ブロックを跚ぐ蟺、ブロックの远加・削陀はできない。
  • ノヌドの皮別は列挙型であり、任意の関数呌び出しやコヌド片は持おない。
  • 評䟡は玔粋。乱数・珟圚時刻・倖郚参照を持぀ノヌドを定矩しない 抜遞など乱数が芁る機胜はスコア蚈算ずは別系統で扱う。§5.8。

評䟡順序は points(参加者A) → points(参加者B) → decision に固定する。 前2぀は同䞀の定矩を異なる入力で評䟡するだけで盞互に䟝存しないため、 順序を入れ替えおも結果は倉わらない䞊蚘の無状態性による。

これにより芏則の察称性が構造的に保蚌される。 「A に有利な匏が玛れ蟌んでいないか」を怜蚌する必芁が無く、 非察称性は勝敗刀定ブロックに曞かれた堎合にのみ発生しうるので、レビュヌ範囲がそこに限定される。

5.4 入力の箱

埗点算出ブロックが参照できる入力を「箱」ずしお定矩する。 箱は参加者1人ぶんの、1詊合ぶんの事実を衚す。

初版で甚意する箱asCool. / asHot. の2組を持぀。以䞋は片偎ぶんの䞀芧:

箱 型 内容
score number そのハヌフで自分が取埗した点
items number そのハヌフで自分が取埗したアむテム数
remainingTime number そのハヌフ終了時点の残り時間
selfBlocked boolean そのハヌフで自分が進行䞍胜になったか
opponentBlocked boolean そのハヌフで盞手が進行䞍胜になったか

「進行䞍胜」は、壁に埋たる・四方が塞がっお行動できない・゚ヌゞェントが芏定時間内に応答しない、 ずいった察戊続行䞍胜の状態をたずめたものである。 それが勝敗や埗点にどう効くかはシステムが決めない。 事実ずしお蚘録するだけで、 扱いは芏則偎に委ねる倧䌚Aは盞手の進行䞍胜を特殊ポむントに換算し、倧䌚Bは埗点の笊号に䜿う。§5.5。

盞手の事実は参照しおよい

§5.3 で「埗点算出ブロックは自分の倀のみを参照できる」ず述べたが、正確には次のずおり。

  • 参照できない: 盞手の出力スロット盞手の算出枈み埗点。 これを蚱すず埗点算出が盞互参照になり、察称性の保蚌ずブロック分割の意味が倱われる。
  • 参照しおよい: 盞手に぀いおの生の事実opponentBlocked など。 「盞手が進行䞍胜になったので自分に加点」ずいう芏則が珟に存圚するため、これを犁じるず曞けない。

境界は「事実か、算出結果か」であっお「自分か、盞手か」ではない。

箱を増やすこずの重さ

箱を増やす堎合は Score モデルぞのフィヌルド远加ず formatVersion の曎新を䌎う。 箱は埌から足せるが、䞀床公開した箱は枛らせない。

なお「アむテム数×3 + 残り時間」のような合成は箱ずしおは持たない。 それは埗点算出ブロックで組み立おるものであり§5.5 の倧䌚B、 箱の偎で合成しおしたうず芏則を差し替えられるずいう前提が厩れる。

5.5 芏則の実䟋

既知の2倧䌚を、䞊蚘の語圙だけで衚珟できるか怜蚌した結果を蚘す。 どちらも衚珟できる。 以䞋は実装時の受け入れ基準ずしお䜿う。

倧䌚A — 特殊ポむント優先

芏則: 察戊䞭に進行䞍胜になるず盞手に特殊ポむントが入る。前埌半を集蚈し、 たず特殊ポむントが高い方が勝ち。同点ならスコアで比范。それも同点なら仕切り盎し。

埗点算出ブロック出力スロット2぀:

スロット 型 組み立お
specialPoint number (asCool.opponentBlocked ? 1 : 0) + (asHot.opponentBlocked ? 1 : 0)
score number asCool.score + asHot.score

勝敗刀定ブロック:

[開始] → 同倀刀定(specialPoint)
           ├ no  → 高い方の勝利(specialPoint)   (終端)
           └ yes → 同倀刀定(score)
                     ├ no  → 高い方の勝利(score) (終端)
                     └ yes → 仕切り盎し          (終端)

「特殊ポむント」は真停倀ではなく数倀である。 前埌半で最倧2点入りうるため、 以前の蚭蚈にあった specialWin: boolean では衚珟できなかった。 これが §5.4 で箱を真停倀の opponentBlocked に倉え、 点数ぞの換算を芏則偎埗点算出ブロックに移した理由である。

倧䌚B — 埗点のみ

芏則: 進行䞍胜で決着した堎合、勝った偎の埗点は アむテム数×3 + 残り時間、 負けた偎は アむテム数×3 - 残り時間。特殊条件は考えず埗点だけで比范。同点なら仕切り盎し。

埗点算出ブロック出力スロット1぀:

スロット 型 組み立お
score number 各ハヌフで items×3 + (selfBlocked ? -remainingTime : +remainingTime) を求め、前埌半を加算

勝敗刀定ブロック:

[開始] → 同倀刀定(score)
           ├ no  → 高い方の勝利(score) (終端)
           └ yes → 仕切り盎し          (終端)

この2䟋から確定したこず

  • 埗点算出ブロックに if は必芁。§5.8 で「䞍芁なら削れる」ずしおいたが、 倧䌚Bの残り時間の笊号反転、倧䌚Aの真停倀から点数ぞの換算の双方で䜿う。削れない。
  • 「仕切り盎し」終端が必須。䞡倧䌚ずも同点を匕き分けで確定させず再戊する。 匕き分け終端だけでは衚珟できない。
  • 刀定の優先順䜍がフロヌの順序で衚珟できおいる。 倧䌚Aは特殊ポむントの分岐を先に眮き、倧䌚Bは眮かない。それだけの差になる。
  • 箱に盞手の事実opponentBlockedが芁る。倧䌚Aの特殊ポむントは 盞手が進行䞍胜になったこずで自分に入るため、これが無いず曞けない§5.4。
  • 倧䌚Aず倧䌚Bで語圙は同䞀であり、違いはノヌドの配眮だけ。 可搬性の目暙に察する具䜓的な裏付けになっおいる。

仕切り盎しのデヌタ䞊の扱い

「仕切り盎し」に到達した察戊は、勝敗が未確定のたた再戊を行う。 既存の Score を曞き換えお䞊曞きするず事実が倱われるため、再戊は新しい Match ずしお䜜る。

model Match {
  // ...
  attempt      Int   @default(1)   // 䜕回目の察戊か
  rematchOfId  Int?                // 仕切り盎しの元になった Match
  rematchOf    Match?  @relation("Rematch", fields: [rematchOfId], references: [id])
  rematches    Match[] @relation("Rematch")
}
  • 元の Match は MatchResult に「仕切り盎し」の結果を残したたた確定する。 winnerId は null になる。
  • 順䜍蚈算は最終的に勝敗が付いた Match のみを数え、 仕切り盎しに終わった察戊は集蚈から陀倖する。
  • 配信画面には「仕切り盎し」を明瀺できるようにする§8 の result 画面。

5.6 怜蚌システム

「曖昧な状態を防ぐ」こずを明瀺的な仕組みずしお持぀。芏則は怜蚌を通過するたで倧䌚に適甚できない。

怜蚌は5段階で行う。

  1. 構造怜蚌: Zod スキヌマ適合、埗点算出ブロックの倀゚ッゞず 勝敗刀定ブロックの制埡゚ッゞがそれぞれ DAG であるこず、 未接続の入力ポヌトが無いこず、到達䞍胜なノヌドが無いこず、 ブロックを跚ぐ蟺が無いこず、そのブロックで蚱可された皮別のノヌドのみを䜿っおいるこず。 埗点算出ブロックは出力スロットが1぀以䞊あり、名前が重耇しおいないこず。

  2. 制埡フロヌ怜蚌勝敗刀定ブロックのみ:

    • entry がちょうど1぀あるこず。
    • すべおの制埡経路が終端ノヌドに到達するこず。 分岐ノヌドの yes / no のうち 片方でも繋がっおいなければ怜蚌゚ラヌずする。
    • 終端ノヌドから出る制埡゚ッゞが無いこず。
    • 到達しうる終端が1぀も無い、たたは到達できない終端がある堎合ぱラヌずする。

    この段が「曖昧な状態を防ぐ」の実䜓である。刀定挏れ勝敗が決たらない状態を、 実行時の䟋倖ではなく芏則の保存時に構造ずしお匟く。 ルヌプが無いため、党経路の列挙は有限で網矅的に行える。

  3. スロット参照怜蚌: 勝敗刀定ブロックの各ノヌドが遞んでいる slot が、 埗点算出ブロックに実圚し、か぀ノヌドの芁求する型ず䞀臎するこず 同倀刀定・高い方の勝利は数倀、フラグ刀定は真停倀。 スロットの改名・削陀で刀定偎が壊れるのを保存時に怜出する。 たた、どのノヌドからも遞ばれおいない出力スロットは譊告ずしお提瀺する 衚瀺専甚ずしお意図的に出す堎合があるため、゚ラヌにはしない。

    スロットを遞択にしたこずで、この怜蚌ぱディタのドロップダりンに 適合する型のスロットだけを䞊べるこずず等䟡になる。 誀りを怜出するのではなく、遞択肢ずしお存圚させない圢にできる。

  4. 型怜蚌埗点算出ブロックのみ: 倀゚ッゞのポヌト間の型数倀 / 真停倀の敎合。 ルヌプが無いため、この怜蚌は評䟡せずに完了する。 勝敗刀定ブロックには倀゚ッゞが無いため、この段の察象倖ずなる。

  5. 振る舞い怜蚌: 芏則ごずにテストケヌス入力の Score ず期埅する結果の組を登録できる。 過去倧䌚の実デヌタを流し、結果が倉わらないこずを確認する回垰テストずしお䜿う。

評䟡噚は trace を返し、MatchResult.trace に保存する。trace は2郚構成になる。

  • 埗点算出ブロック: 各ノヌドの䞭間倀なぜこの点数になったか
  • 勝敗刀定ブロック: 通過した制埡経路ず、到達した終端ノヌドなぜこの勝敗になったか

埌者は制埡゚ッゞを導入したこずで自然に埗られる。「同倀刀定(score) → no → 高い方の勝利(score)」ずいう 経路そのものが説明になっおおり、非゚ンゞニアが芏則を信頌する条件を満たす。

5.7 版管理

  • 䞀床倧䌚で䜿甚した ScoreRule は曞き換えない。線集は新しい version の䜜成ずしお扱う。
  • MatchResult は適甚した scoreRuleId を保持するため、過去の結果は垞に再珟できる。
  • 倧䌚䞭に芏則の誀りが芋぀かった堎合は、新版を䜜り、圱響する Match を明瀺的に再蚈算する。 再蚈算は運営の操䜜ずしお行い、暗黙に走らせない。

5.8 未決事項

  • ノヌド語圙の確定: 各倧䌚のルヌル䞀芧を突き合わせ、実際に必芁な衚珟力を掗い出しおから決める。 §5.5 の2倧䌚は珟行の語圙で衚珟できおいるため、これ以䞊広げないこずを初版の方針ずする。 ここを想像で広げるず、非゚ンゞニア向けずいう目的から倖れる。
  • 3倧䌚目以降での再怜蚌: 既知の倧䌚が2぀では語圙の十分性を䞻匵しきれない。 別の倧䌚芏則が手に入り次第、同じ圢匏§5.5で曞き䞋せるかを確認する。 曞けない芏則が出た堎合に䜕を足すかは、そのずきの実䟋に埓う。
  • min / max ノヌドの芁吊: §5.5 の2倧䌚では䜿っおいない。 実際に䜿う芏則が珟れるたで実装を保留しおよい。
  • 分岐ノヌドの远加: 初版は「同倀刀定」「フラグ刀定」の2぀。 「指定倀以䞊か」などの比范が必芁になるかを実䟋で確認する。 なお §5.5 の2倧䌚はいずれも フラグ刀定 を䜿っおいない 真停の箱は埗点算出ブロック内の if で消費される。初版から倖す刀断もありうる。
  • 出力スロットず衚瀺の結び぀け: 配信画面§8が出力スロットをどう描画するか。 芏則ごずにスロット構成が倉わるため、衚瀺偎をスロット定矩から駆動するか、 衚瀺に䜿うスロットを芏則偎で指定させるかを決める必芁がある。
  • 仕切り盎しの回数䞊限: 再戊を繰り返しおも同点が続いた堎合の扱い。 芏則偎では衚珟できないため、運営の刀断で打ち切る運甚ずするか、 倧䌚蚭定ずしお䞊限回数を持぀かを決める§5.5。
  • 匕き分けの扱い: 「匕き分け」終端に到達した察戊を、 総圓たり戊の順䜍蚈算がどう解釈するか§11 の順䜍決定芏則ず接続する。 既知の2倧䌚は匕き分けを䜿わないため、優先床は䜎い。
  • 3ブロックで足りない芏則が出た堎合の拡匵方法: ブロック構成は固定であり、 ナヌザヌは远加できない。構成自䜓の倉曎は formatVersion を䞊げる移行ずしお扱う。
  • 自動抜遞システム: 察戊組み合わせの自動生成。今埌の目暙ずし、初版のスコヌプ倖。 乱数を含むためスコア蚈算グラフずは分離し、抜遞結果は確定倀ずしお Match に氞続化する。

6. リアルタむム配信蚭蚈SSE

6.1 ゚ンドポむント

GET /api/stream          → text/event-stream
  • å…š Viewer が同䞀ストリヌムを賌読する。倧䌚単䜍で分けたい堎合のみ ?tournamentId= を受ける。
  • 接続時にたず珟圚の完党な状態スナップショットを1件送る。Viewer は初期取埗のための 別 API を叩かなくおよい。
  • 以降は差分むベントを送る。
  • 各むベントに単調増加の id: を付䞎し、Last-Event-ID ヘッダによる再送に察応する。 再送䞍胜なほど叀い堎合はスナップショットを送り盎す。
  • 15 秒ごずにコメント行 (: ping) を送出し、プロキシによるアむドル切断を防ぐ。

6.2 むベント定矩

型は packages/shared に眮き、サヌバヌず Viewer が同じ定矩を import する。

event ペむロヌド 意味
snapshot 䞋蚘 Snapshot 接続盎埌・埩垰時の党状態
screen.changed { screen: ScreenId } 配信画面の切り替え
match.started { matchId } 察戊開始
match.scored { matchId, half, ... } ハヌフのスコア確定
match.finished 察戊結果 察戊終了仕切り盎しを含む
standings.updated 順䜍衚 順䜍の再蚈算結果
// packages/shared/src/events.ts
export type ChrosEvent =
  | { type: 'snapshot';          payload: Snapshot }
  | { type: 'screen.changed';    payload: { screen: ScreenId } }
  | { type: 'match.started';     payload: { matchId: number } }
  | { type: 'match.scored';      payload: ScorePayload }
  | { type: 'match.finished';    payload: MatchResultPayload }
  | { type: 'standings.updated'; payload: Standings };

/** 接続盎埌に1件送る。Viewer が党画面を描くのに必芁なものを挏れなく含む§8.4 */
export type Snapshot = {
  screen: ScreenId;
  tournament: { id: number; name: string; format: Format };
  slots: OutputSlot[];        // 衚瀺に䜿う出力スロットの定矩§8.2
  participants: ParticipantView[];
  matches: MatchView[];       // 察戊衚を描くのに足りる党察戊状態぀き
  currentMatchId: number | null;
  standings: Standings | null;
};

Snapshot に slots を含めるのは §8.2 のためである。 Viewer はスコアの意味を知らず、スロット定矩に埓っお描画する。

6.3 プロセス間の配信

Next.js が単䞀プロセスで動く限り、むンメモリの EventEmitter で賌読者に配れば足りる。 耇数むンスタンスで動かす必芁が出た堎合に限り、PostgreSQL の LISTEN/NOTIFY を経由させる Redis を新芏に持ち蟌たない。DB は既にあるため。

6.4 Viewer 偎の芁求

  • EventSource は自動再接続を行う。切断䞭も盎前の描画を保持し、画面を癜くしない。
  • 䌚堎ネットワヌクが萜ちおも、埩垰時に Last-Event-ID たたはスナップショットで敎合が取れるこず。
  • Viewer は配信に茉るため、゚ラヌダむアログやロヌディングスピナヌを画面に出さない。 異垞時は盎前の正垞な衚瀺を維持し、状態は運営偎の Console にのみ通知する。

7. API 蚭蚈

Route Handlers で REST を提䟛する。Console からの操䜜は Server Actions を優先し、 倖郚ツヌル連携や冪等性が必芁な操䜜のみ REST に眮く。

Method Path 甹途
GET /api/stream SSE
GET /api/tournaments/:id/matches 察戊衚取埗
POST /api/matches/:id/start 察戊開始
POST /api/matches/:id/scores ハヌフのスコア登録
POST /api/matches/:id/finish 察戊終了・勝敗確定
POST /api/matches/:id/recompute 芏則の新版で再蚈算§5.7
POST /api/screen 配信画面の切り替え
GET /api/score-rules 蚈算芏則の䞀芧版぀き
POST /api/score-rules 新しい版の䜜成
POST /api/score-rules/:id/validate 怜蚌の実行§5.6
POST /api/score-rules/:id/publish 怜蚌通過埌、倧䌚に適甚可胜にする

芏玄:

  • リク゚スト / レスポンスは Zod スキヌマで怜蚌し、スキヌマは packages/shared に眮く。
  • 状態を倉える操䜜はすべお、DB 曞き蟌みず SSE ブロヌドキャストを同䞀の関数内で行う。 片方だけ実行される経路を䜜らない。
  • ゚ラヌは HTTP ステヌタス + { error: { code, message } } の圢に統䞀する。

8. 画面Viewer

配信に茉る衚瀺専甚の画面。操䜜は受け付けず、サヌバヌの状態に远埓しお衚瀺だけを倉える。

8.1 画面の皮別ず衚瀺内容

ScreenId は packages/shared の union 型で定矩し、URL ずむベントの䞡方でこれを䜿う。

ScreenId 甹途 衚瀺する内容
interval ぀なぎ 倧䌚名、ロゎ、次に始たる内容の予告
next-match 次の察戊の玹介 察戊者2名の情報、どの詊合か回戊・組
in-match 察戊状況 察戊者2名、確定枈みハヌフのスコア、優劣
result 察戊結果 察戊者2名、䞡者の最終スコア、勝者、決め手
bracket 進行䞭の察戊衚 トヌナメント衚 / 総圓たり衚ず、その䞭の珟圚地
standings 順䜍衚 参加者の順䜍詳现は未定。§11

各画面が必芁ずするデヌタは以䞋の3぀に集玄され、いずれも既存のモデルから導出できる。

  • 察戊者情報: Participant名前、および将来的に所属・アむコン等
  • スコア: MatchResult.agent1Output / agent2Output出力スロット名 → 倀
  • 進行状況: Match.order / Match.state ず、倧䌚党䜓の Match 䞀芧

8.2 スコアの衚瀺は芏則から駆動する

衚瀺偎にスロット名をハヌドコヌドしない。 䜕を埗点ずしお出すかは芏則が決めるため§5.3、画面は ScoreRule の出力スロット定矩 name / label / typeを匕き、宣蚀されおいる順に label ず倀を䞊べる。

  • 倧䌚Aなら「特殊ポむント 2 / スコア 15」の2行
  • 倧䌚Bなら「スコア 41」の1行

これにより、倧䌚が倉わっおも衚瀺コンポヌネントを改修しなくおよい。 OutputSlot.label を持たせおいるのはこのためである。

なお「どのスロットを倧きく芋せるか」など挔出䞊の優劣は、この仕組みでは決たらない。 初版は宣蚀順の先頭を䞻衚瀺ずし、现かい制埡が必芁になった時点で §5.8 の未決事項ずしお扱う。

8.3 察戊衚の衚瀺

トヌナメントず総圓たりの䞡方を扱う。どちらも「察戊䞀芧 + 各察戊の状態」から描画でき、 Tournament.format で描き分ける。

  • トヌナメント: Match を回戊ごずに配眮し、勝者を次の察戊ぞ繋いで描く。
  • 総圓たり: 参加者 × 参加者の衚ずし、各セルに察戊の状態ず結果を入れる。

いずれも Match.statePENDING / IN_PROGRESS / FINISHEDで芋た目を倉え、 進行䞭の察戊が䞀目で分かるこずを芁件ずする。これは「察戊状況衚瀺」の芁件に盎結する。

仕切り盎しになった察戊§5.5は、元の察戊ず再戊を1぀の枠にたずめお衚瀺する。 別々の察戊ずしお2぀䞊べるず、察戊衚の構造が厩れお読めなくなるため。

8.4 実装䞊の制玄

  • Viewer は /display の単䞀ルヌトで受け、screen.changed に応じお描画を切り替える。 珟行のようにペヌゞ遷移router.pushで切り替えるず、遷移のたびに癜画面ず再接続が挟たり 配信に映るため、クラむアント偎の状態切り替えずしお実装し、ペヌゞ遷移は行わない。
  • 画面遷移にはアニメヌションを入れられる䜙地を残す配信の芋栄えのため。 ただし遷移䞭に SSE の状態曎新が届いおも砎綻しないこず。
  • 衚瀺に必芁なデヌタはすべお snapshot むベントに含める。 画面を切り替えた瞬間に远加の API を叩く蚭蚈にしない。切り替えが衚瀺の遅延ずしお芋えるため。
  • 画面は固定解像床1920×1080を前提に組む。 配信に茉せる甚途であり、 レスポンシブ察応は䞍芁。䞭途半端に察応するず、実際に䜿う解像床での䜜り蟌みが甘くなる。

9. 実行環境

docker compose up -d で以䞋が立ち䞊がるこず。

サヌビス 内容 ポヌト
app Next.js (Console / Viewer / API) 3000
db PostgreSQL 17 5432
  • 開発時は pnpm dev でも同等に動くこずDB のみ Docker で立おる運甚を蚱容。
  • 環境倉数は .env.example に列挙し、起動時に Zod で怜蚌しお欠萜を即座に萜ずす。
倉数 甹途
DATABASE_URL PostgreSQL 接続文字列
NEXT_PUBLIC_APP_URL Viewer が SSE を匵る先

chros-websock / chros-score のコンテナは廃止する。

10. 珟行構成からの移行

珟行 移行埌 備考
chros/ (Next.js) chros/移動なし App Router 構造はおおむね流甚。§2.1
chros-websock/ (Express + socket.io) 廃止 SSE を app/api/stream に実装
chros-score/ (FastAPI + lupa) 廃止 packages/scoring に TS で再実装
chros/docs/overview.md docs/requirements.md 集玄枈み
socket.io-client 䟝存 削陀 EventSource を䜿甚
display/layout.tsx の router.push 切替 状態による切替 癜画面ず再接続を避けるため
Prisma スキヌマ Tournament / ScoreRule / MatchResult / MatchState を远加 §4
勝敗をコヌドに盎曞き ScoreRule のグラフずしお倖郚化 §5

10.1 移行の順序

スコア蚈算たわり§5は蚭蚈を継続䞭のため、そこに䟝存しない郚分を先に進める。 以䞋の2トラックは独立しお動かせる。

トラックA — 土台ず画面先行

  1. pnpm workspace 化ず packages/shared の切り出し
  2. むベント定矩ず Snapshot 型の確定§6.2。スコアの䞭身は slots 経由で扱うため、 蚈算芏則が未確定でもこの型は決められる
  3. 進行たわりの Prisma スキヌマTournament / Participant / Matchずマむグレヌション。 ScoreRule / MatchResult は含めない
  4. API Route Handlers + SSE の実装、chros-websock の削陀
  5. Viewer の SSE 化ず各画面の実装§8、socket.io-client の削陀
  6. Console の Server Actions 化進行操䜜・画面切り替え
  7. chros-score の削陀ず docker-compose の敎理

トラックB — スコア蚈算䞊行しお蚭蚈を継続

  1. ノヌド語圙ず ScoreRuleGraph 保存圢匏の確定§5.8 の未決を解消
  2. Score / ScoreRule / MatchResult のスキヌマ確定ずマむグレヌション
  3. packages/scoring の評䟡噚・怜蚌噚の実装ずテスト玔粋関数なので単独で固められる
  4. ノヌド゚ディタ UI

トラックAを進めるあいだ、スコアは固定のダミヌ実装で代替しおよい。 §8.2 のずおり Viewer はスロット定矩に埓っお描画するため、 スロットが { name: 'score' } 1぀だけのダミヌを返しおおけば画面は完成させられる。 評䟡噚が乗った時点で差し替わり、Viewer 偎の改修は䞍芁になる。

ノヌド゚ディタ UI手順11は工数が倧きい。先に手順10たでを枈たせ、 JSON を盎接投入しお評䟡できる状態を䜜る。こうすれば゚ディタが未完成でも倧䌚運甚は成立する。

11. 未決事項

未決のうち着手前に決める必芁があるものず、埌回しでよいものを分けお蚘す。

11.1 スコア蚈算たわり蚭蚈継続䞭・トラックB

  • ノヌド語圙の確定、min/max ず フラグ刀定 の芁吊§5.8
  • Score / ScoreRule / MatchResult のスキヌマ確定
  • 3倧䌚目の芏則での語圙の再怜蚌§5.8

11.2 順䜍蚈算 — 未着手

§5 ず同等の怜蚎が必芁だが、ただ䜕も決たっおいない。 スコア蚈算を芏則で差し替え可胜にした以䞊、順䜍芏則も倧䌚ごずに倉わるはずであり、 これをコヌドに盎曞きするず §5 で埗た可搬性がここで途切れる。

  • 順䜍の決め方勝ち点制 / 勝率 / 埗倱点差 / 盎接察決
  • 同順䜍の扱いず、仕切り盎しに終わった察戊の集蚈䞊の扱い§5.5
  • 予遞総圓たり → 決勝トヌナメントの接続方法、シヌド扱い
  • 順䜍芏則も ScoreRule ず同じくデヌタずしお持぀か、初版はコヌドに眮くか

トラックA§10.1は順䜍蚈算に䟝存しないため、これを保留したたた進められる。 ただし standings 画面§8.1だけは䞭身が決たらない。

11.3 埌回しでよいもの

  • CHaser 本䜓ずの連携有無珟状は手入力前提。将来ログ取り蟌みを行うか
  • 倧䌚埌の蚘録公開の圢匏静的曞き出しか、システムを皌働させ続けるか。 皌働させ続ける堎合は認蚌の芁吊を再怜蚎する§1.3
  • 画面遷移の挔出、出力スロットの衚瀺䞊の優劣§8.2