# WishAudit — модуль «Дележ добычи» (рандомайзер)

Версия аддона поднята до **0.10.0**. Модуль добавлен четырьмя новыми файлами,
существующий код не переписан.

## Что добавилось

| Файл | Назначение |
|---|---|
| `Roll.lua` | протокол, общие часы, детерминированный жребий |
| `RollUI.lua` | барабан, отсчёт 3-2-1, финальный экран |
| `RollSetup.lua` | окно РЛа: предмет и претенденты |
| `RollWheel.lua` | вторая забава: «Колесо фортуны» |
| `RollHook.lua` | кнопка «Рандомайзер» и команда `/wish roll` |
| `RollOptions.lua` | вкладка «Roll» в окне настроек |
| `Media/Wheel/` | клинья секторов колеса (TGA) |
| `Media/Fonts/` | Forum-Regular.ttf, JetBrainsMono-Regular.ttf + лицензии OFL |

Правки в существующих файлах — только две:

* `WishAudit.toc` — четыре строки в конец списка и `## Version: 0.10.0`;
* `Core.lua` — одна строка: `addon.VERSION = "0.10.0"`.

Чтобы полностью отключить модуль, достаточно убрать четыре строки из `.toc` —
остальной аддон этого не заметит.

## Как пользоваться

**Запуск (только РЛ):** кнопка **Рандомайзер** в окне голосования RCLootCouncil
либо `/wish roll` (можно с shift-кликнутой ссылкой на предмет:
`/wish roll [Клинок Испытаний]`).

Кнопка и команда доступны, если включён **РЛ-мод** (настройки → Общие) *и* вы
лидер, помощник или мастер лута.

**В окне РЛа:**

* предмет подтягивается из текущей сессии голосования автоматически; можно также
  вставить ссылку в поле, перетащить предмет мышью или shift-кликнуть по нему,
  когда поле ссылки в фокусе;
* претенденты — весь ростер группы с классовыми цветами; кнопка
  **«Из голосования»** отмечает тех, кто заявился на предмет и не спасовал;
* три переключателя: звук, зерно из `/roll`, объявление итога в чат.

**Результат** видят все, у кого установлен WishAudit нужной версии. Проверить,
кто отстал, — `/wish v`.

## Как устроена синхронизация

По сети уходит **один** пакет (`wr_start`, префикс `WISHROLL`, транспорт —
`Services.Comms` самого RCLootCouncil): предмет, список имён, зерно и момент
старта. Победитель **не передаётся**.

Каждый клиент выводит из зерна детерминированным генератором:

1. порядок круга (тасовка Фишера-Йетса),
2. победителя,
3. длительность вращения,
4. дрожание остановки,
5. поворот круга, ставящий победителя на нужную позицию.

Порядок этих обращений и число холостых тактов прогрева — часть протокола.
Менять их нельзя: клиенты разных версий разойдутся в победителе.

Момент старта задан по **серверным часам**. Клиент ловит смену секунды
`GetServerTime()` и привязывает её к точному `GetTime()` — получается общая для
всей группы шкала с погрешностью в один кадр. Положение барабана в любой момент
— чистая функция от «сейчас минус старт», поэтому пакет, доставленный на
полсекунды позже, ничего не сдвигает: клиент входит в анимацию с середины и
приходит к финалу вместе со всеми. Опоздавший больше чем на весь ролл сразу
видит итог. Если серверные часы у кого-то расходятся больше чем на минуту,
клиент им не верит и крутит от момента получения.

## Устройство ленты

Лента — **колесо**. Имена тасуются один раз и дальше идут по кругу этим же
порядком до конца ленты: `3 2 5 1 4 · 3 2 5 1 4 · 3 2 5 1 4 · ...` Каждое имя
встречается на ленте ровно одинаковое число раз — никто не мелькает чаще
соседа.

Победитель попадает на фиксированную позицию (`total - 6`) поворотом всего
круга. Поворот случайной перестановки остаётся случайной перестановкой, поэтому
тасовка от этого не портится, а путь барабана остаётся одинаковой длины при
любом числе претендентов. Длина ленты — целое число полных кругов (56-80
табличек).

## Проверяемость жребия

По умолчанию зерно берётся из серверного `/roll 1-1000000`. Его результат
попадает в системный чат — его видят все, включая тех, у кого нет аддона, и по
нему любой может пересчитать исход. Если система не ответила за три секунды
(бой, ограничения аддонов), берётся локальное зерно, и в финале это указано.

## Замечание о генераторе

Генератор — Парк-Миллер (`s = s * 16807 mod 2147483647`), целочисленный, без
плавающей арифметики в состоянии: результат побитово одинаков на любой машине.
`math.random` не годится — он засеян по-своему у каждого клиента.

**Прогрев обязателен.** Первый выход этого генератора почти пропорционален
зерну. Без холостых тактов на 300 тысяч прогонов раскладка перекошена: при
четырёх претендентах последний получал 23.3% вместо 25%, а на последовательных
зёрнах — 21.3%. Шестнадцать тактов прогрева (`RNG_WARMUP` в `Roll.lua`) убирают
перекос полностью — хи-квадрат в норме для любого числа претендентов от 2 до 24.

## Чего проверить не удалось

Всё, что не требует игрового клиента, обкатано на заглушке: загрузка файлов,
детерминизм жребия, равномерность, построение обоих окон, все фазы анимации
покадрово и вход «после финала». **Живой рендер не проверялся** — стенд не
рисует. Первый запуск разумно сделать вдвоём в группе: сразу станет видно,
если где-то промахнулся якорь или текстура.

Точки, за которыми стоит присмотреть в первый раз:

* клык (вертикальная латунная черта в центре барабана) — должен быть **над**
  лентой; наконечники используют `Interface\ChatFrame\ChatFrameExpandArrow`;
* затемнение по краям окна барабана — рисуется градиентом, при неудаче просто
  не показывается, барабан работает;
* если шрифты не подхватились, текст останется игровым — модуль не падает.

## Колесо фортуны

Вторая забава. В окне РЛа переключатель вверху: **Барабан** / **Колесо фортуны**.

1. РЛ выбирает предмет и жмёт **«Начать сбор»**.
2. Желающие кидают обычный `/roll` (1-100). Считается **первый** бросок от
   каждого; посторонние вне группы не учитываются.
3. Круг режется на сектора соразмерно броскам, в том порядке, в каком люди
   бросали. Пример из четырёх человек с бросками 10, 30, 25, 35 даёт сектора
   36°, 108°, 90°, 126° — ровно 10%, 30%, 25%, 35%.
4. **«Крутить колесо»** — дальше всё как у барабана: одно зерно, общие
   серверные часы, у всех одинаковый исход в один и тот же миг.

Круг режется на 360 целых градусов методом наибольших остатков, и победитель
выбирается **ровно по нарисованным градусам**, а не по сырым броскам: стрелка
всегда встаёт там, где глаз видит нужный сектор. Плата — округление доли не
более чем на полградуса (около 0.14%).

Секторы разделены тёмными **спицами**, у каждого свой **номер** прямо на круге
(если сектор шире 9°), и тот же номер стоит в легенде. Цвета соседей разводятся
**по надобности**, а не через один: два друида подряд получат заметно разные
оттенки. Круг замкнут, поэтому последний сектор сверяется с первым — простое
чередование ломалось на этом стыке при нечётном числе участников.

Легенда разбита на две колонки: чей сектор начинается в первых 180° по часовой
(правая половина круга) — тот справа, остальные слева. Ники обрезаются до 11
знаков, чтобы левый столбец не наползал на круг.

Крутится **стрелка, а не круг**: повернуть фрейм в WoW нельзя, а вращение
самого круга означало бы сотни поворотов текстур каждый кадр. Заодно имена в
легенде остаются читаемыми всё время вращения.

## Вкладка настроек «Roll»

Личные настройки каждого игрока — по сети не ходят, на исход не влияют.

* **Всегда** — показывать анимацию / показывать окно победителя. Общий
  рубильник: можно отключить вращение, но оставить итоговое окно.
* **Когда меня нет среди претендентов** — то же самое отдельно для чужих
  разролов, чтобы окно не лезло на экран, когда делят вещь не для вас.
* **Прочее** — звук, зерно из серверного `/roll`, объявление в чат, отладка и
  кнопка «Забыть закрытые роллы».

Крестик в углу окна закрывает ролл до конца сессии: ни анимация, ни окно
победителя больше не появятся, итог придёт объявлением в чат.

## Ограничения

* до 24 претендентов в одном ролле (лишние отсекаются);
* в окне РЛа помещается 30 строк ростера без прокрутки;
* переброс не исключает прошлого победителя — броски независимы.
