# Альбомы и Singles: реализованная FSM Riff 1.0.0 включает взвешенную сборку альбомов, персональные и общие Singles, доказательства из имён файлов, владение FILF и удержание недоступных записей. Подписанный FINAL-релиз приложения публикуется вместе с соответствующими исходниками. Singles остаётся классификацией каталога; синтетический жанр не создаётся. Player показывает персональные корзины как `Artist ◇ Singles`, а общую — как `◇ Singles`. Это текстовые подписи в существующих строках. Идентичности, состав, порядок и входные имена A-Z сохранены; это изменение отображения не требует сброса базы медиатеки. ## Область модели Singles — категория представления локальной библиотеки. Она включает короткие релизы, имеющиеся фрагменты альбомов, наборы версий одной или нескольких композиций и записи с явными признаками DJ-сетов. Официальный тип релиза в Интернете не требуется для каждого решения. Модель состоит из двух расширенных конечных автоматов: классификатора `C` и автомата публикации `P`. Данные каталога хранятся в регистрах; поэтому это EFSM, а не автомат с отдельным состоянием на каждый возможный альбом. Автомат доступности ортогонален классификации. Cuetree определяет состав, Indexer публикует согласованный каталог, Player его читает. Модель запускается заново при изменении согласованных фактов, в том числе после IA. Существующие правила идентичности, конкурирующих изданий, копий и порядка позиций продолжают действовать. **Действующее объединение по весам сохраняется целиком.** Состояние `G` вызывает существующую глобальную группировку RIFF-ALBUM-V2: структурные и артистные правила, ограничения изданий, взвешенный остаточный score AG-9 и проверку всех пар при объединении. Порог 65 и веса AG-9 не заменяются предикатами Singles. Сначала эта группировка собирает полный допустимый состав, затем `C` решает, показывать готовую группу отдельным альбомом или внутри корзины. Короткие подгруппы нельзя отправлять в Singles до завершения глобального объединения. Для библиотеки iTunes сохраняются все прежние доказательства из тегов, каталогов и нумерации. Распознавание имени `0 01 …` — дополнительное наблюдение для `G`, когда номер в тегах отсутствует или невалиден; оно не становится обязательным условием группировки. Неполная последовательность имён не отключает существующий score. Если новый предикат Singles не доказан, собранный прежним алгоритмом альбом остаётся альбомом. Изменение касается только классификации и представления результата. [AG-9: действующие веса и ограничения](https://wiki.271.pics/Riff/LATEST/album-grouping-residual-score). ### Нумерация имён проверяется до группировки Полный успешный листинг должен содержать хотя бы два непосредственных аудиофайла с уникальной последовательностью `1..N`: например, `01 …` / `02 …` или iTunes `0 01 …` / `0 02 …`. Неподдерживаемые WMA и аудиокандидаты с ошибкой учитываются как физические соседи, но это не делает их воспроизводимыми. Обложки, текст и плейлисты нейтральны. Вложенные папки, CUE/TOC, неизвестные непрозрачные файлы, ошибки наблюдения и неполный листинг не доказывают полноту. Удерживаемые члены тоже должны образовать последовательность; все пригодные Track должны совпадать с именами, объявленные итоги не должны конфликтовать, содержательные Album не должны различаться. Структура глав запрещает такой вывод. Удерживаемые отсутствующие члены продолжают учитываться; новые файлы, ещё ожидающие метаданных, не создают преждевременного доказательства полноты. Производный номер заменяет лишь отсутствующее или невалидное доказательство порядка: он не записывается в Track или Total Tracks. Папка не придумывает Album или Artist. Если проверка не прошла, прежняя группировка использует остальные доказательства. ## Входы и предикаты Для поколения `k` сначала применяются обычные правила удержания и TTL. Вход `R(k)` содержит все успешно полученные и ещё удерживаемые факты, включая недоступные. Число `N(g)` — количество логических позиций группы после разрешения эквивалентных копий. Это не количество доступных файлов. | Обозначение | Точное значение в реализации | | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `explicit(g)` | Признанная валидная многотрековая CUE/TOC либо самостоятельно озаглавленная встроенная таблица; также удерживаемое явное владение до обычного истечения. | | `chapters(g)` | У одного из базовых MEDIA есть признанная таблица глав без самостоятельного названия альбома. Конкурирующие таблицы проходят существующий explicit-путь. | | `grouped(g)` | Обычная группировка независимо допустила группу по действующим правилам. Отсутствие Album не исправляется именем папки. | | `protected(g)` | Распознанная группа Book/речи, M4B либо потомок стандартного корня Audiobooks; защищённые обычные группы обходят новые предикаты Singles. Явные структуры и FILF идут по более ранним ветвям. | | `short(g)` | `1 <= N(g) <= 3`, все длительности известны и `0 < duration < 600 s`. Неполная нумерация и больший заявленный Total Tracks не мешают. | | `versions(g)` | `4 <= N(g) <= 16`; один непротиворечивый диск; уникальные позиции `1..N`; каждый присутствующий Total Tracks равен `N`; общий содержательный кредит исполнителя; не более трёх осмысленных точных основ и хотя бы один маркер версии. Защищённые номера частей должны совпадать. | | `djset(g)` | Общий содержательный кредит исполнителя и маркер DJ set, DJ mix, continuous mix или mixset в Album; либо у каждого члена есть одновременно жанровые компоненты Electronic/electronica и Live/live recording. | | `candidate(g)` | `short(g) OR versions(g) OR djset(g)`, только после защитных ветвей. | | `owner(g)` | Однозначный содержательный кредит исполнителя, общий для всех позиций: сначала Album Artist, затем Track Artist. Неизвестные/служебные значения не создают владельца. | Все отсутствующие положительные доказательства дают false соответствующего предиката. Неизвестная длительность запрещает именно `short`, а не независимо доказанный `versions`. Противоречие не исправляется большинством голосов. Числа в названиях сохраняют смысл. Длительность поступает из уже выполняемой ограниченной проверки источника Media3. Неизвестная длительность не запрещает поддерживаемое аудио и блокирует только `short`. `djset` — реализованная экспериментальная эвристика метаданных, а не утверждение об официальном типе релиза. Одного Live, большой длительности или имени артиста недостаточно. Списка разрешённых исполнителей и запросов в Интернет при работе нет. ## Классификатор C `C = (Q, Σ, D, δ, E, O)`, где `Q = {E, G, H, C, R, X, A, P, S, Z}`, `Σ = {evaluate, reset}`, `D` — факты и предикаты выше. `E` — начальное состояние. `O = {X, A, P, S, Z}` — результаты: явный альбом, обычный альбом, персональные Singles, общие Singles и отсутствие членов. Буква `P` внутри `C` обозначает персональную корзину, а не автомат публикации. ![FSM: явное владение, обычная группировка, главы, проверка Singles и выбор корзины](https://roadio.link/assets/singles-fsm-ru.30df3096ca60.svg) [Открыть диаграмму отдельно](https://roadio.link/assets/singles-fsm-ru.30df3096ca60.svg). Диаграмма обзорная; таблица ниже задаёт все переходы. В каждом состоянии проверяется первая подходящая строка. Обозначение «иначе» — дополнение всех предыдущих условий этого состояния. | Из | Условие события `evaluate` | В | Действие | | ----------- | ------------------------------------- | ----- | --------------------------------------------------------------------------------------- | | `E` | Нет удерживаемых позиций | `Z` | Не создавать непустую запись; старые пустые проекции живут по собственному TTL. | | `E` | `explicit(g)` | `X` | Сохранить объявленный порядок FILF; исключить соответствующие базовые MEDIA и их копии. | | `E` | Иначе | `G` | Выполнить существующую глобальную группировку базовых MEDIA, включая AG-9 и его веса. | | `G` | Есть главы | `H` | Разрешить принадлежность базового MEDIA, затем расширение глав. | | `G` | `grouped(g)`, без глав | `C` | Передать разрешённые логические позиции. | | `G` | Иначе | `R` | Передать каждую невостребованную позицию отдельно, не изобретая релиз из общих Singles. | | `H` | `grouped(g)` | `A` | Заменить контейнеры упорядоченными FILF; остальные позиции сохранить. | | `H` | Иначе | `X` | Создать явный fallback-альбом таблицы; не отправлять контейнер или главы в Singles. | | `C` | `protected(g)` | `A` | Сохранить обычный альбом. | | `C` | `candidate(g)` | `R` | Перенести группу целиком; сохранить исходные теги и порядок внутри группы. | | `C` | Иначе | `A` | Сохранить обычный альбом. | | `R` | `destination(g)` определён однозначно | `P` | Добавить в персональную корзину, показанную как ` ◇ Singles`. | | `R` | Иначе | `S` | Добавить в общую корзину, показанную как `◇ Singles`. | | `X/A/P/S/Z` | Любое `evaluate` без новых входов | То же | Результат вычисления неизменен. | | Любое | `reset` с новым согласованным входом | `E` | Пересчитать принадлежность по новому снимку. | Последняя строка имеет приоритет для события `reset`. Остальные неоговорённые события дают переход в то же состояние без изменения регистров. Сборщик сначала устанавливает явное владение во всём снимке; один и тот же логический контейнер нельзя пропустить через разные ветви благодаря разным физическим копиям. Это автомат решения о членстве, а не идентификатор каталожной записи. ## Выделение основ и персональные корзины Парсер основ снимает только распознанные завершающие обозначения версии: например, `Song (Radio Edit)`, `Song (Artist Remix)`, `Song - Instrumental`. Результат сравнивается точно после уже существующего канонического сравнения. Новая нормализация и Левенштейн не добавляются. Короткая основа вроде `Go` допустима. Произвольные скобки, номера частей, томов и одинаковый концертный суффикс не доказывают общую композицию. Извлечение основы не меняет фактический Title и не объединяет разные альбомы. Список распознаваемых целых слов задаётся при сборке; английские примеры — mix, remix, edit и instrumental. Шаблонные Track/Untitled/Audio не доказывают общую композицию. Суффикс после тире берётся от последнего разделителя: у `Dream - Radio Day - Club Mix` остаётся основа `Dream - Radio Day`. Языковые признаки выбираются при сборке через `genreLocales` и `catalogVocabulary` в `config/project.json`. В этой версии выбраны английский, испанский, немецкий, русский и португальский. Cuetree получает эти значения от приложения; словарь библиотеки по умолчанию не добавляет языковых доказательств. Роли spoken, live и electronic определяются настроенными идентификаторами GNRE, без отдельного hardcode переводов жанров. Неоднозначные псевдонимы остаются нейтральными. Один словарь используется при сканировании и пересчёте TTL. Его изменение инвалидирует производные метаданные в обычном цикле индексирования. Перед маршрутизацией рассчитывается фиксированная таблица `eligible(artist)` для всего снимка: - Есть хотя бы один независимо собранный музыкальный альбом вне Singles с не менее чем семью логическими позициями, либо с не менее чем четырьмя позициями и известной суммарной длительностью не менее 30 минут. Нужны полная согласованная нумерация, непротиворечивое покрытие дисков и совпадающие присутствующие итоги. Шаблонный Album или противоречивая структура не дают такого основания. - **Или** есть как минимум три независимо выделенные группы `candidate` этого исполнителя. Три трека одного релиза — одна группа, не три. Невостребованные одиночные позиции не создают фиктивные релизы для достижения порога. Условия альтернативны: студийный или полный альбом не обязателен. Учитываются удерживаемые недоступные группы; корзина сама себя не квалифицирует. Маршрутизация не меняет таблицу `eligible` в процессе обхода. Полные компоненты кредита сравниваются по действующим правилам; запятая или амперсанд не являются разрешением разрезать совместный кредит. Неоднозначный владелец ведёт в `S`. Персональная корзина и общая корзина не содержат копии одних и тех же назначенных позиций. ### Детерминированный выбор корзины и стабильные ID `anchors` — владельцы, прошедшие порог независимого полного альбома; `eligible` дополнительно включает владельцев трёх групп-кандидатов. Общие подтверждения — полные наборы компонентов Album Artist либо Track Artist, присутствующие у каждого члена группы. Основной владелец выбирается по общему Album Artist, затем по общему Track Artist. Условия проверяются по порядку: 1. Использовать основного владельца, если он входит в `anchors`. 2. Иначе использовать единственный общий anchor. Несколько общих anchor ведут в общие Singles. 3. Иначе использовать основного владельца, прошедшего `eligible`. 4. Иначе использовать единственный общий подходящий кредит; без однозначного выбора — общие Singles. Так вычисляется `destination(g)` до переноса позиций. Уже невостребованные позиции маршрутизируются по одной и не создают фиктивных релизов; распознанные отдельные книжные источники не создают персональную музыкальную корзину. Выбранная группа-кандидат переносится целиком. Персональный тип `ARTIST_SINGLES` получает имя `: singles` и синтетические компоненты Album Artist. Общий тип `SINGLES` сохраняет имя `[[SINGLES]]`. У каждой корзины свой стабильный случайный ID; теги MDIA остаются фактическими. Опустевшая персональная корзина становится недоступной осиротевшей записью и истекает по обычному TTL. Только пустая общая корзина остаётся живой. Выделившийся из Singles обычный альбом получает идентичность альбома, а не ID корзины. Указанные выше имена хранятся в ALBM. Player выбирает подпись по `AlbumKind`, объединяя полный кредит исполнителей через точку с запятой и пробел перед ромбом. Та же подпись используется в метаданных трека и очереди с контекстом альбома. Имена обычных и явных альбомов, даже похожие на технические, не меняются. Прежние имена по-прежнему задают порядок при равенстве ключей и поиск A-Z. ## Публикация и время жизни Автомат публикации: `Qp = {IDLE, BUILD, VALIDATE, COMMIT}`, начальное состояние `IDLE`. Регистр `accepted` указывает на последний принятый согласованный каталог; `pending` изолирован. События, не допустимые в текущем состоянии, не меняют `accepted`. | Из | Событие / условие | В | Действие | | ----------------------- | ------------------------------ | ---------- | -------------------------------------------------------------------------------- | | `IDLE` | Изменились согласованные входы | `BUILD` | Применить TTL; вычислить `C` для всех групп, затем корзины и связанные каталоги. | | `IDLE` | Входы неизменны | `IDLE` | Нет публикации и расхода TTL. | | `BUILD` | Расчёт завершён | `VALIDATE` | Проверить членство, ссылки, порядок, ID и согласованность полного набора. | | `VALIDATE` | Проверки пройдены | `COMMIT` | Подготовить активацию нового набора. | | `COMMIT` | Атомарная активация успешна | `IDLE` | Заменить `accepted`, зафиксировать новое поколение. | | `BUILD/VALIDATE/COMMIT` | Ошибка или отмена до активации | `IDLE` | Отбросить `pending`, сохранить `accepted`. | TTL измеряется поколениями успешной публикации, не минутами и не количеством запусков сканирования. Изменённые промежуточные публикации также расходуют TTL. Счётчик поколения и удаление фактов фиксируются вместе со снимком; проваленная попытка не старит принятый каталог. Если истечение в поколении публикации удалило исходные факты, состав оставшихся групп классифицируется заново — в том числе при публикации после анализа без нового сканирования. Истечение посторонней записи сохраняет актуальное расширение глав. Автомат доступности `V = {AVAILABLE, OFFLINE}` на каждом обновлении переходит в `AVAILABLE`, если доступна хотя бы одна удерживаемая позиция, иначе в `OFFLINE`. Он не изменяет выход `C`. Доступность альбома — отдельный признак: есть хотя бы одна доступная позиция либо нет ни одной. Полностью недоступная удерживаемая запись не видна в доступном каталоге Player, но её состав продолжает участвовать в классификации и квалификации исполнителя до истечения удержания. ## Проверяемые сценарии и инварианты | Событие | Ожидаемый результат | | ------------------------------------------------------------- | ----------------------------------------------------------------------------------- | | Впервые найдены две короткие позиции из заявленных двенадцати | Singles, даже при номерах 4/12 и 9/12. | | К этим двум добавились остальные десять разных композиций | Вся группа становится отдельным альбомом, если другие признаки Singles отсутствуют. | | Из известных двенадцати временно доступны только две | Альбом остаётся альбомом до TTL отсутствующих позиций. | | Десять позиций действительно истекли по TTL | Остаток пересчитывается и может перейти в Singles. | | До TTL недоступны все двенадцать | Состав удерживается; доступной карточки нет. | | Один контейнер содержит десять валидных глав | В ALBM представлены FILF; это не однотрековый Singles по числу файлов. | | Явная таблица содержит два фрагмента | Сохраняется явный альбом, правило короткой группы не применяется. | | У исполнителя только три подтверждённых сета | Возможна персональная корзина без студийного альбома. | | Построение нового каталога отменено | Предыдущий согласованный каталог остаётся принятым. | Каждая обычная назначенная позиция получает один выход `A/P/S`; явные таблицы сохраняют объявленные повторы и независимые варианты. Глобальная дедупликация по Media.ID запрещена. Контейнер никогда не публикуется вместе со своими FILF в альбомах или Singles. Изменение доступности само по себе не сокращает удерживаемый состав. ID корзины не превращается в ID выделенного из неё альбома. Владельцы реализованных правил: [классификация Singles](https://wiki.271.pics/Cuetree/singles), [нумерация имён](https://wiki.271.pics/Cuetree/filename-evidence), [Cuetree catalogs](https://wiki.271.pics/Cuetree/catalogs), [группировка](https://wiki.271.pics/Riff/LATEST/album-grouping) и [жизненный цикл](https://wiki.271.pics/Riff/LATEST/indexer-library-lifecycle). Статья объясняет реализованную механику; нормативными остаются соответствующие контракты. При исходной проверке Singles прошли 147 core- и шесть CLI-тестов, девять изолированных Android-тестов допуска источника, Android/JVM-проверки сборки и независимая сборка из исходников. Проверены состав, основы версий, порядок из имён, выделение альбома, TTL, FILF и границы ошибок/отмены. Эти результаты не означают завершение анализа всех медиатек или проверку живого автомобильного хоста. Аудит 2026-10-18 добавил регрессии языковых признаков, имён файлов и границ отказа. До подготовки релиза прошли 661 JVM-тест, 15 Python-тестов и 27 изолированных тестов хранения/восстановления на HU. Это результаты конкретных прогонов, а не утверждение о проверке всех медиатек и устройств.