Skip to content

Log Parser Architecture ​

ゲームログを解析して試合情報(BAN/PICK / 勝敗 / 統計)を抽出するエンジンの構成。code-split-and-coverage spec で 3 層 + finalize + statistics に分割した。

ファイル構成 ​

src/utils/logAnalyzer/
├── LogParser.ts              # 一括解析モード(バッチ用 facade)
├── StreamLogParser.ts        # 旧ストリーム実装(互換のため残置)
├── CharacterService.ts       # キャラ ID ↔ 名前変換
├── characterRegistry.ts      # いま使うキャラデータ(同梱 or 配信されたリソースパック)
├── toDbMatch.ts              # MatchData → DB 保存形式(全経路で共通の変換)
├── matchId.ts                # ログ由来の試合 ID / 日付 / 時刻
├── placeholderUid.ts         # UID が分からないプレイヤーの仮 UID
├── pickOrder.ts              # pick が何番目に確定したか / BP の席
├── parserVersion.ts          # 解析器の版(matches.log_parser_version)
├── banTarget.ts              # BAN がどのプレイヤーの所持キャラに向いたか
├── team.ts                   # チームの DB 表現
├── StatisticsCalculator.ts   # 統計集計の facade
├── statistics/
│   └── aggregate.ts          # 純関数の試合統計集計(spec で抽出)
├── core/
│   ├── LogParserCore.ts      # 一括解析オーケストレータ
│   ├── StreamLogParserV2.ts  # ストリーム解析(リアルタイム用、StreamFsm 内蔵)
│   ├── line/
│   │   └── LineParser.ts     # 純粋な行パーサ(regex / 抽出関数 / MARKER)
│   ├── fsm/
│   │   └── StreamFsm.ts      # phase の状態機械
│   ├── aggregator/
│   │   └── Aggregator.ts     # picks / bans / players の集約
│   └── finalize/
│       └── finalizeMatchData.ts  # 集計結果の最終整形
├── battleType/               # BattleType の数値 ↔ 文字列マッピング
├── matchFilters.ts
├── timeUtils.ts
└── types.ts

scripts/analyze-log/ は CLI 解析スクリプト群(同じエンジンを使う薄いラッパ)。

レイヤー構成 ​

層ファイル責務依存
linecore/line/LineParser.ts1 行を ParsedLine に変換する純粋関数群(regex / 抽出関数 / MARKER)なし
fsmcore/fsm/StreamFsm.tsphase の状態保持・遷移・onChange リスナーline(型のみ)
aggregatorcore/aggregator/Aggregator.ts重複排除 / ソート / Normal 戦キャラ抽出line(型のみ)/ CharacterServiceLike (DI)
finalizecore/finalize/finalizeMatchData.tsrank / type 推定 / 整形aggregator
statisticsstatistics/aggregate.ts試合配列から KPI を集計(純関数)finalize 後の MatchData 配列

状態機械(StreamFsm) ​

リアルタイム解析時の phase 遷移。StreamLogParserV2.ts が内蔵する。

各状態の役割:

状態入力で受け付ける主なマーカー出力 / 次状態
IDLE試合開始系マーカーBP へ
BPBP数据日志 (Rank), プレイヤー BAN/PICKaggregator に push
LOADING匹配成功开始加载战斗INGAME へ(タイムアウトすれば IDLE)
INGAMEON GAME START, ON BATTLE END PUSHRESULT へ
RESULTupload sdk battle playerMatchData を確定し IDLE へ

実遷移表は core/fsm/StreamFsm.ts の transition() メソッドに 明示的に列挙 されている。

ログマーカー ​

BP フェーズ関連 ​

マーカー説明出現条件
BP数据日志BAN/PICK 詳細(プレイヤー名 / キャラ ID 等)Rank のみ
匹配成功开始加载战斗マッチング成功 → BP フェーズ終了両方
PageBattleBanPick2V2.prefabBP 画面 UI ログRank のみ

試合進行関連 ​

マーカー説明
ON GAME STARTゲーム開始
ON BATTLE END PUSHバトル終了
upload sdk battle player試合結果(プレイヤー名・勝敗)

マッチタイプマーカー ​

マーカー説明
TYPE:Rank_2v2ランク 2v2
TYPE:Rank_1v1ランク 1v1
TYPE:Normal_2v2ノーマル 2v2
TYPE:Normal_1v1ノーマル 1v1
TYPE:Customカスタム

Rank 判定ロジック(finalize/finalizeMatchData.ts) ​

集計後 (Aggregator 出力時点) は rank/normal 判定が確定していないことがある。finalizeMatchData で以下の 優先順位 で確定する。

優先度判定基準信頼度
1TYPE: マーカー(試合結果の前 500 行以内)確定
2BP区間内に BP数据日志 があるRank と推定
3プレイヤー数(2 = 1v1, 4 = 2v2)+ 既定 Normalフォールバック

一括解析 vs ストリーム解析 ​

種別エントリ用途
一括LogParser → LogParserCore既存ログファイルの解析(DB インポート時)
ストリームStreamLogParserV2リアルタイムログ監視 (LogWatcherService)

両方とも内部では同じ line / aggregator / finalize を使う。違いは fsm の有無のみ:

DB 保存形式への変換(toDbMatch) ​

解析結果(MatchData)を DB の 1 試合分(matches / players / bans / picks)に変えるのは toDbMatch.ts の toDbMatch() だけ。 リアルタイム記録(LogWatcherService)・起動時取り込み(electron/utils/parseLogFiles.ts)・ログ解析タブ(useMatchConverter)の 3 経路が同じ関数を通るので、同じログは同じ行になる。

  • 試合 ID: matchId.ts の ${date} ${time}_${type}。既存 DB との互換のため形式は変えない。ID が違っても同じ試合 (どちらもログ由来・同じ日付で時刻差 ±3 秒以内・参加者名の集合が同じ)は保存時に 1 行へまとめる(electron/database/match/equivalentLogMatch.ts)
  • 仮 UID(placeholderUid.ts): ログで UID が取れないプレイヤーは 2 形式だけ。自分の名前(プロファイルの名前)と一致すれば player:<名前>、それ以外は Unknown。Recorder の本人判定・プロファイル照合は player: を見る。旧形式 unknown_<n> は起動時の移行 (placeholderUidMigration.ts)で書き換えるが、判定では今も仮 UID として扱う
  • pick 順・席(pickOrder.ts): ログの「被选序号」は使えないので、確定した pick が初めて出た BP ダンプの時刻順に番号を付ける。 同じダンプで出た人は同順位(例: 1, 2, 3, 3)。時刻の分からない pick には付けない。BP の席(P1〜P4)と TeamSort も保存する (players の bp_position / team_sort / pick_order、picks の pick_order)
  • 解析器の版(parserVersion.ts): 行ごとに matches.log_parser_version を残す。保存済みの行も直したい解析器の修正をしたら LOG_PARSER_VERSION を 1 つ上げる。書き手(Recorder / own-writer Manager)の次の起動で、版の古いログ試合をログファイルから 読み直し、ログ由来の列だけを更新する(electron/database/match/logReanalysis.ts、呼び出しは electron/main/startupLogReanalysis.ts)。 API の戦績詳細が合流した列は触らない。ログファイルが残っていない試合はそのまま

Statistics 集計(純関数) ​

statistics/aggregate.ts は MatchData[] を入力に取る純関数群。StatisticsCalculator の facade から呼ばれる。

主な集計関数:

  • aggregateMatchStatistics(matches, ...) — プレイヤー / キャラ pick・ban / 対戦相手分析をまとめて返す
  • emptyStatistics() — 空集計の初期値

純関数なので vitest で簡単にテストできる(集計の挙動は core/__tests__/ のシナリオ系テストでカバー)。

CharacterService ​

キャラの ID / DB 名 / 画像名 の対応を管理する。nameAliases で DB 名と画像ファイル名のずれを吸収する。

キャラデータの持ち主は characterRegistry.ts(プロセスに 1 つ)。既定は同梱の src/data/characters.json(キーはキャラ ID = ログの ROLE <n>)で、 配信されたリソースパックが届いたら丸ごと差し替える。引数なしの new CharacterService() はコピーを持たずに毎回 registry を読むので、 差し替え後も作り直さずに新しいキャラが引ける(引数で DB を渡したときはそれを固定で使う。テスト・比較用)。

新規キャラ追加時の手順は Add New Character を参照。

テスト ​

テストは各層のソースと同一ディレクトリに colocate している。

層テストファイル主なケース
linecore/line/LineParser.test.tsregex の table-driven
fsmcore/fsm/StreamFsm.test.ts全遷移を網羅
aggregatorcore/aggregator/Aggregator.test.tsfixture ベース
finalizecore/finalize/finalizeMatchData.test.tsrank/type 推定
一括core/LogParserCore.test.ts / __tests__/LogParserCore.test.ts大規模 fixture
ストリームcore/StreamLogParserV2.test.ts + .regression.test.ts + .casual.test.ts統合シナリオ

CLI ツール ​

bash
yarn analyze-log                  # デフォルト UID で解析
npx tsx scripts/analyze-log/index.ts "path/to/log.txt"

# 特定マーカー確認
grep -n "BP数据日志" scripts/testData/.../Log_*.txt | head -10
grep -n "TYPE:" scripts/testData/.../Log_*.txt | head -30

解析結果の出力内容 ​

試合基本情報 ​

  • 試合 ID / タイムスタンプ
  • マッチタイプ(Rank_2v2 / Normal_1v1 等)
  • ゲーム開始・終了時刻 / duration

プレイヤー情報 ​

  • 名前 / UID
  • 勝敗 / チーム(A or B)
  • 使用キャラ / 所持キャラ枠

BAN/PICK 情報(Rank のみ) ​

  • BAN されたキャラ + BAN したプレイヤー
  • PICK されたキャラ + PICK したプレイヤー

統計情報(statistics/aggregate) ​

  • プレイヤー別 勝敗数・勝率
  • キャラ別 BAN/PICK 回数 / 勝率
  • 対戦相手分析

関連ドキュメント ​


Last verified: 2026-10-24 / commit e111fbeb