機能 #145
完了ノーツレーダー機能
説明
ノーツレーダー¶
某ゲームのノーツレーダーをそれっぽく再現したい
| 名称 | 概要 | 計算方法の検討メモ |
|---|---|---|
| NOTES | 楽曲の長さに対する総ノーツ数の多さ | 譜面全体の平均密度 |
| PEAK | ノーツ数の瞬間密度 | 数秒間の最大瞬間密度 |
| SCRATCH | 楽曲の長さに対するスクラッチの多さ | 譜面全体の平均スクラッチ密度 |
| CHARGE | チャージノートやバックスピンスクラッチの難しさ | チャージ単体、チャージ中の通常ノーツも考慮 |
| CHORD | 同時押しの難しさ | そのまま同時押しの出現頻度? |
| SOF-LAN | BPM変化に対するノーツの難しさ | BPM変化点前後のノーツ数(変化直後から2秒間&時間距離で係数減衰)、BPM変化量 |

メモ¶
- 各要素の最大値は 200.00 とする
- BMSの場合、やろうと思えばいくらでも難しくできるので、どこかで基準を設ける必要がある
- Overjoy Lv8とかを基準にしてしまうと、一般的なレベル帯があまりにも低くなりすぎる気がする
- 実際にすべての値を計算した結果をベースにして係数を設定してレーダー値を決めるといいかも?
プレーヤーノーツレーダー¶
各要素のTOP10を集計、平均値でプレーヤー側のノーツレーダーを算出する。
単曲のレーダー値=対象曲のノーツレーダー値×スコアレートで計算する
レーダー計算する曲¶
- 7KEYS譜面、かつ、難易度表に登録されている譜面のみ
- あらかじめ曲のレーダー値は計算済みにしておく
- ゲーム内では参照するのみ
- プレーヤーノーツレーダーは対象がゲーム内でスコア更新されたら値も更新する
ファイル
いづ さん さんが27日前に更新
操作
#3
- ファイル clipboard-202606092147-bkfn5.png clipboard-202606092147-bkfn5.png を追加
- 説明 を更新 (差分)
エージェント ちゃん さんが27日前に更新
操作
#8
- ステータス を 未着手 から 実装中 に変更
- 進捗率 を 0 から 10 に変更
作業開始します。
まず既存の譜面メタデータ、難易度表、スコア更新処理の構造を確認し、ノーツレーダー計算値をどこで保持・参照するのが自然かを調査します。
エージェント ちゃん さんが27日前に更新
操作
#9
進捗です。
実装した内容:
- 譜面ノーツレーダー値の値オブジェクトを追加
- NOTES / PEAK / SCRATCH / CHARGE / CHORD / SOF-LAN
- 各値は 0.00〜200.00 にクランプ
- BMSModel からレーダー値を計算する処理を追加
- 平均密度、2秒窓のピーク密度、スクラッチ密度、LN/CN/BSS系、同時押し、速度変化周辺ノーツをベースに算出
- SongData にレーダー値を保持
- songdata.db の song テーブルへ以下の列を追加し、既存DBには自動ALTERで追加
radar_notesradar_peakradar_scratchradar_chargeradar_chordradar_soflan
- 楽曲スキャン時にレーダー値を計算してDBへ保存
- プレーヤーノーツレーダー用の純粋ロジックを追加
- 単曲値 = 曲レーダー値 × スコアレート
- 各要素TOP10平均でプレーヤー側レーダーを算出
- SQLite永続化とプレーヤー集計のテストを追加
確認:
-
./gradlew.bat test成功
次に残る接続候補:
- 表示/スキンプロパティへのレーダー値公開
- プレーヤーレーダーの保存先と、スコア更新時の再集計タイミング接続
- 7KEYSかつ難易度表登録譜面のみを対象にするフィルタの適用位置決定
エージェント ちゃん さんが27日前に更新
操作
#10
方針修正しました。
先の実装では仮係数で 0.00〜200.00 に変換するところまで入れていましたが、仕様が未確定な状態では早すぎるため撤回しました。
現在の実装内容:
-
0.00〜200.00の最終レーダー値はまだ計算しません - まずは生特徴量を計算して
songdata.dbに保存します - 保存列は以下です
feature_notes_densityfeature_peak_densityfeature_scratch_densityfeature_chargefeature_chordfeature_soflan
-
NotesRadarFeatureは非負の生値だけを保持します - プレーヤーノーツレーダー集計ロジックは、スケール係数確定後に実装するため一旦外しました
確認:
-
./gradlew.bat test --tests bms.player.beatoraja.song.SQLiteSongDatabaseAccessorTest成功 -
./gradlew.bat test成功
次は、この生特徴量を難易度表登録済み7KEYS譜面で集計して、分布を見てから正規化係数を決めるのがよいと思います。
エージェント ちゃん さんが27日前に更新
操作
#11
データ整備優先のため、生特徴量を追加拡張しました。
既存の6つの合成寄り特徴量:
feature_notes_densityfeature_peak_densityfeature_scratch_densityfeature_chargefeature_chordfeature_soflan
に加えて、計算式検討用の部品特徴量を保存します。
追加した列:
feature_notes_totalfeature_duration_secondsfeature_peak_window_notesfeature_scratch_totalfeature_ln_totalfeature_ln_seconds_totalfeature_charge_notes_during_lnfeature_chord_timeline_countfeature_max_chord_sizefeature_speed_change_countfeature_stop_countfeature_scroll_change_countfeature_soflan_weighted_notes
意図:
- いきなり最終レーダー値を決めず、まず合成前の材料を集める
- 後で難易度表登録済み7KEYS譜面に絞って分布を見ながら、各軸の計算式と正規化係数を決められるようにする
-
CHARGE/CHORD/SOF-LANの高値要因を分解して確認できるようにする
確認:
-
./gradlew.bat test --tests bms.player.beatoraja.song.SQLiteSongDatabaseAccessorTest成功 -
./gradlew.bat test成功
注意:
- 追加列はDBアクセサ生成時に自動追加されます
- 既存行に値を入れるには、再度楽曲全更新が必要です
エージェント ちゃん さんが27日前に更新
操作
#12
feature_charge_notes_during_ln が全件0になる問題を修正しました。
原因:
- 以前の実装は「同一レーンのLN保持中に通常ノーツがあるか」を見ていました
- 実際に拾うべきなのは「どこかのレーンでLN保持中に、別レーンで通常ノーツまたはLN開始がある」ケースでした
修正内容:
- 各TimeLine処理開始時点で保持中のLNレーン数を算出
- 現在処理中のノーツが、保持中LNと別レーンにある場合に
feature_charge_notes_during_lnを加算 - 対象は通常ノーツだけでなく、LN開始も含める
- 該当ノーツ分を
feature_chargeにも加点 - 回帰テスト
NotesRadarCalculatorTestを追加
確認:
-
./gradlew.bat test --tests bms.player.beatoraja.song.NotesRadarCalculatorTest成功 -
./gradlew.bat test --tests bms.player.beatoraja.song.SQLiteSongDatabaseAccessorTest成功 -
./gradlew.bat test成功
注意:
- 既存DBの
feature_charge_notes_during_lnを更新するには、再度楽曲全更新が必要です
エージェント ちゃん さんが27日前に更新
操作
#13
ソフラン特徴量の調査用データ追加¶
生特徴量として、既存の feature_soflan / feature_speed_change_count / feature_stop_count / feature_scroll_change_count / feature_soflan_weighted_notes に加えて、ソフラン地帯単位で見るための以下を追加しました。
feature_soflan_cluster_countfeature_soflan_affected_notesfeature_soflan_cluster_scorefeature_soflan_max_change_ratefeature_soflan_stop_time_total
暫定仕様:
- BPM/STOP/スクロール変更点の前後をソフラン影響範囲としてクラスタ化
- 近いイベントは同一クラスタとしてマージ
- クラスタ内のノーツ数、最大変化率、停止時間を保持
-
cluster_scoreは affected notes と変化率、停止時間から作る調査用スコア
これで単発のBPM変更数だけでなく、「実際にノーツが絡むソフラン地帯か」を見られるようになります。
検証:
-
./gradlew.bat test --tests bms.player.beatoraja.song.NotesRadarCalculatorTest成功 -
./gradlew.bat test --tests bms.player.beatoraja.song.SQLiteSongDatabaseAccessorTest成功 -
./gradlew.bat test成功
次の全更新後、この5項目を含めて難易度表登録済み譜面に絞った分布を見ます。
エージェント ちゃん さんが27日前に更新
操作
#14
全更新後のソフラン特徴量確認¶
難易度表登録済みの 7keys 譜面に絞って確認しました。
- 対象: 10,884譜面
-
feature_soflan_cluster_score > 0: 1,896譜面 -
feature_soflan_cluster_score- p90: 31.19
- p95: 103.63
- p99: 679.46
- max: 5653.17
-
feature_soflan_affected_notes- p90: 49
- p95: 102
- p99: 336.34
- max: 2176
-
feature_soflan_max_change_rate- p95: 5.865
- p99: 4091.89
- max: 163000163
所感:
-
cluster_scoreは、実際にノーツが絡むソフラン地帯を拾う指標として使えそうです。 - 一方で
max_change_rateは 0 BPM / 巨大BPM / 停止ギミック表現で極端な値になります。生値として残すのは有用ですが、最終レーダー計算へそのまま入れるべきではなさそうです。 - 最終式では
log1pに加えて、変化率側に上限を置く、または percentile clipping する方が妥当です。 -
feature_scroll_change_countは登録済み7keysでは非ゼロが5譜面のみでした。BMS母集団では BPM/STOP 型ソフランが中心で、SCROLL命令由来はかなり少ないです。
上位には Overjoy / ☆X / 巨大BPM表現の譜面が混ざるため、最終スケール決定時は「難易度表登録済み」だけでなく、極端な表外・隔離系レベルを除いた分布も別に使うのがよさそうです。
エージェント ちゃん さんが27日前に更新
操作
#15
ソフランレーダー暫定式を実装¶
feature_soflan を暫定的に 0-200 系のレーダー値として計算するようにしました。
方針:
- 生特徴量はそのまま保持
feature_soflan_cluster_scorefeature_soflan_max_change_ratefeature_soflan_affected_notesfeature_soflan_stop_time_total
- レーダー値化する
feature_soflan側だけ、巨大BPM/0 BPM系の外れ値対策を入れる - クラスタごとに
affectedNotes * log1p(cappedChangeRate) * stopBonusを計算 -
cappedChangeRateは暫定で100.0上限 - 集計後、
200 * log1p(score) / log1p(10000)で 0-200 にスケーリングし、最大 200 でクリップ
意図:
- p95付近のソフラン譜面が概ね100前後に乗るスケール
- p99以上の強いソフラン譜面は150以上に伸びる
- 巨大BPM表現や 0 BPM 系でも 200 を超えない
検証:
-
./gradlew.bat test --tests bms.player.beatoraja.song.NotesRadarCalculatorTest成功 -
./gradlew.bat test成功
注意:
既存DBの feature_soflan は前回更新時の値なので、この暫定式の値を見るには再度楽曲全更新が必要です。
エージェント ちゃん さんが27日前に更新
操作
#16
ソフランレーダー暫定式の全更新後確認¶
難易度表登録済み 7keys に絞って、暫定 0-200 化後の feature_soflan を確認しました。
対象: 10,884譜面
分布:
- 非ゼロ: 1,896譜面
- p90: 75.13
- p95: 100.38
- p99: 135.94
- p99.9: 155.30
- max: 186.13
帯域別:
- 0: 8,988譜面
- 0-25: 167譜面
- 25-50: 217譜面
- 50-75: 421譜面
- 75-100: 541譜面
- 100-125: 350譜面
- 125-150: 172譜面
- 150-175: 27譜面
- 175-200: 1譜面
- 200超過: 0譜面
上位例:
- 909-Sym: 186.13
- Resonyance Fever: 174.88
- エキサイティングなユーザ経験: 172.84
- Calamity Fortune [LAST-DANCE]: 172.47
- Aloof Wolf: 171.20
- 点、線、面、立体 [SP CLOCKWORK]: 169.97
- Lots Of Spices[Another]: 166.25
- Engine: 160.45
- Aleph-0[INSANE]: 158.34
所感:
- p95 がほぼ100になっており、暫定スケールとしては狙い通りです。
- 最大も186で、巨大BPM/0 BPM表現があっても200に張り付いていません。
- 上位はソフラン譜面として納得感がありますが、☆X / Overjoy / 巨大BPM表現も混ざるため、最終調整では母集団フィルタを分けて見る必要があります。
- 現時点ではこの式を暫定採用して、他レーダー項目との相対バランスを見るのがよさそうです。
エージェント ちゃん さんが26日前に更新
操作
#17
ソフランレーダー式を調整¶
指摘内容:
同じBPM変化でも、変化直後に1ノーツだけ降る譜面と、変化直後に多くのノーツが降る譜面では後者のほうが難しい。現行式はクラスタ内ノーツ数は見ていたが、クラスタ内での時間位置を等価に扱っていた。
対応:
- レーダー値用に、BPM/STOP/SCROLL変化点からの距離でノーツ重みを変えるように変更
- 変化直後を最も重くし、変化後2秒へ向けて軽くする
- 変化前1秒のノーツは補助的に低い重みで扱う
- 生特徴量の
feature_soflan_affected_notes/feature_soflan_cluster_scoreは従来通り維持 -
feature_soflanに使う内部スコアだけradarWeightedNotesを使う - 強いソフラン譜面が190付近まで届きやすいよう、暫定スケールを
scoreAtMax=5000に調整
追加テスト:
- 同じBPM変化・同じ影響ノーツ数でも、変化直後にノーツがある譜面の
feature_soflanが、遅れてノーツが来る譜面より高くなることを確認 - 巨大BPM表現でも
feature_soflan <= 200を維持
検証:
-
./gradlew.bat test --tests bms.player.beatoraja.song.NotesRadarCalculatorTest成功 -
./gradlew.bat test成功
注意:
この調整後の feature_soflan を実データで見るには再度楽曲全更新が必要。
エージェント ちゃん さんが26日前に更新
操作
#18
楽曲DB全更新CLI追加と実行結果¶
GUIを起動せずに楽曲DB全更新だけを実行するCLIを追加しました。
追加:
bms.player.beatoraja.tool.SongDatabaseUpdateCli- Gradleタスク
updateAllSongs
実行方法:
./gradlew.bat updateAllSongs
オプション:
./gradlew.bat updateAllSongs --args="--incremental"
./gradlew.bat updateAllSongs --args="--path=<folder>"
調整:
- CLI実行時は
-Dbeatoraja.songdb.update.sequential=trueを付け、楽曲更新を直列化 - 並列更新ではDB書き込みロックに多数スレッドが詰まり、実行状態の把握が難しかったため
- 1000譜面ごとの進捗ログを追加
確認:
- 直列CLIで全更新完了
- 実行時間:
242007 ms、Gradle全体では4m 2s -
songdata.db: integrity_checkok -
songinfo.db: integrity_checkok - song count: 52,979
- folder count: 4,208
- information count: 52,492
更新後の難易度表登録済み 7keys feature_soflan:
- 対象: 10,884譜面
- 非ゼロ: 1,896譜面
- p90: 84.93
- p95: 112.21
- p99: 150.05
- p99.9: 180.76
- max: 200.0
上位例:
- 909-Sym: 200.00
- Aloof Wolf: 200.00
- Resonyance Fever: 199.51
- Calamity Fortune [LAST-DANCE]: 195.15
- エキサイティングなユーザ経験: 194.21
- Lots Of Spices[Another]: 192.80
- 点、線、面、立体 [SP CLOCKWORK]: 190.84
- Aleph-0[INSANE]: 185.22
- Aleph-0[ANOTHER]: 180.44
神姫系は最高 149.29 でした。ここを190級にしたい場合、現行の「BPM変化直後ノーツ」だけでは足りず、低BPM視認難・連続低速地帯の重みを追加する必要がありそうです。
検証:
-
./gradlew.bat test成功
エージェント ちゃん さんが26日前に更新
操作
#19
NOTES / PEAK レーダー暫定式¶
難易度表登録済み 7keys の生特徴量を確認し、feature_notes_density / feature_peak_density を 0-200 系に暫定式化しました。
生分布:
-
feature_notes_density- p90: 24.74
- p95: 27.35
- p99: 32.71
- max: 82.66
-
feature_peak_density- p90: 43.0
- p95: 46.5
- p99: 53.5
- max: 397.0
暫定式:
radar = min(200, 100 * sqrt(raw / rawAt100))
NOTES rawAt100 = 27.35
PEAK rawAt100 = 46.5
狙い:
- p95 付近を100に置く
- p99以上は自然に伸ばす
- 極端なOverjoy/隔離系は200まで到達可能
- 対数式より中位帯の差を残す
既存DBの生特徴量にこの式を当てた疑似分布:
- NOTES
- p90: 95.11
- p95: 99.99
- p99: 109.37
- p99.9: 119.61
- max: 173.84
- PEAK
- p90: 96.16
- p95: 100.00
- p99: 107.26
- p99.9: 119.59
- max: 200.00
上位例:
- NOTES: 愛歌「ネグラドルナ」[MODEL 8+] 173.84、戦歌「オルグラリヤ」158.05
- PEAK: 幽雅に咲かせ、墨染の桜[HYPER] 200.00、愛歌「ネグラドルナ」[MODEL 8+] 160.64、戦歌「オルグラリヤ」154.15
検証:
-
./gradlew.bat test --tests bms.player.beatoraja.song.NotesRadarCalculatorTest --tests bms.player.beatoraja.song.SQLiteSongDatabaseAccessorTest成功 -
./gradlew.bat test成功
注意:
DBへ実値として反映するには、追加したCLIで再度全更新が必要です。
./gradlew.bat updateAllSongs
エージェント ちゃん さんが26日前に更新
操作
#20
レーダー値の上限基準について¶
各レーダーで最大値 200.00 が1曲も出ない状態は表示として弱いため、暫定方針として以下に寄せます。
- 生特徴量の基準点を
100.00用と200.00用で分ける -
200.00基準以上の譜面は一律200.00にクリップする - それ未満は
100.00基準も通る曲線で再スケールする
まず NOTES/PEAK に適用しました。
- NOTES:
notes/sec = 27.35を 100、68.32を 200 - PEAK:
peak notes/sec = 46.5を 100、120.0を 200
既存DBの生特徴量に対する試算では、難易度表登録済み7keys内で NOTES/PEAK ともに 200 到達譜面が出ることを確認しています。DB上の実値反映には再度 ./gradlew.bat updateAllSongs が必要です。
検証:
./gradlew.bat test
結果: BUILD SUCCESSFUL
エージェント ちゃん さんが26日前に更新
操作
#21
NOTES 200基準の見直し¶
前回の notes/sec = 68.32 を 200.00 基準にする案は、統計的な外れ値に寄りすぎるため見直しました。
難易度表登録済み7keys 10,884件の NOTES 生値分布:
p95 27.3462
p99 32.7139
p99.5 34.4873
p99.9 39.1261
max 82.6555
暫定式を以下に変更しました。
NOTES 100 = 27.35 notes/sec // p95
NOTES 200 = 34.49 notes/sec // p99.5
この基準だと、既存DB生特徴量への試算では NOTES 200.00 到達が55譜面になります。Overjoy Lv5 の リジットXXパラダイス は 39.1239 notes/sec のため 200.00 にクリップされます。
検証:
./gradlew.bat test
結果: BUILD SUCCESSFUL
エージェント ちゃん さんが26日前に更新
操作
#22
NOTES式の再調整¶
NOTESは単純な 100基準/200基準 の曲線ではなく、難易度表登録済み7keysの密度分布に沿ったアンカー補間に変更しました。
方針:
-
notes/secの経験分布を固定アンカー化する - p70 を 100.00 付近に置く
- p99.5 を 200.00 に置く
- p99.5以上は 200.00 にクリップする
現在の主なアンカー:
p50 14.7869 -> 71.43
p70 18.8414 -> 100.00
p75 20.0354 -> 116.95
p80 21.3127 -> 133.89
p85 22.8554 -> 150.83
p90 24.7402 -> 167.78
p95 27.3462 -> 184.72
p97 29.2541 -> 191.51
p98 30.4739 -> 194.91
p99 32.7139 -> 198.30
p99.5 34.4873 -> 200.00
この形だと、秒間30前後を継続する上位譜面が高いNOTES値になるため、体感に近い見え方になります。
既存DB生特徴量への試算:
200到達: 55譜面
リジットXXパラダイス: 39.1239 notes/sec -> 200.00
検証:
./gradlew.bat test
結果: BUILD SUCCESSFUL
エージェント ちゃん さんが26日前に更新
操作
#23
PEAK生特徴量: 3秒窓で再集計¶
PEAKの集計窓を 2秒 から 3秒 に変更し、./gradlew.bat updateAllSongs でDBを再計算しました。
- 実行時間: 247,436 ms
- 結果: BUILD SUCCESSFUL
-
songdata.dbintegrity_check: ok
対象はこれまでと同じく、難易度表登録済み7keys 10,884件です。PEAK生値は feature_peak_window_notes / 3.0 で見ています。
p0 0.6667
p10 8.6667
p25 16.3333
p50 26.3333
p70 33.3333
p75 35.0000
p80 37.0000
p85 39.0000
p90 41.3333
p95 44.6667
p97 46.6667
p98 48.3333
p99 51.0567
p99.5 54.3333
p99.9 60.8617
max 265.6667
上位例:
265.67 幽雅に咲かせ、墨染の桜[HYPER]
117.33 愛歌「ネグラドルナ」[MODEL 8+]
108.67 戦歌「オルグラリヤ」
82.67 compressed eyes -XaaaCi-
71.67 Apollo [SUPERVAKA]
71.67 Last Battle-T260G-[ORETIC-2]
70.00 volcanic
参考:
リジットXXパラダイス: 144 notes / 3s = 48.0000 notes/sec
2秒窓より短い瞬間詰め込みは丸まりつつ、3秒持続する局所発狂は十分拾えているため、PEAKは3秒窓を採用してよさそうです。レーダー化はこの分布をもとに別途決めます。
エージェント ちゃん さんが26日前に更新
操作
#24
PEAKレーダー式の実装¶
PEAKもNOTESと同じく、経験分布アンカー補間に変更しました。
- 集計窓: 3秒
- 生値:
feature_peak_window_notes / 3.0 - p70 を 100.00
- p99.5 を 200.00
- p99.5以上は 200.00 にクリップ
主なアンカー:
p50 26.3333 -> 71.43
p70 33.3333 -> 100.00
p75 35.0000 -> 116.95
p80 37.0000 -> 133.90
p85 39.0000 -> 150.85
p90 41.3333 -> 167.80
p95 44.6667 -> 184.75
p97 46.6667 -> 191.53
p98 48.3333 -> 194.92
p99 51.0567 -> 198.31
p99.5 54.3333 -> 200.00
実装後に ./gradlew.bat updateAllSongs を実行してDBへ反映しました。
- 実行時間: 268,063 ms
- 結果: BUILD SUCCESSFUL
-
songdata.dbintegrity_check: ok
難易度表登録済み7keys 10,884件での反映後分布:
p50 71.43
p70 100.00
p75 116.95
p80 133.90
p85 150.85
p90 167.80
p95 184.75
p97 191.53
p98 194.92
p99 198.28
p99.5 200.00
p99.9 200.00
max 200.00
200到達 56譜面
参考:
リジットXXパラダイス: 144 notes / 3s = 48.0000 notes/sec -> PEAK 194.24
検証:
./gradlew.bat test
結果: BUILD SUCCESSFUL
エージェント ちゃん さんが26日前に更新
操作
#25
SCRATCHレーダーの暫定式を更新しました。
変更内容¶
- 生特徴量として
feature_scratch_peak_window_notesを追加- 3秒窓内の最大スクラッチ数
- 局所的な連皿を平均密度とは別に拾うための値
- SCRATCHレーダー値を以下の合成スコアから算出するように変更
averageScore = scratchTotal / durationSeconds * sqrt(min(scratchTotal / notesTotal / 0.25, 1.0))
peakScore = scratchPeakWindowNotes / 3.0 * 0.5
scratchRaw = max(averageScore, peakScore)
scratchRaw は難易度表登録済み譜面の分布ベースのアンカーで 0.00~200.00 に補間しています。p70付近を100、p99.5付近を200に寄せる暫定設定です。
確認結果¶
-
./gradlew.bat test --tests bms.player.beatoraja.song.NotesRadarCalculatorTest --tests bms.player.beatoraja.song.SQLiteSongDatabaseAccessorTest: 成功 -
./gradlew.bat test: 成功 -
./gradlew.bat updateAllSongs: 成功song database update finished: 239268 ms- DB件数: 52979
-
PRAGMA integrity_check: ok
サンプル¶
幽雅に咲かせ、墨染の桜[NORMAL] 系は、局所連皿ピークによりSCRATCHが高く出ることを確認しました。
-
幽雅に咲かせ、墨染の桜[NORMAL]- SCRATCH: 200.00
- scratchTotal: 335~336
- scratchPeakWindowNotes: 35
- peak/sec: 11.6667
-
幽雅に咲かせ、墨染の桜[NORMAL+]- SCRATCH: 200.00
- scratchTotal: 382~383
- scratchPeakWindowNotes: 38~39
- peak/sec: 12.6667~13.0000
-
幽雅に咲かせ、墨染の桜[biginner]/[NORMAL-]- SCRATCH: 199.155
- scratchPeakWindowNotes: 32
- peak/sec: 10.6667
平均スクラッチ密度だけでは低く見える譜面でも、3秒局所ピークが強ければSCRATCHレーダーとして出るようになっています。
エージェント ちゃん さんが26日前に更新
操作
#26
CHARGE検討用の生特徴量を追加し、全更新して分布を確認しました。
追加した生特徴量¶
-
feature_charge_notes_during_ln_peak_window- LN中に別レーンで処理するノーツ数の3秒窓ピーク
- 平均では埋もれる局所的なLN拘束を拾うための値
-
feature_max_simultaneous_ln- 譜面中の最大同時LN本数
- 単LN拘束と多重LN拘束を分けるための値
既存の以下の特徴量と組み合わせてCHARGE式を検討できます。
feature_ln_totalfeature_ln_seconds_totalfeature_charge_notes_during_lnfeature_duration_secondsfeature_notes_total
確認結果¶
-
./gradlew.bat test --tests bms.player.beatoraja.song.NotesRadarCalculatorTest --tests bms.player.beatoraja.song.SQLiteSongDatabaseAccessorTest: 成功 -
./gradlew.bat test: 成功 -
./gradlew.bat updateAllSongs: 成功song database update finished: 249172 ms-
PRAGMA integrity_check: ok
分布メモ¶
mode=7 and feature>0 の16103件で確認しました。
feature_charge_notes_during_ln_peak_window¶
p50 4
p70 14
p80 22
p90 38
p95 55
p97 66
p98 73
p99 87
p99.5 101
p99.9 132
max 74412
feature_max_simultaneous_ln¶
p50 1
p70 2
p80 3
p85 4
p90 6
p95 7
p99.5 8
max 8
再検討メモ¶
CHARGEは以下の要素を分けて扱うのが良さそうです。
holdDensity = feature_ln_seconds_total / feature_duration_seconds
overlapDensity = feature_charge_notes_during_ln / feature_duration_seconds
overlapRatio = feature_charge_notes_during_ln / feature_notes_total
overlapPeak = feature_charge_notes_during_ln_peak_window / 3.0
maxLn = feature_max_simultaneous_ln
暫定候補としては次のような合成が自然でした。
chargeRaw =
holdDensity * 0.8
+ overlapDensity * 0.35
+ overlapRatio * 3.0
+ overlapPeak * 0.18
+ min(maxLn, 4) * 0.25
この候補では、普通のCN譜面は中位から高め、穴抜き系やLN拘束中にほぼ叩き続ける譜面は上位、局所的にLN中ノーツが密集する譜面も平均より高く評価できます。
ただし L.D.∞ などの鑑賞・ネタ寄り外れ値は依然として極端に大きいため、最終レーダー化ではNOTES/PEAK/SCRATCHと同じくp99~p99.5付近を200基準にして、それ以上はクリップするのが妥当そうです。
エージェント ちゃん さんが26日前に更新
操作
#27
CHARGEレーダーに暫定式を反映し、全更新後の値を確認しました。
反映した式¶
holdDensity = feature_ln_seconds_total / feature_duration_seconds
overlapDensity = feature_charge_notes_during_ln / feature_duration_seconds
overlapRatio = feature_charge_notes_during_ln / feature_notes_total
overlapPeak = feature_charge_notes_during_ln_peak_window / 3.0
maxLn = feature_max_simultaneous_ln
chargeRaw =
holdDensity * 0.8
+ overlapDensity * 0.35
+ overlapRatio * 3.0
+ overlapPeak * 0.18
+ min(maxLn, 4) * 0.25
chargeRaw は mode=7 and feature>0 の分布からアンカー補間しています。
- p70付近: 100
- p99.5付近: 200
- p99.5超過: 200でクリップ
確認結果¶
-
./gradlew.bat test --tests bms.player.beatoraja.song.NotesRadarCalculatorTest --tests bms.player.beatoraja.song.SQLiteSongDatabaseAccessorTest: 成功 -
./gradlew.bat test: 成功 -
./gradlew.bat updateAllSongs: 成功song database update finished: 240252 ms-
PRAGMA integrity_check: ok
CHARGEレーダー分布¶
mode=7 and feature>0 の16101件です。
nonzero 15202
200到達 81
p10 14.3032
p25 35.6985
p50 71.4037
p70 99.9983
p75 116.9501
p80 133.8943
p85 150.8239
p90 167.8001
p95 184.7502
p97 191.4452
p98 194.9200
p99 198.3100
p99.5 200.0000
p100 200.0000
サンプル¶
普通のCN系は中位~高めに収まり、LN拘束が強い譜面は上位に出ています。
The First Blade [LegendFencer] CHARGE 151.7409
MoonLigh'dDust [SP INSANE] CHARGE 148.8704
Sylph (SP ANOTHER) CHARGE 141.0641
空から贈る、あなたへの [SP ANOTHER] CHARGE 137.2803
Majesty of Anubis CHARGE 134.3138
B.B.K.K.B.K.K. [戻] CHARGE 100.9356
The First Blade [HYPER] CHARGE 100.2108
上位200到達には、FREEDOM DiVE [LN FOUR DIMENSIONS]、iterated [LN INFINITE]、銀の風 more迷人、HAELEQUIN -aletheia-_穴抜き など、LN拘束中に大量処理がある譜面が入っています。鑑賞・ネタ寄り外れ値も200に含まれますが、p99.5クリップなので分布全体への影響は抑えられています。
エージェント ちゃん さんが26日前に更新
操作
#28
CHORD検討用の生特徴量を追加し、全更新して分布を確認しました。
追加した生特徴量¶
-
feature_chord_peak_window_score- 3秒窓内の同時押し重みスコア最大値
- 各タイミングのスコアは既存と同じ
(同時押し数 - 1) * 同時押し数
-
feature_chord_peak_window_notes-
feature_chord_peak_window_scoreが最大になった3秒窓内の同時押し構成ノーツ数
-
既存の以下の特徴量と組み合わせてCHORD式を検討できます。
-
feature_chord- 既存の平均同時押しスコア密度
feature_chord_timeline_countfeature_max_chord_sizefeature_notes_totalfeature_duration_seconds
確認結果¶
-
./gradlew.bat test --tests bms.player.beatoraja.song.NotesRadarCalculatorTest --tests bms.player.beatoraja.song.SQLiteSongDatabaseAccessorTest: 成功 -
./gradlew.bat test: 成功 -
./gradlew.bat updateAllSongs: 成功song database update finished: 241014 ms-
PRAGMA integrity_check: ok
分布メモ¶
mode=7 and feature>0 の16102件で確認しました。
feature_chord¶
p50 11.8278
p70 21.7603
p80 29.4097
p90 43.0769
p95 56.9521
p98 74.3618
p99 88.9233
p99.5 106.4349
p99.9 193.4037
max 13790.8138
feature_chord_peak_window_score¶
p50 120
p70 204
p80 264
p90 370
p95 466
p98 602
p99 688
p99.5 804
p99.9 1964
max 480288
feature_chord_peak_window_notes¶
p50 52
p70 72
p80 84
p90 102
p95 117
p98 135
p99 149
p99.5 164
p99.9 426
max 82602
式候補¶
平均同時押し負荷、局所ピーク、同時押し比率、最大同時押し数を混ぜるなら、以下が候補です。
averageScore = feature_chord
peakScore = feature_chord_peak_window_score / 3.0
chordRatio = feature_chord_timeline_count / feature_notes_total
maxChord = feature_max_chord_size
chordRaw =
averageScore * 0.45
+ peakScore * 0.55
+ chordRatio * 20.0
+ min(maxChord, 8) * 1.2
この候補の分布は以下です。
p50 38.9424
p70 60.1646
p80 75.1742
p90 100.9253
p95 125.3180
p98 155.8728
p99 174.9860
p99.5 204.9837
p99.9 443.9166
max 94271.9233
所感¶
- CHORDはNOTES/PEAKとの相関がかなり強いため、単に高密度な譜面が上に来やすいです。
- ただし
feature_chord_peak_window_scoreを入れると、局所的な同時押しラッシュを平均から分離できます。 -
feature_chord_timeline_count / feature_notes_totalは、総ノーツのうちどれだけ同時押し化しているかを見る補助指標として有効です。 -
feature_max_chord_sizeはほとんど上位で8に張り付くため、主成分ではなく小さい補正が妥当です。 - 最終レーダー化は、これまでと同じくp70付近を100、p99.5付近を200に置いて、p99.5超過は200クリップが良さそうです。
エージェント ちゃん さんが26日前に更新
操作
#29
CHORDレーダーに暫定式を反映し、全更新後の値を確認しました。
反映した式¶
averageScore = feature_chord
peakScore = feature_chord_peak_window_score / 3.0
chordRatio = feature_chord_timeline_count / feature_notes_total
maxChord = feature_max_chord_size
chordRaw =
averageScore * 0.45
+ peakScore * 0.55
+ chordRatio * 20.0
+ min(maxChord, 8) * 1.2
chordRaw は mode=7 and feature>0 の分布からアンカー補間しています。
- p70付近: 100
- p99.5付近: 200
- p99.5超過: 200でクリップ
確認結果¶
-
./gradlew.bat test --tests bms.player.beatoraja.song.NotesRadarCalculatorTest --tests bms.player.beatoraja.song.SQLiteSongDatabaseAccessorTest: 成功 -
./gradlew.bat test: 成功 -
./gradlew.bat updateAllSongs: 成功song database update finished: 256918 ms-
PRAGMA integrity_check: ok
CHORDレーダー分布¶
mode=7 and feature>0 の16102件です。
nonzero 15994
200到達 81
p10 14.2901
p25 35.6532
p50 71.4299
p70 100.0151
p75 116.9611
p80 133.9024
p85 150.8817
p90 167.8184
p95 184.7500
p97 191.5300
p98 194.9200
p99 198.3100
p99.5 200.0000
p100 200.0000
上位例¶
上位200には、局所同時押しピークや高密度同時押し比率が強い譜面が入りました。
L.D.∞ CHORD 200.00
HEXPERUS [Supernova] CHORD 200.00
P8100000000000000000000000000000007 CHORD 200.00
omega i/v/ix/vii/iv/iv CHORD 200.00
Unleashed World -ERROR- CHORD 200.00
Long9999999999999^99999999999 CHORD 200.00
Kaiden Aura (overjoy) CHORD 200.00
痩せろコラ CHORD 200.00
DENGEKI Tube CHORD 200.00
NOTES/PEAKとの相関は強いですが、feature_chord_peak_window_score を入れたことで局所的な同時押しラッシュも平均同時押し密度から分離して拾えるようになっています。
エージェント ちゃん さんが26日前に更新
操作
#30
ここまでの作業内容と今後の作業予定をまとめます。
ここまで実施したこと¶
1. レーダー用の生特徴量を追加¶
NotesRadarFeature を追加し、各譜面についてレーダー計算・調整に使う生特徴量をDBへ保存できるようにしました。
主な追加特徴量:
- NOTES
feature_notes_totalfeature_duration_secondsfeature_peak_window_notes
- SCRATCH
feature_scratch_totalfeature_scratch_peak_window_notes
- CHARGE
feature_ln_totalfeature_ln_seconds_totalfeature_charge_notes_during_lnfeature_charge_notes_during_ln_peak_windowfeature_max_simultaneous_ln
- CHORD
feature_chord_timeline_countfeature_max_chord_sizefeature_chord_peak_window_scorefeature_chord_peak_window_notes
- SOFLAN
feature_speed_change_countfeature_stop_countfeature_scroll_change_countfeature_soflan_weighted_notesfeature_soflan_cluster_countfeature_soflan_affected_notesfeature_soflan_cluster_scorefeature_soflan_max_change_ratefeature_soflan_stop_time_total
2. 楽曲DB更新時に特徴量を計算・保存¶
SongDatabaseUpdater から譜面解析時に NotesRadarCalculator を呼び、songdata.db の song テーブルへ特徴量とレーダー値を保存するようにしました。
既存DBに対して不足カラムが追加されるよう、SQLiteアクセサ・DTO・ResultSetHandler・永続化テストも更新しています。
3. 全更新用Gradleタスクを追加¶
こちら側で全更新を実行できるように、updateAllSongs タスクを追加しました。
実行例:
./gradlew.bat updateAllSongs
複数回実行し、直近では以下を確認済みです。
song database update finished: 256918 ms
PRAGMA integrity_check: ok
4. 各レーダーの暫定式を実装¶
6種すべてについて、0.00~200.00のレーダー値を出す暫定式を実装しました。
NOTES¶
- 秒間ノーツ密度を使用
-
mode=7 and feature>0相当の分布をもとにアンカー補間 - p70付近を100、p99.5付近を200
PEAK¶
- 3秒窓の最大ノーツ数を採用
- p70付近を100、p99.5付近を200
SCRATCH¶
平均スクラッチ負荷と局所スクラッチピークの大きい方を採用。
averageScore = scratchTotal / durationSeconds * sqrt(min(scratchTotal / notesTotal / 0.25, 1.0))
peakScore = scratchPeakWindowNotes / 3.0 * 0.5
scratchRaw = max(averageScore, peakScore)
幽雅に咲かせ、墨染の桜[NORMAL] など、局所的な連皿譜面が高く出ることを確認済みです。
CHARGE¶
LN拘束量、LN中ノーツ、局所ピーク、最大同時LN数を合成。
holdDensity = feature_ln_seconds_total / feature_duration_seconds
overlapDensity = feature_charge_notes_during_ln / feature_duration_seconds
overlapRatio = feature_charge_notes_during_ln / feature_notes_total
overlapPeak = feature_charge_notes_during_ln_peak_window / 3.0
maxLn = feature_max_simultaneous_ln
chargeRaw =
holdDensity * 0.8
+ overlapDensity * 0.35
+ overlapRatio * 3.0
+ overlapPeak * 0.18
+ min(maxLn, 4) * 0.25
p70付近を100、p99.5付近を200に補間。
CHORD¶
平均同時押し負荷、局所同時押しピーク、同時押し比率、最大同時押し数を合成。
averageScore = feature_chord
peakScore = feature_chord_peak_window_score / 3.0
chordRatio = feature_chord_timeline_count / feature_notes_total
maxChord = feature_max_chord_size
chordRaw =
averageScore * 0.45
+ peakScore * 0.55
+ chordRatio * 20.0
+ min(maxChord, 8) * 1.2
p70付近を100、p99.5付近を200に補間。
SOFLAN¶
BPM/STOP/SCROLL変化の周辺ノーツを重み付けして評価。
- BPM変化直後ほど重く、時間が離れるほど軽くする
- 変化前後のクラスタを作り、近い変化はマージ
- 巨大BPMや極端な変化率は上限を設けて評価
- Aleph-0、神姫のような強いソフラン譜面が高く出る方向で調整
5. テスト追加・確認¶
以下を確認済みです。
./gradlew.bat test --tests bms.player.beatoraja.song.NotesRadarCalculatorTest --tests bms.player.beatoraja.song.SQLiteSongDatabaseAccessorTest
./gradlew.bat test
./gradlew.bat updateAllSongs
追加した主なテスト:
- LN中の別レーンノーツをCHARGEとして数える
- LN中ノーツの3秒ピークを数える
- 最大同時LN数を数える
- SCRATCHの3秒ピークを数える
- 局所SCRATCHでSCRATCHレーダーが高く出る
- CHORDの3秒ピークを数える
- 局所CHORDでCHORDレーダーが高く出る
- SOFLAN変化直後のノーツを重く評価する
- 極端なSOFLAN値を200以内に収める
- DB永続化カラムの保存・復元
現在の状態¶
全6レーダーについて、暫定式としてDB更新時に0.00~200.00の値が計算・保存される状態です。
NOTES 実装済み
PEAK 実装済み
SCRATCH 実装済み
CHARGE 実装済み
CHORD 実装済み
SOFLAN 実装済み
今後やること¶
1. 上位譜面の横断確認¶
各レーダーの上位譜面を一覧化し、体感と大きくズレていないか確認します。
特に確認したい点:
- 200到達譜面が妥当か
- 190台の譜面が体感上も最上位クラスか
- NOTES/PEAK/CHORDが似すぎていないか
- SCRATCH/CHARGE/SOFLANが特徴譜面を拾えているか
2. 外れ値・鑑賞譜面・人外譜面の扱いを決める¶
Overjoy Lv8級、人類にクリア不能な譜面、鑑賞・ネタ譜面が難易度表やローカルDBに含まれます。
今後決めるべきこと:
- どの母集団をレーダー式の基準にするか
- 統計的外れ値を式決定から除外するか
- DB上の値としては200クリップでよいか
- 表示やランキングで外れ値を別扱いする必要があるか
3. beatoraja上での実機確認¶
DB上の値は計算・保存できていますが、画面表示や運用上の確認が必要です。
確認項目:
- 楽曲選択画面で各レーダー値が意図通り表示されるか
- DB更新後に既存曲・新規曲とも値が反映されるか
- 既存DBからのマイグレーションで問題がないか
- 値が未計算の曲が0扱いで破綻しないか
4. 代表譜面での体感確認¶
各レーダーについて代表譜面を選び、実機またはDB値で確認します。
例:
- SCRATCH:
幽雅に咲かせ、墨染の桜[NORMAL]系 - SOFLAN:
Aleph-0、神姫 - CHARGE:
FREEDOM DiVE [LN FOUR DIMENSIONS]、iterated [LN INFINITE] - CHORD: 局所同時押しピークが強い譜面、同時押し比率が高い譜面
- NOTES/PEAK: 高密度譜面、局所発狂譜面
5. 仕様確定後にコミット¶
現時点では暫定式実装済みですが、最終確定前の検証段階です。
ユーザー確認後、問題なければ以下の形式でコミット予定です。
refs#145 Implement notes radar feature calculation
※ git pushは行いません。
エージェント ちゃん さんが26日前に更新
操作
#31
スキンプロパティ公開(本日の作業サマリー)¶
実施内容¶
src/bms/player/beatoraja/skin/property/FloatPropertyTypes.java に、6種のノーツレーダー値をスキンから参照できるフロートプロパティを追加しました。
| プロパティ名 | ID | 対応レーダー |
|---|---|---|
chart_radar_notes |
369 | NOTES |
chart_radar_peak |
370 | PEAK |
chart_radar_scratch |
371 | SCRATCH |
chart_radar_charge |
372 | CHARGE |
chart_radar_chord |
373 | CHORD |
chart_radar_soflan |
374 | SOFLAN |
スキンの float プロパティ定義でこれらの ID を指定することで、選択中の曲のレーダー値(0.00〜200.00)を取得できます。
テスト¶
./gradlew.bat test → BUILD SUCCESSFUL
現在の実装状況¶
| 項目 | 状態 |
|---|---|
レーダー値計算ロジック(NotesRadarCalculator) |
✅ 完了 |
生特徴量クラス(NotesRadarFeature) |
✅ 完了 |
DB保存(songdata.db 自動マイグレーション) |
✅ 完了 |
全更新CLI(./gradlew.bat updateAllSongs) |
✅ 完了 |
| スキンプロパティ公開 | ✅ 完了(本日追加) |
| プレーヤーノーツレーダー | ⬜ 未実装 |
| 実機確認(beatorajaでの表示確認) | ⬜ 未実施 |
残タスク¶
- プレーヤーノーツレーダー — スコア更新時に各要素TOP10平均でプレーヤー側レーダーを算出する処理
- 実機確認 — スキンにレーダー値を表示して目視確認
エージェント ちゃん さんが26日前に更新
操作
#32
進捗¶
プレーヤーノーツレーダー確認用の専用画面を追加しました。
実装内容:
-
F5でPLAY中以外からプレーヤーノーツレーダー画面へ遷移 - 画面上で
F5またはESCにより元の画面へ戻る - PLAY中は
F5を無効化 - プレーヤーレーダーチャートを表示するデフォルトスキンを追加
- 左右キー、マウスホイール、選曲入力のターゲット上下(スクラッチ相当)で表示軸を切り替え
- NOTES → PEAK → SCRATCH → CHARGE → CHORD → SOF-LAN
- 各軸の対象曲TOP10を表示
- 表示形式:
<レーダー値> <難易度表レベル> <曲名(差分名)> - 難易度表レベルはテーブルキャッシュから
sl1/★9などのフォルダ名を補完 - 取得できない場合は譜面レベル
LvNにフォールバック
- 表示形式:
確認:
.\gradlew.bat test --tests bms.player.beatoraja.ScoreDatabaseAccessorTest
.\gradlew.bat test
どちらも BUILD SUCCESSFUL です。
Claude Codeレビューも実施しました。score.mode = 7 を追加すべきという指摘がありましたが、このコードベースの score.mode は譜面モードではなくLN/コース用スコアモードのため不採用にしています。
エージェント ちゃん さんが26日前に更新
操作
#33
追記修正¶
プレーヤーノーツレーダー画面で既存スコアが反映されない問題に対応しました。
原因:
-
player_notes_radarテーブル追加後、既存scoreからの初回再集計が画面表示前に必ず実行されていなかった - そのため、新規プレイでスコア更新された場合以外はプレーヤーレーダーチャートが空のままになる可能性があった
修正内容:
-
PlayDataAccessor初期化時、保存済みプレーヤーレーダーが空なら既存スコアから再集計 - プレーヤーノーツレーダー画面を開くたびに既存スコアから再集計
- 空データ環境で毎フレーム再集計しないよう、スキンプロパティ読み取り時の自動再計算は行わない
確認:
.\gradlew.bat test --tests bms.player.beatoraja.ScoreDatabaseAccessorTest
.\gradlew.bat test
結果: BUILD SUCCESSFUL
注意:
既存スコアの再プレイは不要です。ただし、譜面側のレーダー値が songdata.db に未計算の場合はプレーヤーレーダーも算出できないため、その場合は楽曲DB全更新(updateAllSongs 相当)が必要です。
エージェント ちゃん さんが26日前に更新
操作
#34
追記修正¶
プレーヤーノーツレーダー画面でレーダーチャートだけ表示され、対象曲リストやラベルが表示されない問題を修正しました。
原因:
-
skin/default/playernotesradar.jsonのフォント指定がVL-Gothic-Regular.ttfになっていた -
skin/default直下には該当フォントが存在せず、ログ上もSkin Font読み込み失敗が発生していた - その結果、テキストSkinObjectがprepare時に削除され、対象曲リストだけでなくタイトル/軸ラベルも表示されていなかった
修正内容:
- フォント参照を実ファイル位置に合わせて
../../font/VL-Gothic-Regular.ttfに変更
確認:
- 実DBに対してNOTES対象曲TOP10が取得できることを確認
-
./gradlew.bat test→ BUILD SUCCESSFUL
エージェント ちゃん さんが26日前に更新
操作
#35
プレーヤーノーツレーダー画面について、最新スクショ確認後の追加対応を実施しました。
- デフォルトスキンに現在の各レーダー値一覧を追加
- NOTES / CHORD / PEAK / CHARGE / SCRATCH / SOF-LAN
- TOTAL RADAR SCORE
- 合計値を
PlayerNotesRadar.total()として計算し、スキン向けfloatプロパティplayer_radar_totalも追加 - デフォルトスキンでは小数2桁の文字列プロパティで現在値を表示
- 既存スコアからの再計算は画面生成時の同期実行を避け、
RuntimeTaskManagerのバックグラウンドタスクで実行するよう調整 -
PlayerNotesRadar/PlayerNotesRadarEntry/PlayerNotesRadarAxisの単体テストと合計値検証を追加
検証:
python -m json.tool skin/default/playernotesradar.json./gradlew.bat test
Claude Codeレビューも実施し、指摘された player_radar_total のID重複は修正済みです。
エージェント ちゃん さんが26日前に更新
操作
#36
最新スクショで確認された表示被りを修正しました。
-
SCRATCH/SOF-LANなどのラベルと値が近すぎるため、現在値の数値列を右へ移動 -
TOTAL RADAR SCOREと合計値が重なっていたため、ラベルをTOTALに短縮し、値列位置を調整 -
TARGETと現在軸名が重なっていたため、軸名表示位置を右へ移動
検証:
python -m json.tool skin/default/playernotesradar.json./gradlew.bat test
エージェント ちゃん さんが26日前に更新
操作
#37
追加対応しました。
- プレーヤーノーツレーダー画面右上のヘルプが途切れていたため、文言を短縮して表示幅を調整
LEFT/RIGHT: axis F5/ESC: back
- プレーヤーノーツレーダー画面で撮影したスクリーンショットのファイル名に状態名が付くよう修正
yyyyMMdd_HHmmss_Player_Notes_Radar.png
- Claude Codeレビューで指摘された、レーダー画面を開き直した際のバックグラウンド再計算タスク重複起動を防止
- スコア保存時のプレーヤーノーツレーダー再計算は7KEYSスコア更新時のみに制限
検証:
python -m json.tool skin/default/playernotesradar.json./gradlew.bat test
エージェント ちゃん さんが26日前に更新
操作
#38
追加仕様を反映しました。
- TOP10選定で同一曲の別差分を除外
- デイリーチャレンジと同じく
title + artistをキーに重複判定 - 同一曲内では対象レーダー値が最も高い1譜面のみを採用
- 対象曲一覧だけでなく、プレーヤーレーダー値のTOP10平均計算にも同じ除外条件を適用
- デイリーチャレンジと同じく
- プレーヤーレーダーチャートの軸順を曲ノーツレーダーと同じ順に変更
- 12時から時計回りに
NOTES -> PEAK -> SCRATCH -> SOF-LAN -> CHARGE -> CHORD
- 12時から時計回りに
- デフォルトスキンのレーダーラベルと現在値ラベルの色を曲ノーツレーダー表示に合わせて調整
- Claude Codeレビュー指摘対応
- スクショ状態名分岐の既存
else if抜けを修正 - プレーヤーレーダー値文字列プロパティIDを軸順に整理
- レーダー更新タスク完了時の一覧更新は、現在もプレーヤーノーツレーダー画面の場合のみ実行
- 未使用メソッドを削除
- スクショ状態名分岐の既存
検証:
python -m json.tool skin/default/playernotesradar.json./gradlew.bat test
エージェント ちゃん さんが26日前に更新
操作
#39
NPEログを調査して修正しました。
原因:
-
beatoraja.0.logの 2026-06-10 18:24:26.663 に以下のNPEを確認Cannot invoke "bms.player.beatoraja.skin.Skin.mousePressed(...)" because the return value of "bms.player.beatoraja.MainState.getSkin()" is null
- 直前にプレーヤーノーツレーダー画面からMusic Selectへ戻るスキン切替が走っており、dispose済みまたは切替中のstateに残ったマウス押下イベントを
FrameInputServiceが処理していた
対応:
-
FrameInputServiceのmousePressed/mouseDraggedでcurrent.getSkin()がnullの場合はスキンへイベントを渡さないようガード - スコア保存後のプレーヤーノーツレーダー再計算をバックグラウンド化し、リザルト遷移時の同期DB処理を回避
- プレーヤーノーツレーダー対象曲一覧の軸切替時DB取得もバックグラウンド化
- リクエストIDで古い結果を破棄
- 画面離脱後は一覧へ反映しない
- プレーヤーノーツレーダー画面の
shutdown()で更新タスクにinterrupt - プロパティID資料の不足分を
docs/discussion.mdに追記
検証:
./gradlew.bat test- Claude Codeレビュー実施、指摘された
entriesRequestIdの可視性問題はvolatile化して修正済み
エージェント ちゃん さんが26日前に更新
操作
#40
調査・対応メモ¶
最新ログでは beatoraja-player-notes-radar-entries-* のランタイムタスクが多数残ったまま終了していました。F5のプレーヤーノーツレーダー画面で軸切替ごとにTOP10集計SQLを非同期実行していたため、集計が返る前に画面側が空のままになりやすい構造でした。
対応内容:
- プレーヤーノーツレーダーのTOP10対象曲を
score.dbのplayer_notes_radar_entryテーブルに永続化するよう変更 -
player_notes_radarとplayer_notes_radar_entryを同一更新処理で保存 - F5のプレーヤーノーツレーダー画面は保存済みデータを読むだけに変更
- 既存DB向けに、起動時に集計値またはTOP10保存データがない場合は非同期で移行集計を実行
- スコア更新時は7KEYS譜面の場合にバックグラウンドでレーダー再集計を要求
- TOP10集計SQLを
row_number()ベースに変更し、同一譜面の別差分除外と速度を改善 - 難易度表レベル表示を保存時に反映するテストを追加
確認:
-
./gradlew.bat test実行済み、成功 - Claude Code review 実行済み。今回のTOP10非表示修正を止めるブロッキング指摘はなし
補足:
F5押下時には再集計しない方針に変更しています。既存DBで初回起動直後だけは、移行集計が完了するまでTOP10が空に見える可能性がありますが、完了後は保存済みテーブルから即時表示されます。
エージェント ちゃん さんが26日前に更新
操作
#41
追加対応メモ¶
プレーヤーノーツレーダーTOP10の難易度表レベル表記について、参照対象を以下の難易度表に限定しました。
- NEW GENERATION通常難易度
- NEW GENERATION発狂難易度
- 第三期Overjoy
- 通常難易度表
- 発狂BMS難易度表
- Satellite
- Stella
実装内容:
- 難易度表レベル収集時に、上記7表以外のキャッシュ済み難易度表を除外
- URL一致を優先し、名称はNFKC正規化・空白除去後に補助判定
- DP/LN/24K/PMSなどの難易度表レベルはプレーヤーノーツレーダーTOP10には使わない
-
Mode.BEAT_7K.id == 7をテストで確認 - プレーヤーノーツレーダー更新中にスコア保存側が重いDB集計のロック待ちをしないよう、更新要求の多重起動抑制を軽量化
- DB保存失敗時に未保存の計算値をメモリキャッシュへ返さないよう修正
確認:
-
./gradlew.bat test --tests bms.player.beatoraja.PlayDataAccessorTest --tests bms.player.beatoraja.ScoreDatabaseAccessorTest成功 -
./gradlew.bat test成功 - Claude Code review は一度実行し、有効指摘を修正済み。修正後の再レビューはClaude CLIのセッション上限により実行不可でした。
エージェント ちゃん さんが26日前に更新
操作
#42
追加対応メモ¶
プレーヤーノーツレーダーを既存スコアから手動再集計できるGradleタスクを追加しました。
追加コマンド:
./gradlew.bat updatePlayerNotesRadar
オプション:
./gradlew.bat updatePlayerNotesRadar --args="--player=<playerName>"
./gradlew.bat updatePlayerNotesRadar --args="--score-db=<path>"
./gradlew.bat updatePlayerNotesRadar --args="--song-db=<path> --table-dir=<path>"
実装内容:
-
build.gradleにupdatePlayerNotesRadarJavaExecタスクを追加 -
PlayerNotesRadarUpdateCliを追加し、UIを起動せずにscore.dbのplayer_notes_radar/player_notes_radar_entryを再計算 - 難易度表レベルの対象表判定を
PlayerNotesRadarLevelTablesに切り出し、ゲーム起動時更新とCLIで共有 -
docs/discussion.mdに手動再集計コマンドを追記
確認:
-
./gradlew.bat updatePlayerNotesRadar --args="--help"成功 -
./gradlew.bat test --tests bms.player.beatoraja.PlayDataAccessorTest --tests bms.player.beatoraja.ScoreDatabaseAccessorTest成功 -
./gradlew.bat test成功
補足:
Claude Code reviewは前回以降CLIのセッション上限中のため再実行できていません。
エージェント ちゃん さんが26日前に更新
操作
#43
追加対応メモ¶
WMII_FHDのリザルト画面で、その回のスコアを使ったノーツレーダー値を表示できるようにしました。
実装内容:
- リザルト用レーダー値プロパティを追加
-
result_radar_notes= 1890 -
result_radar_peak= 1891 -
result_radar_scratch= 1892 -
result_radar_soflan= 1893 -
result_radar_charge= 1894 -
result_radar_chord= 1895 -
result_radar_total= 1897
-
- 計算式は自己ベストではなく、その回のスコアを使用
リザルト単曲レーダー値 = 曲ノーツレーダー値 * (今回EXスコア / (総ノーツ * 2))
- WMII_FHDのリザルトExpand PanelのGRAPH側に2行で表示
NOTES / PEAK / SCRSOF / CHG / CHD / TOTAL
- Luaから参照できるよう、追加プロパティは
main_state.float_number()で取得可能なRateType側に登録 -
skin/WMII_FHD/result/resultExpand.luaを追跡対象にするため.gitignoreに例外を追加 -
docs/discussion.mdにIDと計算式を追記
確認:
-
./gradlew.bat test --tests bms.player.beatoraja.skin.property.SkinPropertyExecutionTest --tests bms.player.beatoraja.skin.property.SkinPropertyFactoryRegistryTest成功 -
./gradlew.bat test成功
補足:
Claude Code reviewはClaude CLIのセッション上限により実行できませんでした。
エージェント ちゃん さんが26日前に更新
操作
#44
PEAKの短時間バースト補正を実装しました。
- 既存の3秒窓PEAKを維持しつつ、1秒窓の短時間バースト密度を70%混ぜた補正値も評価するように変更
peakRaw = max(3秒窓密度, 1秒窓密度 * 0.70 + 3秒窓密度 * 0.30)- 1秒以内に集中する短時間バーストでPEAKが上がることを単体テストで追加確認
-
docs/discussion.mdに計算式を追記
確認:
-
./gradlew.bat test --tests bms.player.beatoraja.song.NotesRadarCalculatorTest成功 -
./gradlew.bat test成功 - Claude Codeレビューは
You've hit your session limit · resets 10:20pm (Asia/Tokyo)のため実行不可でした。
エージェント ちゃん さんが26日前に更新
操作
#45
WMII_FHDリザルトのGraph Dataパネル下部表示を調整しました。
-
DURATION AVG/TIMING AVG/STDDEVの数値描画を削除 - 同じ下部エリアを黒背景で覆い、今回プレイスコア基準のノーツレーダーを2行表示に変更
-
NOTES/PEAK/SCR -
SOF/CHG/CHD/TOTAL
-
- 以前追加していた上部のノーツレーダー表示は削除し、指定された下部エリアへ移動
-
.gitignoreを調整し、skin/WMII_FHD/result/resultExpand.luaだけを追跡対象にしました
確認:
-
durationAvg/durationAvgAdot/stddav/stddaAdotの描画参照がresultExpand.luaから消えていることを確認 - ローカルに
lua/luacがないためLua構文チェッカは未実行です。
エージェント ちゃん さんが26日前に更新
操作
#46
WMII_FHDリザルトのノーツレーダー表示背景を不透明化しました。
-
resultRadarBackのテクスチャ背景を削除 - 組み込み黒矩形
id = -110を使用し、a = 255の不透明黒背景に変更 - この対応では
.gitignoreは変更していません。
エージェント ちゃん さんが26日前に更新
操作
#47
Luxe_Flat風のプレーヤーノーツレーダー画面スキンを追加しました。
-
skin/Luxe_Flat/playernotesradar.jsonを作成 - 1920x1080、Luxe_Flat既存の背景画像を使用
- 左に大きめのプレーヤーレーダーチャート
- 右上に各軸の現在値とTOTAL
- 右下に現在選択中レーダー軸のTOP10対象曲を1列表示
- 軸ラベル色はノーツレーダーの色に合わせました
-
.gitignoreは変更していません
確認:
- PowerShell
ConvertFrom-JsonでJSONとして読めることを確認 -
skin/Luxe_Flat/playernotesradar.jsonは既存.gitignoreの/skin/*対象のため、Gitステータスには表示されません。
エージェント ちゃん さんが26日前に更新
操作
#48
Luxe_Flat風プレーヤーノーツレーダー画面をLuaスキン化して作り直しました。
-
skin/Luxe_Flat/playernotesradar.jsonを削除 -
skin/Luxe_Flat/playernotesradar.luaskinを追加 -
skin/Luxe_Flat/playernotesradar.luaを追加 - 専用フレーム画像を追加
skin/Luxe_Flat/playernotesradar/panel_radar.pngskin/Luxe_Flat/playernotesradar/panel_values.pngskin/Luxe_Flat/playernotesradar/panel_list.pngskin/Luxe_Flat/playernotesradar/topbar.png
- レイアウトを再設計
- 上部: タイトル / 現在ターゲット / 操作ガイド
- 左: レーダーチャート
- 右上: 6軸の現在値を2列グリッド表示、CHORDだけ別扱いにしない
- 右下: TOP10対象曲を1列で広く表示
-
.gitignoreは変更していません
確認:
- 一時的なJUnitで
LuaSkinLoader.loadHeader("skin/Luxe_Flat/playernotesradar.luaskin")が成功することを確認後、テスト差分は戻しました。 - Claude Codeレビューは
You've hit your session limit · resets 10:20pm (Asia/Tokyo)のため実行不可でした。
エージェント ちゃん さんが26日前に更新
操作
#49
プレーヤーノーツレーダーTOP10をテーブル表示できるよう、列別StringPropertyを追加しました。
- 既存の結合済み行
player_radar_entry1-player_radar_entry10(1870-1879) は互換用に維持 - 新規refを追加
-
player_radar_entry1_value-player_radar_entry10_value(1900-1909) -
player_radar_entry1_level-player_radar_entry10_level(1910-1919) -
player_radar_entry1_title-player_radar_entry10_title(1920-1929)
-
-
PlayerNotesRadarStateにVALUE/LEVEL/TITLE別アクセサを追加 - Luxe_Flat
playernotesradar.luaのTOP10表示を、1行文字列ではなく3列テキストに変更 -
docs/discussion.mdに新規IDを追記
確認:
-
./gradlew.bat test --tests bms.player.beatoraja.PlayerNotesRadarTest --tests bms.player.beatoraja.skin.property.SkinPropertyExecutionTest成功 -
./gradlew.bat test成功 - 一時JUnitで
LuaSkinLoader.loadHeader("skin/Luxe_Flat/playernotesradar.luaskin")成功確認後、テスト差分は戻し済み - Claude Codeレビューは
You've hit your session limit · resets 10:20pm (Asia/Tokyo)のため実行不可でした。
エージェント ちゃん さんが26日前に更新
操作
#50
Luxe_Flatプレーヤーノーツレーダー画面のフォントと左上ラベルを調整しました。
- 左上の
PLAYER PROFILEを削除 -
VL-Gothic-Regular.ttfからLuxe_Flat既存のBMFontへ変更- タイトル/現在ターゲット:
font_Title - 見出し/ラベル:
font_sub/font_sub_small - 数値/TOP10:
font_songlist
- タイトル/現在ターゲット:
- 一時JUnitで
LuaSkinLoader.loadHeader("skin/Luxe_Flat/playernotesradar.luaskin")成功確認後、テスト差分は戻し済み
スクショ上のガビガビ感は、VL Gothic TTFを大きめサイズで描画していた影響が強いと判断しました。
エージェント ちゃん さんが26日前に更新
操作
#51
プレーヤーノーツレーダー画面のTOP表示を調整しました。
- Luxe_Flatスキン上部中央の
CURRENT TARGET/ ターゲット名表示を削除 - TOP10パネル見出しを動的表示に変更
NOTES CHARTS TOP 10PEAK CHARTS TOP 10- など、現在選択中の軸名を反映
- 新規StringProperty
player_radar_axis_charts_top10(1930) を追加 -
docs/discussion.mdに新規IDを追記
確認:
-
./gradlew.bat test --tests bms.player.beatoraja.skin.property.SkinPropertyExecutionTest.playerRadarTableColumnStringPropertiesExist成功 - 一時JUnitで
LuaSkinLoader.loadHeader("skin/Luxe_Flat/playernotesradar.luaskin")成功確認後、テスト差分は戻し済み -
./gradlew.bat test成功 - Claude Codeレビューは
You've hit your session limit · resets 10:20pm (Asia/Tokyo)のため実行不可でした。
エージェント ちゃん さんが26日前に更新
操作
#52
Luxe_FlatプレーヤーノーツレーダーのTOP10見出しを部分色分けしました。
-
main_state.text(1866)で現在TARGET名を参照 - TARGET名部分だけ軸色で表示
-
CHARTS TOP 10は従来の見出し色で表示 -
SCRATCH CHARTS TOP 10でも被らないよう、サフィックスの開始位置を右へ調整
確認:
- 一時JUnitで
LuaSkinLoader.loadHeader("skin/Luxe_Flat/playernotesradar.luaskin")成功確認後、テスト差分は戻し済み
エージェント ちゃん さんが26日前に更新
操作
#53
Luxe_Flatプレーヤーノーツレーダーのレーダーチャート半径設定を曲ノーツレーダーと揃えました。
limitRadiusRatio = 0.53maxRadiusRatio = 0.98-
max = 200,limit = 100は維持
これにより、グリッド最外周が100相当、100を超える値はグリッド外側へ伸びて最大200まで描画されます。
確認:
- 一時JUnitで
LuaSkinLoader.loadHeader("skin/Luxe_Flat/playernotesradar.luaskin")成功確認後、テスト差分は戻し済み
エージェント ちゃん さんが26日前に更新
操作
#54
Luxe_Flatプレーヤーノーツレーダーのレーダーチャート色を曲側ノーツレーダーと揃えました。
曲側 notes_radar_chart と同じ設定:
gridColor = "FFFFFFAA"fillColor = "00CCFF28"outlineColor = "00AEEFFF"
確認:
- 一時JUnitで
LuaSkinLoader.loadHeader("skin/Luxe_Flat/playernotesradar.luaskin")成功確認後、テスト差分は戻し済み
エージェント ちゃん さんが26日前に更新
操作
#55
- ステータス を 実装中 から 完了 に変更
- 進捗率 を 10 から 100 に変更
実装完了¶
コミット: 1312f491 (refs#145 Add notes radar and player radar support)
実装内容:
- 曲ノーツレーダー算出と PEAK の短時間バースト補正を追加
- プレーヤーノーツレーダー用DBテーブル、既存スコアからの再計算、TOP10対象曲の永続化を追加
-
gradlew updatePlayerNotesRadarでプレーヤーノーツレーダーを手動更新できるCLIを追加 - PLAY中以外から遷移できる専用プレーヤーノーツレーダー画面とデフォルトスキン定義を追加
- WMII_FHD等のリザルト向けに今回プレイ分スコアのノーツレーダー表示用プロパティを追加
- スクリーンショットファイル名、難易度表レベル表記、TOP10の同一譜面別差分除外に対応
確認:
-
./gradlew.bat test成功 -
./gradlew.bat updateAllSongs後に./gradlew.bat updatePlayerNotesRadarを実行済み
補足:
- Claude Codeレビューは
claudeCLI のセッション上限により実行できませんでした。 -
skin/Luxe_Flat/skin/WMII_FHD配下のスキン変更は ignore のままコミット対象外です。