機能 #313
完了ノーツレーダー6軸の細分化データモデル設計(弱点分析 #293 の前提整備)
説明
目的¶
#293(弱点分析・地力推定パネル)の前提として、既存のノーツレーダー6軸(NOTES / PEAK / SCRATCH / SOFLAN / CHARGE / CHORD)を、より細かい音ゲー特性(単皿・連皿、LN拘束、最大1秒密度…等)へ細分化した内部データモデルの設計を先行検討する。本チケットの成果物は設計(採用方針の決定)であり、実装・パネル本体は別チケット(#293 ほか)で行う。
#293 ユーザーメモの「6軸を拡張したもっと細かい音ゲーデータを保持したほうがいいかもしれない。先にこちらを検討」を受けたもの。
スコープ¶
- 対象: 細分化サブ軸の定義、データ取得方式、保持(スキーマ)方式、集計・正規化方式、既存6軸との関係、段階導入計画の決定。
- 対象外: #293 の弱点分析パネルそのものの実装(未着手のまま保留)。弱点判定で比較する「全体平均」の定義は本設計確定後に #293 側で別途決定する(ユーザー指示)。
現状のデータパイプライン(調査結果)¶
6軸レーダー値は次の経路で算出・保持されている。
-
beatoraja 本体が算出:
bms.player.beatoraja.song.NotesRadarCalculator#calculate(BMSModel)がNotesRadarFeature(約30フィールドの record)を生成。譜面ロード時SongDataModelMapper#apply()で呼ばれる。 -
SongDataには最終6値のみ保持:featureNotesDensity/featurePeakDensity/featureScratchDensity/featureSoflan/featureCharge/featureChord(各 0〜200 にスケール済み)。NotesRadarFeatureが持つ生メトリクス約24個は SongData に載らず破棄される。 -
plugin が伝送:
bms.player.beatoraja.ir.IzuIRConnection(IRChartData)が上記6値をfeature_*キーでChartDataDto.values(Map<String,String>)に詰めて送信。 -
server が保持:
ScoreService#upsertSong()がchart.values()からfeature_*を読み、songsテーブルのradar_notes/radar_peak/radar_scratch/radar_soflan/radar_charge/radar_chord(DOUBLE PRECISION, マイグレーションV9__add_song_radar.sql)へ格納。エンティティはSongEntity。 -
集計:
PlayerNotesRadarAxis(enum, 軸→列マッピング)とPlayerNotesRadarQueryRepository(aggregate/topEntries)が、mode='7' に限定し、各譜面の達成率min(exscore/(notes*2), 1.0)で重み付けし、軸ごと上位10譜面を平均して 0〜200 値を返す。PlayerNotesRadarService#radar()/detail()。
重要な含意: 細分化に必要な生データの多くは beatoraja 内部で既に計算されているが伝送・保持されていない。よって細分化は本質的に「解析の露出 → 伝送 → スキーマ → 集計」の拡張であり、新規の重い譜面解析はサブ軸の一部に限られる。
細分化サブ軸の提案とデータ源マッピング¶
ユーザー例の各サブ軸を、取得可能性で A: 既存計算の露出のみ / B: 新規解析が必要 に分類する。B は izu-ir 側で BMSModel(TimeLine / Note / NormalNote / LongNote / Mode#isScratchKey)から追加算出する想定。
SCRATCH(皿)¶
| サブ軸 | 定義(案) | 区分 | データ源 / 備考 |
|---|---|---|---|
| 単皿 | 孤立した皿の量 | B | 皿総数 scratchTotal から連皿分を除く。新規判定要 |
| 連皿 | 短間隔で連続する皿の密度 | A寄り |
scratchPeakWindowNotes(3秒窓内皿数)を流用可。窓定義の精緻化は B |
| 皿複合 | 皿と鍵盤が同時/近接する密度 | B | 皿タイムライン近傍の鍵盤数。新規解析 |
| 終盤皿 | 譜面終盤(末尾x%)の皿密度 | B | 「終盤」の定義(末尾何%か)要決定 |
| 皿直後鍵盤 | 皿の直後一定時間内の鍵盤量 | B | 皿後ms窓の鍵盤カウント。新規解析 |
CHARGE(LN/CN)¶
| サブ軸 | 定義(案) | 区分 | データ源 / 備考 |
|---|---|---|---|
| LN数 | LN/CN 総本数 | A | longNoteTotal |
| LN拘束 | LN の累積拘束時間 | A | longNoteSecondsTotal |
| LN中鍵盤 | LN保持中に押す通常鍵盤量 | A/B |
chargeNotesDuringLongNote(鍵盤/皿合算)。鍵盤のみへ分離は B |
| LN中皿 | LN保持中の皿量 | B | 上記から皿のみ分離。新規解析 |
| LN終端密度 | LN終端付近のノーツ密度 | B | LN終端ms窓の密度。新規解析 |
PEAK(密度ピーク)¶
| サブ軸 | 定義(案) | 区分 | データ源 / 備考 |
|---|---|---|---|
| 最大1秒密度 | 1秒窓の最大ノーツ数 | B | 既存は3秒窓 peakWindowNotes。1秒窓は新規 |
| 最大5秒密度 | 5秒窓の最大ノーツ数 | B | 新規窓 |
| 最大10秒密度 | 10秒窓の最大ノーツ数 | B | 新規窓 |
| 終盤ピーク | 譜面終盤のピーク密度 | B | 「終盤」定義に依存 |
| 局所難率 | 局所的な難所の集中度 | B | 指標定義要決定(密度分散/上位窓比など) |
SOFLAN / CHORD も同様に細分化余地あり(
soflan*系メトリクス、chordTimelineCount/maxChordSize/chordPeakWindowNotes等)。本チケットで全軸の最終サブ軸セットを確定する。
データ取得の実現方式(要決定)¶
-
案A: beatoraja の生メトリクスを
SongData経由で露出-
NotesRadarFeatureの生フィールドをSongDataに載せ、plugin でfeature_*に追加する。 - 長所: 既存計算の再利用。短所: beatoraja 本体の改変に依存し、izu-ir が特定 beatoraja ビルドに強く結合する(脆い)。サブ軸の一部(B分類)はそもそも未計算。
-
-
案B: izu-ir plugin 側に独自アナライザを実装(推奨)
- plugin 内に
NotesAnalyzerを新設し、beatoraja のBMSModel系モデルクラスから全サブ軸を izu-ir 独自に算出。 - 長所: 軸定義を izu-ir が完全制御でき、beatoraja 内部実装に非依存。A/B 双方のサブ軸を一元的に扱える。短所: 解析ロジックの新規実装コスト。
-
要確認(重要): スコア送信時に plugin が
BMSModel(または BMS ファイル)へアクセスできるか。アクセス不可なら譜面ロード時にキャッシュして送信時に参照する設計が必要。
- plugin 内に
保持(スキーマ)方式(要決定)¶
-
案1:
songsにradar_*列を追加し続ける — 既存踏襲だが、〜15+列で列爆発・マイグレーション肥大。 -
案2: 正規化テーブル
song_chart_feature(song_sha256, axis_key, value, updated_at)(EAV) — 軸追加がデータ行追加で済み、集計クエリもaxis_keyで汎用化。推奨。 -
案3:
songs.chart_features JSONB— 柔軟だが集計クエリ・型保証が弱い。
伝送層は ChartDataDto.values(任意キーの Map)が既に汎用なので、いずれの案でも plugin→server 互換性は高い。
集計・正規化方針¶
- 既存同様、各サブ軸を 0〜200 アンカーテーブル方式でスケールし、達成率
min(exscore/(notes*2), 1.0)重み付け+軸ごと上位N平均を踏襲。 - 既存6軸は親カテゴリとして残し、サブ軸はそのドリルダウンとして表現(後方互換・UI 段階導入)。
#293 への接続¶
- サブ軸の average を「親軸グループ平均」または「本人全サブ軸平均」と比較して弱点サブ軸を抽出する余地が生まれる。
- 「全体平均」の具体定義(本人内平均 / 全プレイヤー横断平均)は、本設計でサブ軸セットが確定した後に #293 側で決定する。
段階導入計画(案)¶
- Phase 1: A分類(既に算出済み or 軽微な算出)サブ軸の露出・伝送・保持・集計。
- Phase 2: B分類(皿直後鍵盤・終盤系・1/5/10秒窓・局所難率・LN終端密度・LN中皿)の新規解析実装。
- 各 Phase は別 機能 チケットとして起票予定。
未解決の検討事項(Open Questions)¶
- plugin がスコア送信時に
BMSModel/ BMS ファイルへアクセスできるか(案B の前提)。 - 「終盤」の定義(末尾何 % / 何秒か)。
- 「局所難率」の具体指標定義。
- スキーマ案(1/2/3)の最終選択。
- 既存
songs行のバックフィル方針(新サブ軸は再プレイ/再ロードまで NULL になる扱い)。 - 全軸の最終サブ軸セット(SOFLAN/CHORD 含む)の確定。
テスト方針(将来実装時)¶
- plugin:
NotesAnalyzer単体テスト(既知譜面で各サブ軸値の境界・期待値)。 - server: 集計クエリ/正規化の境界テスト、コントローラ 404。
- web: パネル描画・i18n(#293 側)。
エージェント ちゃん さんが17日前に更新
設計方針 : データ生成はユーザー自身の beatoraja 本体側で実施¶
ユーザーより、レーダー系データはユーザー自身が管理する beatoraja 本体で生成しているため、細分化(サブ軸の解析・算出)も本体側で検討する方針。
この決定による前提の変化:
- 「データ取得の実現方式」は 案A(beatoraja 本体で生メトリクスを算出 →
SongData経由で露出 → plugin がfeature_*キーで送信)が自然な選択肢となる。本体を管理下に置けるため、案Aの短所として挙げた「beatoraja 本体改変依存で脆い」は実質解消。izu-ir 側に独自アナライザ(案B)を新設する必要性は低下。 -
役割分担: 細分化サブ軸の定義・解析実装は beatoraja 本体側。izu-ir 側の責務は 伝送(
ChartDataDto.valuesの任意キー拡張)・保持(スキーマ)・集計・正規化・#293 への接続 に絞られる。 - 「現状のデータパイプライン」の調査結果(
NotesRadarCalculator→NotesRadarFeature→SongDataの最終6値のみ保持)はそのまま有効。本体側でSongDataに追加フィールドを載せ、plugin がそれをfeature_*に詰めれば izu-ir まで流れる。
izu-ir 側に残る検討事項(本チケットで継続):
- 保持スキーマ案(1:
radar_*列追加 / 2: EAVsong_chart_feature/ 3: JSONB)の最終選択 ― サブ軸が増える前提では 案2(EAV)推奨は変わらず。 - 集計・正規化(0〜200 スケール、達成率重み付け)の踏襲。
- 既存
songs行のバックフィル方針(新サブ軸は再プレイ/再ロードまで NULL)。 - 本体側で確定したサブ軸キー名と izu-ir の
axis_key/ 列名の対応。
本体側でのサブ軸定義が固まり次第、izu-ir 側の Phase 1(伝送・保持・集計)を 機能 チケットとして起票する。
エージェント ちゃん さんが17日前に更新
- ステータス を 未着手 から 実装中 に変更
- 進捗率 を 0 から 10 に変更
実装に着手しました。
- ブランチ:
feature/313 - 対応方針: 現状データパイプラインを精査の上、ノーツレーダー6軸の細分化サブ軸の最終セット・各サブ軸の数値定義・データ源(A:既存露出 / B:新規解析)・保持スキーマ(案2 EAV)・集計/正規化・バックフィル方針・段階導入計画を確定する設計ドキュメントを
docs/に作成する(成果物は設計のみ。実装は Phase 1/2 別チケット)。
エージェント ちゃん さんが17日前に更新
設計方針 : 細分化タクソノミーを確定(パターン軸の新設=ハイブリッド)¶
設計ドキュメントを docs/notes-radar-subdivision-design.md として作成・コミットしました(feature/313)。本チケットの成果物は設計のみで、実コードは別チケット(#293 / Phase 1・2)。
コード調査による現状訂正(重要)
- 本チケット本文の調査記述「生メトリクス約24個は SongData に載らず破棄される」は 現状と食い違う古い記述でした。実際には本体
songdata.db・SongDataとも 29特徴量列すべてを保持・round-trip 済み(SQLiteSongDatabaseAccessor/SongDataResultSetHandler、自動ALTER対応)。未対応なのは IR伝送(IRChartDataは最終6値のみ送信)・server保持(radar_ 6列)・集計(6軸)* と、パターン系サブ軸の新規解析のみ。 - スコア送信時(
MusicResult.IRSendStatus)はSongData+BMSModel双方にアクセス可 → 未解決事項「plugin が BMSModel へアクセスできるか」は アクセス可 で確定。 - BSS 専用型は存在しない(
LongNoteは UNDEFINED/LONGNOTE/CHARGENOTE/HELLCHARGENOTE)→ **BSS は「scratch lane の LongNote」**で検出と確定。
方向性の転換(ユーザー確認済み)
現状 NotesRadarCalculator は「各 TimeLine の縦方向集計」のみで、レーン音符時系列を追跡せず 横方向のパターン認識指標が皆無。チケット当初案のサブ軸は既存密度/構造メトリクスの窓違い・内訳分離に偏り、地力傾向(乱打・縦連・トリル・階段・デニム・軸・ガチ押し・皿種別・ソフラン種別・リズム難)を捉えていませんでした。よって 物量/密度系は既存露出で安価に細分化しつつ、本命としてパターン認識軸を新規解析で追加するハイブリッドを採用。#293 の弱点分析に直結します。
確定した採用方針
| 論点 | 決定 |
|---|---|
| データ取得 | 案A: 本体 NotesRadarCalculator で全サブ軸算出 |
| 保持スキーマ | 案2 EAV song_chart_feature(song_sha256, axis_key, value, updated_at)
|
| 伝送 |
IRChartData.values に feature_<axis_key> 追加(最終6値→全 feature_*) |
| 集計・正規化 | 既存踏襲(0–200アンカー・達成率 min(exscore/(notes*2),1.0) 重み・上位N平均) |
| 既存6軸 | 親カテゴリとして温存・ドリルダウン+新規パターン親軸(後方互換) |
| バックフィル | #145 同方式(楽曲全更新で再計算→songdata.db を IR 取り込み) |
| 数値しきい値 | 粒度先行・実データ分布を見て後決め |
確定サブ軸タクソノミー(粒度)
- NOTES: density / density_peak_1s / density_peak_10s / local_hardness / endgame
- PEAK: density / burst_1s / sustained_3s
- CHORD: density / max_size / gachioshi / denim / jika(軸)
- PATTERN(新規親): randa(乱打) / jack(縦連) / trill / kaidan(階段)
- SCRATCH: density / single(単皿) / ren(連皿) / complex(皿複合) / bss
- SOF-LAN: soflan / accel / decel / stop / visual
- RHYTHM(新規親): 24th / henpyoshi(変拍子) / irregular(不規則間隔)
A=既存露出 / B=新規解析の分類、各サブ軸の定義・データ源・正規化、新規パターン解析プリミティブ(per-lane 音符時系列・間隔ヒストグラム)のアルゴリズムスケッチと暫定閾値、段階導入計画(Phase 1=A分類の露出/伝送/保持/集計、Phase 2=B分類の新規解析)、本体側の変更接点、テスト方針は設計ドキュメントに収録。
残課題(実データ依存で後決め): 全パターン軸の 0–200 アンカー、ジャック等の閾値を固定msかBPM相対か、乱打エントロピー閾値、皿複合窓、デニム/ガチ押しsize閾値、リズムoff-grid許容、集計上位N、soflan細分の二重計上回避係数、パターン軸の本体正規化 vs server正規化。
Phase 1/2 は別 機能 チケットとして起票予定。
エージェント ちゃん さんが17日前に更新
- 次のチケットに先行 機能 #314: ノーツレーダー細分化 Phase 1: A分類サブ軸の露出・IR伝送・EAV保持・集計 を追加
エージェント ちゃん さんが17日前に更新
- ステータス を 実装中 から 完了 に変更
- 進捗率 を 10 から 100 に変更
対応が完了しました。
概要¶
#293(弱点分析)の前提として、ノーツレーダー6軸を細分化する内部データモデルの**設計(採用方針の決定)**を確定し、設計ドキュメントとして追加した。成果物は設計のみで、実コードは含まない(実装は #314 Phase 1 / #315 Phase 2)。
変更点¶
- 設計ドキュメント
docs/notes-radar-subdivision-design.md(新規)を追加。-
方向性の転換: 現状
NotesRadarCalculatorは縦集計のみでパターン認識が皆無 → 物量/密度系は既存露出で安価に細分化しつつ、本命はパターン認識軸(乱打/縦連/トリル/階段/デニム/軸/ガチ押し/皿種別/ソフラン種別/リズム難)の新規解析追加というハイブリッド方針。 -
採用方針: データ生成=本体(案A)/保持=EAV
song_chart_feature(案2)/伝送=IRChartDataの feature_* 全送信化/集計=既存0–200アンカー踏襲/バックフィル=#145同方式。 - 確定タクソノミー: NOTES/PEAK/CHORD(+ガチ押し)/PATTERN/SCRATCH/SOF-LAN/RHYTHM のサブ軸を A(既存露出)/B(新規解析)で分類。新規パターン解析プリミティブ(per-lane 音符時系列・間隔ヒストグラム)のアルゴリズムスケッチ・暫定閾値・段階導入・本体変更接点・テスト方針・実データ後決め項目を収録。
-
方向性の転換: 現状
- 現状訂正: チケット記載「生メトリクスは破棄」は古い記述で、実際は本体SQLite・SongDataとも20特徴量を保持・round-trip済み(未対応はIR伝送・server保持・集計・パターン解析のみ)。
- 変更ファイル:
docs/notes-radar-subdivision-design.md1件(新規)。
テスト¶
- コード変更がないため Gradle ビルド/テストは対象外(設計ドキュメントのみ)。
claude-reviewは PASS(指摘なし)。技術的主張は実コードで検証済み。
後続チケット¶
関連 MR¶
- !67 (feature/313 → develop)
エージェント ちゃん さんが17日前に更新
訂正: 上の完了コメント中「本体SQLite・SongDataとも20特徴量を保持」は誤記で、正しくは 29特徴量(最終6値+生メトリクス23)です。設計ドキュメント docs/notes-radar-subdivision-design.md 本体は正しく29と記載されています。