Problem
Mapper v2.1 currently prevents window-level leakage by grouping windows from the same beatmap/chart into a single train/eval side, but it does not guarantee song/audio-level disjointness.
run_mapper_v2_1_phase_b_training() falls back to split_train_eval_dataset() when eval_index_path is not supplied. The split helper derives group identity via _map_identity_from_metadata(), which considers fields such as beatmap_path, map_path, beatmap_id, etc., but not audio_path / audio content identity.
This means different difficulties/charts that share the same source audio can be split across train and eval:
same audio/song
├── Easy.osu -> train
├── Hard.osu -> eval
└── Insane.osu -> train
For Mapper v2.1 this is especially problematic because include_full_song_context: true is used in the current training preset. The eval chart may therefore be unseen while its full audio has already appeared during training through another difficulty.
The current evaluation should consequently be interpreted as roughly unseen chart / potentially seen song, not a strict unseen song evaluation.
There is a second related gap when an explicit eval_index_path is supplied: train and eval datasets are constructed separately, with no overlap assertion that their beatmaps or audio identities are disjoint.
Relevant code
src/pulsefield_model/training/mapper_v2_1.py
src/pulsefield_model/training/common.py
split_train_eval_dataset()
_map_identity_index_groups()
_map_identity_from_metadata()
src/pulsefield_model/data/mapper_sparse_windows_v2_1.py
- sample metadata already exposes
audio_path
tests/training/test_common_split.py
- currently tests whole-beatmap grouping only; fixtures give every beatmap a distinct audio path
Proposed behavior
Make the atomic split unit the underlying audio/song identity, so every chart/difficulty backed by the same audio is assigned entirely to either train or eval.
Preferred identity hierarchy:
- Stable audio content hash (e.g. SHA-256) stored in the dataset/index, if available.
- Canonicalized
audio_path as a practical fallback.
- Only fall back to chart identity when no audio identity exists.
beatmap_set_id alone is weaker than audio identity: separate sets can theoretically contain identical audio, and a set may contain unusual layouts that should not define the actual leakage boundary.
Difficulty stratification can remain, but selection should operate on audio groups rather than individual beatmap groups.
Acceptance criteria
Why this matters
A strict audio-disjoint split is necessary if Mapper eval metrics are intended to measure generalization to genuinely unseen songs rather than only unseen chart annotations for songs the model has already heard.
Problem
Mapper v2.1 currently prevents window-level leakage by grouping windows from the same beatmap/chart into a single train/eval side, but it does not guarantee song/audio-level disjointness.
run_mapper_v2_1_phase_b_training()falls back tosplit_train_eval_dataset()wheneval_index_pathis not supplied. The split helper derives group identity via_map_identity_from_metadata(), which considers fields such asbeatmap_path,map_path,beatmap_id, etc., but notaudio_path/ audio content identity.This means different difficulties/charts that share the same source audio can be split across train and eval:
For Mapper v2.1 this is especially problematic because
include_full_song_context: trueis used in the current training preset. The eval chart may therefore be unseen while its full audio has already appeared during training through another difficulty.The current evaluation should consequently be interpreted as roughly unseen chart / potentially seen song, not a strict unseen song evaluation.
There is a second related gap when an explicit
eval_index_pathis supplied: train and eval datasets are constructed separately, with no overlap assertion that their beatmaps or audio identities are disjoint.Relevant code
src/pulsefield_model/training/mapper_v2_1.pysrc/pulsefield_model/training/common.pysplit_train_eval_dataset()_map_identity_index_groups()_map_identity_from_metadata()src/pulsefield_model/data/mapper_sparse_windows_v2_1.pyaudio_pathtests/training/test_common_split.pyProposed behavior
Make the atomic split unit the underlying audio/song identity, so every chart/difficulty backed by the same audio is assigned entirely to either train or eval.
Preferred identity hierarchy:
audio_pathas a practical fallback.beatmap_set_idalone is weaker than audio identity: separate sets can theoretically contain identical audio, and a set may contain unusual layouts that should not define the actual leakage boundary.Difficulty stratification can remain, but selection should operate on audio groups rather than individual beatmap groups.
Acceptance criteria
beatmap_pathvalues sharing the sameaudio_path; assert they cannot be split across train/eval.eval_index_pathmode that detects shared audio identities between train and eval and fails loudly (or explicitly supports an opt-out if needed).Why this matters
A strict audio-disjoint split is necessary if Mapper eval metrics are intended to measure generalization to genuinely unseen songs rather than only unseen chart annotations for songs the model has already heard.