* refactor(scanner): make tag value splitting position-based
Replaces the ZWSP substitution trick with index-based cutting, in
preparation for artist split exceptions, which need match positions.
* feat(scanner): protect whitelisted names in tag value splitting
Separator matches inside word-bounded exception matches no longer split.
Matching is case-insensitive and longest-first; boundaries are rune-aware.
* feat(scanner): add Scanner.ArtistSplitExceptions config option
* feat(scanner): honor artist split exceptions for participant tags
Applies Scanner.ArtistSplitExceptions to artist, albumartist and role tag
splitting. Generic tags (genre, mood, ...) are unaffected.
* fix(scanner): apply split exceptions when per-tag Split overrides participant tags
Per-tag Tags.<name>.Split makes the generic ingestion path split the tag
before participant mapping runs, bypassing the whitelist. Attach the
exceptions to participant tag mappings (including sort variants) in clean().
* feat(scanner): split performer names and honor split exceptions
Performer pair values were never split; multiple names in one PERFORMER
value stayed a single artist. Split them with the roles separators, using
the same whitelist protection as other participant tags.
* test(scanner): lock MBID ordering for split performer values
* refactor(scanner): consolidate split-exception wiring and drop hot-path lock
ArtistSplitExceptionsRx is called per tag mapping per scanned file across
concurrent goroutines; replace the mutex+joined-key cache with an atomic
pointer compared via slices.Equal. Route all participant call sites through
WithParticipantExceptions and a shared splitParticipantValues helper.
* refactor(scanner): unexport artistSplitExceptionsRx
All external callers go through WithParticipantExceptions, so the accessor
does not need to be part of the model package API.