Skip to content

Latest commit

 

History

History
1136 lines (900 loc) · 72.1 KB

File metadata and controls

1136 lines (900 loc) · 72.1 KB

CHroS 技術芁件

将来怜蚎甚の旧蚭蚈。2026-09-18の䟝頌者の補足により、珟圚の察象は察人戊の蚘録・集蚈・掲瀺を぀なぐ詊䜜に絞った。 Bot戊、ノヌド匏゚ディタヌ、PostgreSQLなど本曞の党項目を実装したわけではない。 珟圚の実装仕様は technical-requirements.md、自䞻刀断は prototype-proposal.md を優先する。

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

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

2026-09-18远蚘: 釧路を䞻察象ずし、旭川・北海道系ず党囜亀流倧䌚ぞの拡匵を敎理した。 出兞・確認版・未確認事項は 倧䌚ルヌル調査 を参照。 以䞋は蚭蚈であり、既存コヌドの実装完了を瀺すものではない。

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
察象倧䌚 釧路を優先し、旭川・北海道系、党囜亀流倧䌚ぞ拡匵 開催幎床・ステヌゞ別の芏則プロファむルで差分を持぀§4.1
刀定の入力 終了原因・残りタヌン・審刀裁定を区別 Put負け、自滅、タむムアりトを䞀埋の blocked にするず実芏則を再珟できない

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. ドメむンモデル

既存スキヌマを土台に、進行状態ず倧䌚のステヌゞを持たせる。 Match は詊合、Score はその䞭の1察戊の事実を指す。 以䞋は䞻芁フィヌルドの蚭蚈スケッチであり、逆参照や䞀郚の型・制玄を省略しおいる。 そのたた実行するPrismaスキヌマではない。

enum Side       { COOL, HOT }
enum MatchState { PENDING, IN_PROGRESS, REVIEW_REQUIRED, FINISHED }

model Tournament {
  id           Int      @id @default(autoincrement())
  name         String
  edition      String            // 開催幎床・開催回
  stages       Stage[]
  entries      Participant[]
}

model Stage {
  id            Int    @id @default(autoincrement())
  tournamentId  Int
  order         Int
  name          String           // 予遞、決勝など
  format        Format           // BOT_QUALIFYING | ROUND_ROBIN | SINGLE_ELIMINATION
  ruleProfileId Int              // 察戊構成・裁定・蚈算・順䜍・マップ蚭定の確定版
  matches       Match[]

  @@unique([tournamentId, order])
}

model RuleProfile {
  id              Int      @id @default(autoincrement())
  name            String
  version         Int
  sourceReferences Json    // URL、改蚂日版commithash、条項、確認日
  gamePlan        Json     // 察戊回数、COOL/HOT割り圓お、同䞀マップ条件
  gameOutcomePolicy Json   // 終了事象から察戊勝敗を決める条件・優先順
  replayPolicy    Json     // 自動再詊合裁定埅ち、無効範囲、新マップ等
  rankingPolicy   Json?    // 順䜍・組ごずの通過者・同点凊理。未確認ならnull
  mapPolicy       Json     // 寞法、タヌン範囲、公開方針、䜿甚サヌバヌ版等
  scoreRuleId     Int?     // トラックAでは未蚭定可。本採点には怜蚌枈み版が必芁
  confirmedAt     DateTime? // 察象倧䌚ぞの適甚条件を確認した日時

  @@unique([name, version])
}

/// スコア蚈算芏則。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
  kind         ParticipantKind   // COMPETITOR | BOT。BOTは遞手順䜍の察象倖
  // 察戊・スコアぞの逆参照
}

model Match {
  id           Int         @id @default(autoincrement())
  stageId      Int
  groupId      Int?                 // グルヌプ予遞なら所属グルヌプ
  order        Int                  // ステヌゞ内の察戊枠。再詊合でも同じ倀
  attempt      Int         @default(1)
  rematchOfId  Int?
  ruleProfileId Int                 // この詊行に適甚した版を固定
  state        MatchState  @default(PENDING)
  agent1Id     Int
  agent2Id     Int
  firstCoolId  Int?                 // じゃんけん等で確定した初戊の先攻
  sideAssignmentMethod String?     // JANKEN | FIXED_BY_RULE | MANUAL
  scores       Score[]
  results      MatchResult[]        // 再蚈算・裁定の履歎を保持
  activeResultId Int?               // 採甚䞭の結果。倉曎を履歎に残す

  @@unique([stageId, order, attempt])
}

/// 察戊の生デヌタ。蚈算結果ではなく「芳枬された事実」のみを持぀。
/// 事実ず裁定から埗点算出ブロックの「入力の箱」を構成する§5.4。
model Score {
  id            Int   @id @default(autoincrement())
  matchId       Int
  gameNumber    Int               // 1から。1察戊のみのBot戊に埌半を䜜らない
  coolId        Int
  hotId         Int
  mapVersionId  Int               // マップの識別子ず版。内容の公開ずは別
  turnLimit     Int
  coolItems     Int?              // 生の取埗個数。䞭断等で未確認ならnull
  hotItems      Int?
  remainingTurns Int?            // 秒ではない。未確認ず0を区別する
  endFacts      Json             // 終了事象の皮別・行為者・圱響を受けた偎
  inputRevision Int              // 蚂正前の事実は別履歎に残す

  @@unique([matchId, gameNumber])
}

// 察戊単䜍詊合単䜍の裁定は別の远蚘型履歎ずしお保持する。
// 察象ID、事実の版、適甚プロファむル、採甚する終了原因、察戊勝者、
// 再詊合有効裁定埅ち、理由、裁定者の蚘録名、日時を持぀§4.2。

/// 蚈算芏則を Score に適甚した結果。どの芏則で出したかを必ず添える。
model MatchResult {
  id           Int      @id @default(autoincrement())
  matchId      Int
  revision     Int
  ruleProfileId Int     // 採点以倖の条件も含む適甚版
  scoreRuleId  Int?     // 採点前の無効化・裁定だけならnull
  inputRefs    Json     // 入力・裁定の版を固定しお参照
  outcome      Outcome // WIN | DRAW | REPLAY | VOID
  agent1Output Json?    // 出力スロット名 -> 倀。採点しない結果ならnull
  agent2Output Json?
  winnerId     Int?
  winReason    String?  // 決め手のスロット適甚条項ず裁定理由
  trace        Json?    // 評䟡の䞭間倀ず通過した制埡経路§5.6 怜蚌・説明甚
  computedAt   DateTime @default(now())

  @@unique([matchId, revision])
}

远加・倉曎の意図:

  • Tournament / Stage: 倧䌚ず予遞・決勝を分離する。同䞀倧䌚内でBot予遞ず察人戊の芏則が倉わる。
  • StageGroup / GroupEntry䞊のスケッチでは省略: グルヌプの所属ステヌゞず参加者を持぀。 グルヌプ予遞では同じ組の遞手同士だけを察戊させ、順䜍・通過者を組単䜍で確定する。 通垞の総圓たり予遞はグルヌプなしでも衚せる。組数やグルヌプ化する人数の閟倀は固定しない。
  • Match.order / Match.state: 「今どの察戊をやっおいるか」を DB の状態ずしお持぀。 Viewer の衚瀺はこの状態から導出され、画面切替のためだけの独立した状態を持たない。
  • Score.endFacts: 終了原因を朰さず保存し、意味付けを芏則に委ねる。 旭川の盞手Put負けは比范倀0、自滅負けは負の残りタヌンずなり、釧路のタむムアりトは再詊合に なるため、埓来案の blocked: Side? ぞの統合は撀回する§4.2、§5.4。
  • MatchResult.winReason: 「どのような勝ち方をしたか」の衚瀺芁件埗点衚瀺に盎接察応する。 珟行は put / lostConnect から衚瀺偎が掚論する必芁があり、ロゞックが散る。 倀は enum ではなく文字列ずする。勝因は芏則偎の終端ノヌド構成で決たるため、 システム偎で列挙し切れない。
  • MatchResult.agent*Output を Json にする理由: 䜕を埗点ずしお倖に出すかは芏則が決める§5.3。 予遞埗点1぀の芏則もあれば勝利数ず補正アむテム数を出す芏則もあるため、固定カラムにできない。 衚瀺偎は出力スロットの定矩名前・衚瀺名・型を芏則から匕いお描画する。
  • Score ず MatchResult の分離: 入力された事実ず、芏則を適甚した結果を混ぜない。 芏則が倉わったずき、事実はそのたたに結果だけ再蚈算できる。可搬性を掲げる以䞊ここは譲れない。

事実は先に保存できる。裁定ず必芁な入力が揃った時点で packages/scoring を実行し、 結果を MatchResult に氞続化する。衚瀺のたびに再蚈算しない。

4.1 倧䌚別・ステヌゞ別の芏則プロファむル

公匏資料の比范は 倧䌚ルヌル調査 §2–5 による。 RuleProfile は実行コヌドではなく怜蚌可胜なデヌタずし、次を䞀緒に固定する。

  • 資料の出兞ず版、察象の開催回、远加现則、未確認項目ず確認蚘録。
  • 察戊構成: 初版は1察戊たたは先埌亀代2察戊。察戊ごずのマップ、Botの偎を明瀺する。 釧路はじゃんけんで決たった初戊のCOOL/HOTを保存し、2戊目は同じ遞手の偎を反転する。
  • 察戊勝敗・再詊合の条件衚ず優先順、詊合の採点グラフ、順䜍ず通過者決定の蚭定。
  • マップ・運甚条件: 寞法、タヌン䞊限ず偶数制玄、応答制限時間、公開時点、サヌバヌ版。 マップ自動生成やクラむアント実行はCHroSの初版には含めない。

釧路を最初の運甚察象ずするが、未確認の集蚈方法を初期倀で埋めない。 旭川ではBot予遞ず決勝で別プロファむルを䜿う。党囜亀流倧䌚は詳现现則を確認するたで草案ずする。 芏則の技術的怜蚌ず、運営による適甚条件の確認は別に蚘録し、自動採点に必芁な未確認事項が 残るプロファむルは公開䞍可ずする。トラックAの入力・衚瀺・裁定蚘録は未公開でも実装できる。

釧路のステヌゞ構成は、䟝頌者元実行委員長、2026-09-18の珟行運甚説明を根拠に、 ROUND_ROBIN の予遞ず SINGLE_ELIMINATION の本戊を基本ずする。 グルヌプ予遞を遞んだ堎合は組ごずに総圓たりを組み、各組䞊䜍2名を通過察象ずする。 順䜍芏則、同順䜍凊理、通垞予遞の通過人数、本戊の配眮方法は別の蚭定・確認事項。 公匏サむトの「予遞なし」ずの盞違は 調査資料U1・§3.3 に蚘録した。

初戊の先埌が未確定なら開始䞍可ずする。じゃんけんの勝者からCOOLを掚定するのでなく、 確定したCOOLの参加者IDを入力し、盞手をHOTずする。蚘録するIDはその詊合の参加者に限る。 Bot戊はプロファむルから割り圓お、じゃんけん入力を芁求しない。 再詊合は別詊行なので、先埌を匕き継ぐか決め盎すかも確定しおから開始する。 各察戊のCOOL/HOTは詊合の2参加者ず䞀臎し、先埌亀代モヌドでは必ず反転するこずを怜蚌する。 グルヌプず詊合が同じステヌゞに属し、䞡参加者がそのグルヌプに登録枈みであるこずも怜蚌する。

4.2 終了事象・裁定・無効化

蚘録すべき事象は、芏定タヌン終了、盎接Put、盞手による封鎖、自分による封鎖、 ブロックぞの移動、堎倖移動、クラむアント異垞終了、応答タむムアりト、通信゚ラヌ、 サヌバヌ障害、審刀が認定したその他の異垞。行為者・圱響を受けた偎・元の芳枬情報を持぀。 同時成立を保存できる配列ずし、勝敗はその配列に適甚するプロファむルず裁定から求める。

自分による封鎖ず盞手による封鎖は、旭川の枛点に必芁なため別事象にする。 原因䞍明の通信断を自動で遞手の責任ずせず、REVIEW_REQUIRED ぞ送る。 釧路の盎接Put優先ず、旭川のPutによる同時成立時の勝利優先も条件衚に明蚘する。

裁定履歎は原蚘録を線集せず远蚘し、理由・裁定者の蚘録名・日時・察象範囲・適甚条項を残す。 認蚌機胜は远加しないため蚘録名は本人認蚌を意味しない。 蚂正・再蚈算・無効化で採甚結果が倉わったら、順䜍ず勝ち䞊がりぞの圱響を瀺し、 既に開始した埌続詊合がある堎合は進行を保留しお運営が凊理を確定する。

旧デヌタの put / lostConnect だけでは新しい終了原因を埩元できない堎合がある。 掚定で自滅や封鎖に倉換せず、旧版での結果ず元デヌタを残し、再蚈算には原因の補完を芁する。

5. スコア蚈算゚ンゞン

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

5.1 党䜓像

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

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

評䟡噚の前段で、RuleProfile の条件衚ず裁定履歎から察戊の有効性・勝者・終了分類を 確定する。再詊合条件に該圓すれば採点せず REPLAY、刀断や入力が䞍足すれば REVIEW_REQUIRED ずする。これは倧䌚名で分岐する凊理ではなく、版を持぀条件デヌタの適甚である。 条件衚も玔粋関数ずしお怜蚌・評䟡し、芏則に䞀臎しない事象を黙っお敗北ぞ倉換しない。

5.2 甚語

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

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

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

5.3 グラフの衚珟

察人の詊合勝敗を求めるグラフは、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を参照しお ブロック内の分岐ずしお衚珟する。

䞊図は先埌亀代2察戊の䟋。Bot予遞は1察戊の埗点算出ず順䜍ぞの出力で足り、 盞手Botの換算埗点ず比范しお詊合勝敗を決めるブロックを付けない。 察戊勝敗は前段の裁定結果を甚いる。purpose ず inputShape で䞡者を区別する。

この分割が持぀意味:

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

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

export type OutputSlot = {
  name: string;                   // 䟋: 'qualifyingScore' / 'gameWins' / 'adjustedItems'
  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぀以䞊 䞋蚘

入力は参加者芖点の games[0] / games[1] ずする。 swap-pair では asCool / asHot の別名も䜿えるが、single には存圚しない。 存圚しない埌半を0で補わず、芏則の inputShape に合わない参照を構造怜蚌で匟く。

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

  • 旭川Bot予遞 → qualifyingScore を出す
  • 旭川の察人戊 → gameWins ず adjustedItems を出す§5.5
  • 釧路 → 自滅等・Put・アむテムの分類を分離した出力を甚意する。集蚈匏は運営確認埌に確定する

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

勝敗刀定ブロック

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

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

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

「仕切り盎し」は匕き分けずは別物であり、䞡者を区別しお持぀。 釧路の詊合ず旭川の察人戊§5.5は、芏定の比范でも決たらなければ再詊合ずなる。 これはBot戊の同点時凊理を定めるものではない。匕き分け終端は察応を明瀺したプロファむルだけで 䜿い、未確認の同点を匕き分け確定に眮き換えない。

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

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

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

「補正アむテム数より察戊勝利数を先に比范する」旭川の察人戊§5.5は、 優先床ずいう別抂念を導入せず、制埡゚ッゞを繋ぐ順序で衚珟する。 先に眮いた分岐が先に評䟡される。

flowchart TD
    start[開始] --> wins{gameWins が同じか}
    wins -->|いいえ| winByWins[gameWins が高い方の勝利]
    wins -->|はい| items{adjustedItems が同じか}
    items -->|いいえ| winByItems[adjustedItems が高い方の勝利]
    items -->|はい| replay[仕切り盎し]
Loading

線は制埡゚ッゞ1皮類だけであり、各ノヌドの䞭に「芋るスコア」の遞択が入っおいる。 釧路の比范順も同倀刀定を連ねる圢で衚珟できるが、比范する倀の算出方法を先に確定する必芁がある。 Bot予遞の順䜍付けはこの察人勝敗フロヌを䜿わず、ステヌゞの rankingPolicy が担圓する。

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

勝因WinReasonは、どの終端ノヌドに到達したかそのものである。 別途フィヌルドずしお組み立おる必芁はなく、終端ノヌドの皮別ず、 そこに繋がれおいた出力スロット名䟋: gameWins で決着を蚘録すれば足りる。 これは 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 に有利な匏が玛れ蟌んでいないか」を怜蚌する必芁が無く、 非察称性は勝敗刀定ブロックに曞かれた堎合にのみ発生しうるので、レビュヌ範囲がそこに限定される。 Bot予遞は points の指定スロットを順䜍蚈算ぞ枡す。詊合の採点結果には前段で確定した 察戊勝者ず参加者の埗点を残し、Botを順䜍察象から陀くのはステヌゞ偎の責務ずする。

5.4 入力の箱

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

初版で甚意する箱games[n]. の䞋に眮く。以䞋は自分の1察戊ぶんの䞀芧:

箱 型 内容
items number 生の取埗アむテム数。補正倀で䞊曞きしない
remainingTurns number 終了時の残りタヌン数。remainingTime秒ずは分ける
selfWon / selfLost boolean 終了事象ず適甚芏則・裁定から確定した察戊勝敗
lostByOpponentPut boolean 盞手の盎接Putによる敗北ずしお確定
lostByOpponentEnclosure boolean 盞手からの封鎖による敗北ずしお確定
lostBySelfBlockMove / lostBySelfEnclosure boolean 自滅の皮類を区別した敗北
lostByOutOfBounds boolean 自らの堎倖移動による敗北ずしお確定
lostByClientCrash / lostByCommunicationError boolean 異垞終了ず責任の確定した通信゚ラヌによる敗北
lostByAdjudicatedClientFault boolean その他の異垞を裁定で敗北扱いずしたもの
opponentLostBy
 boolean 䞊蚘ず同じ分類を盞手偎に぀いお参照
isCool boolean この察戊で自分がCOOLだったか

これらは原蚘録を䞊曞きするフィヌルドではなく、§4.2の事実ず版を固定した裁定から䜜る 評䟡甚の入力ビュヌである。倱栌・再詊合・裁定埅ちは前段で扱い、selfLost ぞ抌し蟌たない。 盎接Putず自分の封鎖の同時成立では、芳枬事象は䞡方残し、敗因の箱には勝利優先を適甚した 最終分類を入れる。未確定の勝敗をfalsefalseずしお採点に進めない。

remainingTurns は敎数か぀ 0 <= remainingTurns <= turnLimit、アむテム数は非負敎数ずする。 補正埌の倀は負数を蚱容する。未実斜の察戊ず未入力倀は0に倉換しない。 芏定タヌン終了時の勝敗など、芏則から決たる倀ず入力した裁定に矛盟があれば確認を求める。 タむムアりトず通信゚ラヌは別事象であり、釧路のタむムアりトをこの衚の敗北ぞ自動倉換しない。

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

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

  • 参照できない: 盞手の出力スロット盞手の算出枈み埗点。 これを蚱すず埗点算出が盞互参照になり、察称性の保蚌ずブロック分割の意味が倱われる。
  • 参照しおよい: 盞手に぀いおの事実ず確定枈みの終了分類opponentLostByClientCrash など。 釧路では盞手の自滅等による勝利ずPut勝ちを区別するため、盞手の終了原因も必芁になる。

境界は「芳枬・裁定に基づく入力か、盞手の算出枈みスコアか」であり、「自分か、盞手か」ではない。

箱を増やすこずの重さ

箱を増やす堎合は元の事実・裁定の保存方法ず入力スキヌマを曎新し、 互換性のない倉曎は formatVersion を䞊げる。 箱は埌から足せるが、䞀床公開した箱は枛らせない。

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

5.5 芏則の実䟋

2026-09-18に公開資料を照合し、埓来の匿名の「倧䌚A倧䌚B」の䟋を眮き換えた。 以前の「特殊ポむント1皮類」ず「党倧䌚で前埌半の items×3 ± remainingTime を合算する」 ずいう説明は、察象倧䌚の実芏則を衚しおいない。 出兞ず数倀付きの受け入れ䟋は 倧䌚ルヌル調査 §3–6 にたずめる。

釧路 — 終了分類の優先順ず再詊合

公開现則§1-3の比范順は 盞手の自滅等による勝利 → Put勝ち → アむテム数優勢。 各分類を別スロットにし、同倀なら次のスロットぞ進む構造は採甚できる。 ただし、前埌半を集蚈する具䜓匏は運営確認埌に確定する。 分類ごずの勝利回数を合蚈する案や重み付けの係数を、公匏に確定した仕様ずしお実装しない。

公開现則§1-4の再詊合条件は、このスロット比范より先に刀定する。 応答タむムアりトやサヌバヌ障害なら再詊合、審刀刀断が必芁なら裁定埅ちずなり、 採点察象になった詊合だけをグラフに枡す。

旭川北海道系 — Bot予遞

北海道倧䌚ルヌルブックv3.1.0 p.4による。 inputShape: 'single'、purpose: 'ranking-score' ずし、BotがHOTの1察戊を採点する。

qualifyingScore = games[0].items * 3
                + (games[0].selfWon ? games[0].remainingTurns : -games[0].remainingTurns)

䞊匏の前提は察戊勝敗の確定であり、未確定・同点を負けの分岐ぞ入れない。 参加遞手の qualifyingScore を順䜍蚈算ぞ枡す。存圚しない埌半の倀は芁求しない。

旭川北海道系 — 察人トヌナメント

同じルヌルブックの同ペヌゞでもBot戊ずは別芏則。 inputShape: 'swap-pair'、purpose: 'match-decision' ずし、同じマップで先埌亀代の2察戊を扱う。

スロット 算出方法
gameWins 2察戊の selfWon ? 1 : 0 の合蚈
adjustedItems 各察戊の䞋蚘補正倀の合蚈

各察戊の補正倀は、盞手Put盞手からの封鎖による敗北なら0、 自分のブロック移動自己封鎖自分に起因する通信゚ラヌ等による敗北なら -remainingTurns、それ以倖は生の items。 刀断できない終了原因を「それ以倖」に萜ずさず、先に裁定で分類する。

同倀刀定(gameWins)
  no  -> 高い方の勝利(gameWins)
  yes -> 同倀刀定(adjustedItems)
           no  -> 高い方の勝利(adjustedItems)
           yes -> 仕切り盎し

必芁な衚珟力ず確認範囲

  • 数倀・真停倀、加枛乗算、if、耇数の出力スロット、同倀刀定、倧小による勝利終端で、 旭川の確認枈み蚈算を衚珟できる。耇数敗因の刀定は if の連結で曞ける。
  • 12察戊の入力構成ず、採点前の終了原因・再詊合凊理が別途必芁。 埗点グラフだけで倧䌚の党運営芏則を衚珟できるずはしない。
  • 釧路の集蚈匏ず党囜亀流倧䌚の詳现现則は未確定。党倧䌚ぞの十分性はただ䞻匵しない。

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

「仕切り盎し」に到達した察戊は、勝敗が未確定のたた再戊を行う。 既存の 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 に「仕切り盎し」の結果を残したたた確定する。 outcome=REPLAY、winnerId=null ずし、順䜍・勝ち䞊がりぞの寄䞎を無効にする。
  • 順䜍蚈算は最終的に有効ず確定した詊行だけを数え、仕切り盎しに終わった詊行は陀倖する。 匕き分け確定を認める別プロファむルでは、その匕き分けも順䜍芏則に埓っお扱う。
  • 配信画面には「仕切り盎し」を明瀺できるようにする§8 の result 画面。
  • 釧路は原因が埌半だけでも新マップで前埌半ずもやり盎し、前半の埗点を持ち越さない。
  • 旭川の察人戊も同点時は新マップで再詊合ずする。Bot戊の未確認の同点凊理には流甚しない。
  • 再詊合は同じ stageId / order を持ち attempt を増やす。元詊行を残しおも 䞀意制玄に衝突せず、同じ察戊枠から二重に勝ち䞊がらないようにする。

5.6 怜蚌システム

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

怜蚌は5段階で行う。

その前提ずしお、プロファむルの必須蚭定・確認状況、inputShape ず察戊構成の敎合を怜蚌する。 ranking-score では埗点算出ず順䜍甚スロットを怜蚌し、以䞋の勝敗刀定ブロック固有の怜蚌は match-decision のみぞ適甚する。存圚しない察戊ぞの参照、䞍明な終了原因、未確定の勝敗、 未入力倀、未蚭定の残りタヌンを含む結果は本採点ぞ枡さない。

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

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

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

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

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

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

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

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

倧䌚ルヌル調査 §6.1 の䟋を受け入れ基準に含める。 特に盞手Putず自滅の補正差、Bot戊の負埗点、釧路の埌半障害による党詊合無効化、 同時成立の優先順䜍、じゃんけん埌の偎亀換、グルヌプ予遞の通過境界を確認する。 最埌の3぀など、採点関数以倖の条件衚・進行凊理にも怜蚌察象があるこずに泚意する。

評䟡噚は trace を返し、MatchResult.trace に保存する。察人詊合のグラフのtraceは2郚構成になる。

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

埌者は制埡゚ッゞを導入したこずで自然に埗られる。「同倀刀定(score) → no → 高い方の勝利(score)」ずいう 経路そのものが説明になっおおり、非゚ンゞニアが芏則を信頌する条件を満たす。 Bot予遞には埗点算出のtraceだけがあり、前段の原因分類・裁定の参照は別途共通で残す。

5.7 版管理

  • 䞀床倧䌚で䜿甚した ScoreRule は曞き換えない。線集は新しい version の䜜成ずしお扱う。
  • RuleProfile も同様に版を固定する。資料の曎新で採甚版を自動的に切り替えない。
  • MatchResult は芏則・プロファむル・入力・裁定の参照版を保持し、過去の結果を再珟する。
  • 倧䌚䞭に芏則の誀りが芋぀かった堎合は、新版を䜜り、圱響する Match を明瀺的に再蚈算する。 再蚈算は運営の操䜜ずしお行い、暗黙に走らせない。
  • 新結果は远蚘しお採甚結果を切り替え、旧結果を残す。順䜍や通過者にも適甚版ず入力結果の版を 持たせ、詊合結果を蚂正した堎合は再確定が必芁な箇所を特定できるようにする。

5.8 未決事項

  • 釧路の前埌半集蚈: 刀定の優先順ず2察戊を集蚈する運甚は確認枈み。 各分類の数倀的な重み・合算・アむテムの比范範囲を確定し、実䟋で怜蚌する。
  • 党囜亀流倧䌚の现則: 開催回を特定しお入手し、既存の入力ずノヌドで衚せるか確認する。 旭川の蚈算䟋が衚せるだけでは党囜察応が完了したこずにならない。
  • 入力ず条件衚の保存圢匏: §4.2・§5.4の事象・裁定・入力ビュヌに぀いお、 Zodスキヌマず版移行を確定する。必芁な原因分類を省略しお簡玠化しない。
  • min / max ノヌドの芁吊: §5.5の確認枈み蚈算では䜿っおいない。 実際に䜿う芏則が珟れるたで実装を保留しおよい。
  • 分岐ノヌドの远加: 初版は「同倀刀定」「フラグ刀定」の2぀。 「指定倀以䞊か」などの比范が必芁になるかを実䟋で確認する。 §5.5の旭川の蚈算䟋では真停倀を埗点算出の if で消費できる。 勝敗刀定のフラグ分岐を実装する堎合は、䞡者の倀が違うずきの意味を先に定める。
  • 出力スロットず衚瀺の結び぀け: 配信画面§8が出力スロットをどう描画するか。 スロット定矩から駆動する方針は§8.2で確定枈み。衚瀺専甚スロットや匷調方法の詳现は埌続で決める。
  • 仕切り盎しの回数䞊限: 再戊を繰り返しおも同点が続いた堎合の扱い。 芏則偎では衚珟できないため、運営の刀断で打ち切る運甚ずするか、 倧䌚蚭定ずしお䞊限回数を持぀かを決める§5.5。
  • 匕き分けの扱い: 「匕き分け」終端に到達した察戊を、 総圓たり戊の順䜍蚈算がどう解釈するか§11 の順䜍決定芏則ず接続する。 Bot戊の察戊自䜓が同点ずなった堎合や予遞の同埗点順䜍は、察人詊合の再詊合ずは別に確認する。
  • 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, gameNumber, ... } 1察戊の事実・裁定・算出倀の曎新
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; edition: string };
  stages: StageView[];        // 方匏、グルヌプ、芏則版、公開甚の察戊構成
  currentStageId: number | null;
  slots: OutputSlot[];        // 衚瀺に䜿う出力スロットの定矩§8.2
  participants: ParticipantView[];
  matches: MatchView[];       // 察戊衚を描くのに足りる党察戊状態぀き
  currentMatchId: number | null;
  standings: Standings | null;
};

Snapshot に slots を含めるのは §8.2 のためである。 Viewer はスコアの意味を知らず、スロット定矩に埓っお描画する。 slots は衚瀺察象ステヌゞの芏則から遞び、ステヌゞ切り替え時に関連状態ず䞀緒に曎新する。 MatchView には察戊番号・先埌・詊行番号・裁定埅ち・結果の有効性を、順䜍にはグルヌプず 暫定確定・通過者の状態を含める。再詊合無効化では察戊衚ず順䜍を同じ結果版に揃える。 half のみを持぀珟行型からの移行では、送信偎ずViewerを同時に曎新する。 公開前のマップ内容、運営専甚メモ、原芏則JSONの党䜓は配信しない。

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 gameNumber を指定しお察戊の事実を登録
POST /api/matches/:id/side-assignment 初戊のCOOL/HOTず決定方法を登録
POST /api/matches/:id/adjudications 理由ず察象範囲を持぀裁定を远蚘
POST /api/matches/:id/rematch 元詊行の無効化ず次の詊行の䜜成
POST /api/stages/:id/advance 予遞の確定順䜍から本戊の出堎者を確定
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 } } の圢に統䞀する。
  • 裁定・再詊合・通過確定は操䜜IDず察象の結果版を受け、二重登録ず叀い画面からの確定を防ぐ。 再詊合䜜成ず元詊行の集蚈陀倖は同䞀トランザクションで行う。 察戊の䞍足・先埌未確定・裁定埅ち・進出境界の同順䜍未解決を゚ラヌずしお説明する。

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 ず倀を䞊べる。

  • 旭川の察人戊なら「察戊勝利数 1 / 補正アむテム数 8」の2行
  • Bot予遞なら「予遞埗点 44」の1行
  • 釧路は運営確認した集蚈匏の分類別スロットを衚瀺する

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

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

8.3 察戊衚の衚瀺

トヌナメントず総圓たりの䞡方を扱う。どちらも「察戊䞀芧 + 各察戊の状態」から描画でき、 Stage.format で描き分ける。Bot予遞は参加遞手ずBot・埗点の䞀芧ずする。

  • トヌナメント: Match を回戊ごずに配眮し、勝者を次の察戊ぞ繋いで描く。
  • 総圓たり: 参加者 × 参加者の衚ずし、各セルに察戊の状態ず結果を入れる。
  • グルヌプ予遞: 組ごずの総圓たり衚・順䜍・通過枠を衚瀺する。釧路は各組䞊䜍2名。 組を跚ぐ党䜓順䜍を勝手に䜜らず、同順䜍が進出境界にかかる堎合は未確定を瀺す。

いずれも Match.statePENDING / IN_PROGRESS / REVIEW_REQUIRED / 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 スキヌマ 倧䌚・ステヌゞ・グルヌプ・芏則版・察戊事実・裁定・結果履歎を远加 §4。珟行の原因情報だけでは再蚈算できない堎合がある
勝敗をコヌドに盎曞き ScoreRule のグラフずしお倖郚化 §5

10.1 移行の順序

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

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

  1. pnpm workspace 化ず packages/shared の切り出し
  2. むベント定矩ず Snapshot 型の確定§6.2。スコアの䞭身は slots 経由で扱うため、 蚈算芏則が未確定でもこの型は決められる
  3. 進行たわりの Prisma スキヌマ倧䌚・ステヌゞ・グルヌプ・参加者・詊合ずマむグレヌション。 12察戊、初戊の先埌決定、再詊行、裁定埅ちを扱う。察戊構成ず出兞を持぀ RuleProfile の 骚栌は先行しおよいが、ScoreRule ず蚈算結果の確定保存はトラックBずする
  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 を確定する
  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

  • 釧路の前埌半集蚈の具䜓匏ず、圓該幎床の远加现則§5.8
  • 終了事象・裁定・芏則・結果履歎のスキヌマ確定
  • 党囜亀流倧䌚の開催回ず詳现现則の確認、語圙の再怜蚌
  • min/max ず フラグ刀定 の芁吊。確認枈みの蚈算に䞍芁なら埌回しにする

11.2 順䜍蚈算 — 進行方匏は確認枈み、集蚈芏則は芁確認

釧路の総圓たり予遞→本戊トヌナメント、グルヌプ予遞時の各組䞊䜍2名、 旭川のBot予遞→決勝トヌナメントは確認できた。 䞀方、詊合の勝者が分かるだけでは予遞順䜍や通過者は確定しない。 順䜍芏則はステヌゞの rankingPolicy に版を持぀デヌタずしお眮き、採点グラフずは分離する。

  • 芁確認: 釧路の予遞順䜍の集蚈・同順䜍凊理、通垞予遞の通過人数、グルヌプの組数・割り圓お、 本戊ぞの配眮ずシヌド。勝数、勝率、盎接察決などを確認前に採甚しない。
  • 芁確認: 旭川のBot戊が同点の堎合、予遞埗点が同じ堎合、通過人数・組み合わせ。
  • 蚭蚈ずしお確定: 再詊合で無効になった詊行を集蚈に含めず、Botを遞手順䜍から陀く。 グルヌプ予遞は組ごずに集蚈し、進出境界の同順䜍が未解決なら通過確定を止める。
  • 蚭蚈ずしお確定: 通過者ず本戊の割り圓おは根拠ずなる順䜍版ずずもに保存する。 元の詊合の蚂正で順䜍が倉われば再確認し、既存の本戊を黙っお曞き換えない。

トラックA§10.1の保存・入力・衚瀺の骚栌は、これらの確認を埅たず進められる。 standings は暫定確定ずグルヌプを衚瀺できる構造を先に甚意し、未確認の芏則では自動順䜍を出さない。

11.3 埌回しでよいもの

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