Перейти к содержимому

Датасеты

Если вы делаете что-то поверх наших датасетов — редактор, импортёр, конвертер из своего формата, ИИ-генератор контента — начните отсюда. Страница самодостаточна: всё, что нужно, либо здесь, либо по публичному URL.

Датасет — именованный контейнер с механикой: классы, подклассы, виды, линии, предыстории, черты. Концептуально сорсбук: одна группа контента, один источник, одна лицензия. Игрок подключает наборы к себе, визард мержит подключённые и строит по ним персонажа.

Внутри всё устроено на грантах: сущность не имеет полей «навыки» и «бонусы», она имеет массив типизированных грантов — «дай владение навыком», «дай +1 к Ловкости», «предложи выбрать один из…». Словарь грантов закрыт — 28 типов. Вы не пишете DSL и не исполняете чужой код, вы наполняете типизированный JSON.

Статические файлы, забираются curl’ом без ключей и регистрации.

Артефакт URL
Схема всего датасета https://longstoryshort.app/schema/v1/wizard-dataset.json
Схемы по сущностям …/schema/v1/dataset-{class,subclass,race,subrace,background,feat}.json
Схема гранта …/schema/v1/grant.json
SRD 5.1 https://longstoryshort.app/datasets/srd-2014.json (~209 KB)
SRD 5.2 https://longstoryshort.app/datasets/srd-2024.json (~274 KB)
Слаги заклинаний SRD 5.1 https://longstoryshort.app/datasets/srd-spell-slugs-2014.json (318 записей)
Слаги заклинаний SRD 5.2 https://longstoryshort.app/datasets/srd-spell-slugs-2024.json (339 записей)

JSON Schema — draft-07. Схема генерируется из тех же исходников, по которым работает приложение, и включает description на каждом типе гранта: она же и есть справочник по полям, отдельной таблицы полей нет и не нужно.

SRD-наборы — не игрушечные примеры: это ровно те данные, на которых работает продакшен. Вместе — 24 класса и 24 подкласса на 20 уровней, 18 видов с линиями, черты, счётчики, развилки снаряжения.

Правите JSON руками — одна строка даёт автокомплит и подсветку ошибок в VS Code, JetBrains и любом редакторе с поддержкой JSON Schema:

{
"$schema": "https://longstoryshort.app/schema/v1/wizard-dataset.json",
"id": "my-homebrew",
"name": "Мой хоумбрю",
"system": "dnd_5",
"edition": "2024",
"author": "Вы",
"license": "CC-BY-4.0",
"races": []
}

Пишете код — валидируйте чем угодно:

const validate = new Ajv().compile(await (await fetch(SCHEMA_URL)).json());
if (!validate(dataset)) console.error(validate.errors);

Хотите понять формат — откройте srd-2024.json и найдите варвара.

28 типов, дискриминация по полю type. Точные поля каждого — в grant.json.

Канал Типы
Владения skill-fixed, skill-choice, expertise-choice, tool-fixed, tool-choice, language-fixed, language-choice, armor-prof, weapon-prof, saving-throw
Числа bonus, asi-fixed, asi-flexible, asi-pool, hp-die, speed, size
Контент trait, resource, feat
Заклинания spellcasting, spell-fixed, spell-choice
Снаряжение equipment-fixed, equipment-choice, gold, gold-dice
Развилка pick-one

Заклинания — три разных вопроса, три гранта. spellcasting объявляет, что сущность кастует, и как (характеристика, прогрессия, из чьего списка — spellList, когда он отличается от id класса, как у гибридов вроде Мистического рыцаря). spell-fixed выдаёт одно именное заклинание по слагу — всегда подготовленное, вне лимита. spell-choice предлагает игроку выбрать count заклинаний заданного круга (0 — заговоры) из списка spellList. У spell-fixed/spell-choice общие поля выдачи: ability (характеристика, пусто — наследует листовую), uses (count/countExpr + per: 'long-rest' | 'short-rest'

  • shortRestRegain — свой счётчик заклинания, а не ячейка), withSlots (можно ли поверх счётчика заплатить ещё и ячейкой), alwaysPrepared (сегодня выразим только как true).

Счётчик заклинания — это Ресурс, просто заведённый другим каналом, и поля восстановления у него те же. per говорит, какой отдых его восстанавливает; shortRestRegain — сколько возвращает КОРОТКИЙ отдых, формулой над теми же переменными (1, [PROF], ceil([LVL]/2)). Без него короткий отдых наполняет счётчик целиком, и шаблон «одно применение на коротком отдыхе, все на длинном» приходилось врать в одну из сторон. Поле осмысленно только при per: 'short-rest' — при 'long-rest' его никто не читает, и линт об этом предупредит.

Ячейки заклинаний растут из progression. Это не справочное поле: персонаж, собранный визардом, получает ячейки сам — по классу, уровню и семейству таблиц. Их четыре: full (бард, жрец, друид, чародей, волшебник), half (паладин, следопыт), third (Мистический рыцарь, Арканный ловкач) и pact (колдун — свой пул на листе). Переписывать числа в персонажа не надо: игрок может поправить любое, но хранится разница с вашей таблицей, а не итог, и левелап её не затирает.

Если прогрессия вашего класса не совпадает ни с одной из четырёх — объявите собственную таблицу, slotsByLevel. Двадцать одна строка (индекс = уровень персонажа, нулевая — заглушка), в строке девять чисел по кругам 1…9; строка короче девяти дополняется нулями. Её числа побеждают встроенные, но progression рядом не отменяется: тип продолжает отвечать за то, в какой пул едут ячейки и с каким весом класс сложится в мультиклассе. Уровень выше последней строки значит «ячеек нет», а не «как на последней», — поэтому короткая таблица это ошибка, а не предупреждение. Классу, чья таблица совпадает со встроенной, поле заводить не надо: скопированные 20×9 чисел — ровно то место, где ваши данные разойдутся с правилами при следующей правке.

Ячейки к тому же стали целями системы бонусов — spellSlot.1spellSlot.9 и pactSlot.1pactSlot.9 (полный список таргетов, как всегда, в grant.json). Предмет или черта, дающие лишнюю ячейку, выражаются обычным грантом bonus.

Свой класс-заклинатель: spellList обязателен. Список заклинаний резолвится по id класса, а собственного списка заклинаний у вашего класса в базе нет — без этого поля игрок получит ячейки и пустой список, из которого нечего выбрать, без единой ошибки. Укажите, чей список класс использует: "cleric", "wizard", любой id класса. То же и у подкласса-гибрида вроде Мистического рыцаря: у него classId: "fighter", а spellList: "wizard".

«Сколько можно взять» — отдельные поля, и без них счётчика не будет. cantripsByLevel и knownByLevel — таблицы по уровням (индекс = уровень персонажа, нулевой элемент — заглушка); preparedFormula — там, где подготовленные считаются формулой, а не таблицей; bookAtFirst/bookPerLevel — книга волшебника, отдельный счёт от подготовленных. Отсутствие числа значит «не показываем», а не «ноль»: лист не станет угадывать лимит за вас.

У формулы три значения, и половина уровня в них округляется по-разному:

Значение Кто так считает На 1 уровне при модификаторе +3
mod+level жрец, друид, волшебник 4
mod+half-level паладин, следопыт — половина вниз 3
mod+half-level-up половина вверх 4

Разница не косметическая: на каждом нечётном уровне это целое заклинание. Общее у всех трёх — минимум одно, иначе кастер с отрицательным модификатором не смог бы подготовить ничего.

Слаг — не то, что вы придумаете. spell-fixed/выбранное игроком заклинание резолвятся по slug — слагифицированному английскому имени (hellish-rebuke), которое должно совпасть с тем, что реально лежит в базе заклинаний Long Story Short. Слаг ссылается на заклинание вне самого датасета — свой корпус заклинаний вы поставляете отдельным файлом (импорт заклинаний — отдельный путь от импорта датасета), а датасет лишь называет слаг. Ссылка на SRD-заклинание резолвится всегда — файлы слагов из таблицы выше говорят, какие это. Ссылка на НЕ-SRD заклинание резолвится только у игрока, который уже загрузил заклинание с этим слагом (свой хоумбрю или файл заклинаний, который вы выложили вместе с датасетом) — это не ошибка датасета, а нормальное «пока нет».

Сам линт про это не знает — у него нет контекста игрока, и он остаётся чистой функцией без сети (это же используется как гейт при сборке бандла и в тестах). Но при подключении датасета через визард на нашем сайте предупреждение о неизвестном слаге автоматически перепроверяется против вашей собственной коллекции заклинаний и пропадает, если слаг у вас уже есть — типичный сценарий при тестировании своего датасета вместе со своим файлом заклинаний.

pick-one вкладывает гранты в свои options — обходя датасет, рекурсируйте внутрь, иначе половина контента пройдёт мимо вашей валидации.

bonus бьёт по таргету. Схема содержит закрытый enum допустимых таргетов — в нём только те, которые лист реально читает. Всё остальное отклоняется наравне с опечаткой, и это то поведение, которое вам нужно: бонус на неподдержанный таргет так же невидим, как prof.skill.stelth. Открытые семейства, где ключ приходит из данных персонажа (weapon.<любое>.attack), выражены через pattern и проходят.

Схема проверяет форму, а не смысл. Валидный по схеме датасет всё ещё может молча ничего не делать — потому что оставшиеся правила межполевые и межсущностные, а JSON Schema так не умеет.

Схема поймает Схема НЕ поймает
Опечатку в имени поля или лишнее поле Всё из таблицы ниже
Неизвестный тип гранта
Опечатку в таргете бонуса или ключе навыка
Таргет, который лист не читает
Отсутствие обязательного поля, неверный тип значения

Проверки, которые надо сделать самим. Ровно этот список гоняет наш импортёр; error блокирует импорт, warning пускает — но почти каждый warning означает «грант записан и не сделает ничего»:

Проверка Что ломается
bonus несёт ровно одно из value | expr error
expr-формула вычисляется error
Ключи навыков, характеристик, брони и оружия — из известных error
Грант выбора предлагает хотя бы один слот (count > 0) error
id уникален внутри коллекции error
classId / raceId / featId ведёт в существующую сущность error
Именованный pick-one лежит там, где его вообще раскрывают error
resource несёт ровно одно из max | maxExpr; формулы вычисляются error
Лицензия — из белого списка (см. «Жёсткие правила») error
trait несёт params и прозу — текст потеряется, канал не тот warning
Формула броска у trait парсится warning
pairId счётчика указывает на существующую черту (или явный null) warning
Счётчик стоит там, где его никто не читает warning
Тип гранта реален, но канала-потребителя у него нет warning
Открытый фит-слот, под который нет ни одного кандидата warning
spell-choice предлагает хотя бы одно заклинание (count > 0), круг задан, spellList не пуст error
Два spell-choice одного круга на одной сущности — пики склеятся в один слот error
spell-fixed/spell-choice: uses без count и без countExpr, или сразу оба error
uses.shortRestRegain не вычисляется в число на каком-то уровне — короткий отдых молча восстановит счётчик целиком error
uses.shortRestRegain при per: 'long-rest' — поле не сработает warning
spell-fixed: слаг не входит в наш публичный список SRD-слагов (см. таблицу выше) warning
spellcasting: slotsByLevel — не массив строк по уровням, или короче 21 записи, или в строке не целое неотрицательное число error
spellcasting: slotsByLevel пуста, или строка длиннее девяти кругов warning

Ссылки между наборами легальны. Хоумбрю-книга подклассов вправе ссылаться classId на SRD-класс, которого в ней самой нет. Ваш валидатор должен резолвить ссылки не только внутри проверяемого файла, но и по всем подключённым наборам, иначе пометит битой каждую такую ссылку.

Готового пакета с этими проверками мы пока не отдаём — реализуйте у себя.

id сущности — внешний ключ, а не название. По нему резолвятся ссылки и привязываются выборы игрока. Переименовали id в опубликованном наборе — сломали чужие листы. Слаг английского названия, один раз и навсегда.

Лицензия обязательна и проверяется. Пустое поле отклоняет импорт так же, как неподходящее. Проходят только CC0, CC-BY и CC-BY-SA с версией (CC-BY-4.0, не CC-BY). OGL, MIT, свободный текст, любые NC/ND-варианты — нет. Это касается и вашего собственного оригинального контента.

Правка датасета ретроактивно меняет уже собранных персонажей. Поле version есть, но импорт его не читает, и защиты с нашей стороны пока нет. Ваше разделение «черновик / опубликовано» — единственный барьер. v1 в URL схемы — про версию формата, не про версию контента.

Публиковать датасет пока некуда. Реестра сообщества нет; подключение идёт через импорт JSON-файла.

Тексты черт лежат в description у trait-гранта. Это единственный канал; файлы самодостаточные, скачали — и у вас полный набор с текстами. Авторьте так же.

Всё описано. SRD 5.1 — 183 черты, SRD 5.2 — 232 — все с текстом.

Перед публикацией оба набора прогоняются всеми проверками из раздела выше, и при любой находке файл не выкладывается — так что как эталон им можно доверять.

Валидатор в своём пайплайне. Самое дешёвое и самое полезное: схема — в CI, проверки из раздела выше — рядом.

Визуальный редактор. Схема даёт формы и типы полей почти даром. Но enum таргетов — не украшение: никогда не давайте автору вводить таргет строкой, стройте пикер из списка в схеме.

Генерация датасетов моделью. Артефакты складываются в связку:

  1. По-сущностные схемы — не для удобства, а по необходимости. Полная схема — union из 28 вариантов с вложенностью в несколько уровней; для structured output это плохо. Генерируйте один класс за вызов против dataset-class.json — дешевле и заметно надёжнее.
  2. SRD как few-shot. Модель учится формату на примерах существенно лучше, чем на схеме, а у вас их 24 класса и 18 видов — эталонных, не выдуманных.
  3. Схема закрывает пространство выбора. Закрытый enum таргетов против свободной строки — это разница между «модель попадает» и «модель придумывает prof.skill.stelth».
  4. Проверки замыкают цикл. Сгенерировали → провалидировали → прогнали проверки → вернули сообщение в модель → перегенерировали. Без четвёртого шага вы получите датасеты, которые проходят валидацию и молча не работают.

Конвертер из своего формата. SRD-наборы — ваш golden file: гоняйте конвертацию в обратную сторону и сверяйтесь.