Feature Specification: Заклинания и свитки — спелл-экономика (spec-059)
Feature Branch: claude/spec-059-build (восстановлено из
claude/spec-059-scrolls, коммит b51e166; та ветка отставала от main на 8)
Created: 2026-07-10
Status: 📗 Specify — правила от Andrey (2026-07-10, чат). Два этапа.
Бриф (Andrey, verbatim-суть)
Новая функция — свитки. Два этапа:
- Парсер с dnd.su (прецедент — предметы, spec-018): выкачать ВСЕ заклинания https://dnd.su/spells/ (никакого homebrew), одновременно ДВЕ версии — основную базовую редакцию и правила 2024 года. В разделе spell (это НОДА) версия — переключателем. Не у всех спеллов есть версия 2024. Итог: база данных всех спеллов.
- Правила написания свитков — похожи на крафт, но вложения считаются иначе. Проверки «заклинание в списке класса персонажа» НЕТ — только гейт уровня: привязка к уровню партии (сейчас 9-й → максимум 5-й уровень заклинаний). Можно писать один свиток вместе. День = 8 часов, рабочая неделя = 40 часов.
Таблица написания (канон Andrey)
| Уровень | Время | Часы (8ч/день, 40ч/нед) | Цена, зм |
|---|---|---|---|
| Заговор | 1 день | 8 | 15 |
| 1 | 1 день | 8 | 25 |
| 2 | 3 дня | 24 | 250 |
| 3 | 1 рабочая неделя | 40 | 500 |
| 4 | 2 недели | 80 | 2 500 |
| 5 | 4 недели | 160 | 5 000 |
| 6 | 8 недель | 320 | 15 000 |
| 7 | 16 недель | 640 | 25 000 |
| 8 | 32 недели | 1 280 | 50 000 |
| 9 | 48 недель | 1 920 | 250 000 |
Все числа — ДМ-настройки (scribe_settings в campaigns.settings, правило
AGENTS.md; паттерн craft_settings). Часы — производные от дней/недель, но
хранится итоговое поле часов (день/неделя-конвертация тоже настройка).
Производные и дефолты (Claude; помечать при несогласии)
- Гейт уровня заклинания:
maxSpellLevel = min(9, ceil(party_level / 2))— стандартная full-caster прогрессия (ур. 9 → 5-й круг ✓, 17 → 9-й ✓). Заговоры доступны всегда. Party_level — с ноды петли (spec-056). - Совместное письмо = писцы делят ЧАСЫ (как крафтеры в 056): форма —
участники + часы per-PC (дефолт поровну),
Σ(часов) ≥ таблица[уровень]. Отличие от крафта: вложение — ЧАСЫ по фиксированной таблице, а не зм/час×БМ; деньги — фиксированная цена таблицы. - Деньги: цена списывается с общака (канон runCraft/runExpedition); свиток → в общак ИЛИ конкретному PC (выбор, как крафт).
- Свиток — предмет каталога: find-or-create «Свиток: <Имя> (N ур.)»,
новая item-категория
scroll(«Свиток»), связь со спеллом (item_attributes-поле или nodes.fields.spell_node_id — решить в Plan; прецедент schema_for_node_id). Расходник по сути. - День записи в леджер: QUICK_ACTION_DAY-конвенция не подходит (у письма есть срок) — как в крафте: день+старт опциональны, wall-clock = max(hours_i) — просто лог, не гейт.
Этап 1 — база заклинаний (нода spell)
- node_type
spell(новый; миграция + вики-рендер /tg + десктоп-нода). - Поля спелла: level (0–9), school, время накладывания, дистанция, компоненты (В/С/М + материал), длительность, классы, источник, markdown 2014 и markdown 2024 (nullable) — переключатель редакции на экране ноды (десктоп + /tg вики). Точная раскладка полей — по фактам разведки dnd.su (какие поля реально есть → см. dndsu-recon.md).
- Скрапер
scripts/spells-dndsu-codegen.tsпо образцуitems-dndsu-codegen.ts(кэш HTML, парс, codegen сид-миграций per-campaign). Без homebrew (фильтр по официальным источникам). - Сид для новых кампаний — тем же путём, что предметы 018 (проверить в разведке: как 018 сидит новые кампании).
Этап 2 — механика
scribe_settings(паттерн craft_settings): таблица уровень → {hours, costGp}, hoursPerDay=8, hoursPerWeek=40, формула гейта (maxSpellLevel) — формулу не настраиваем, это правило; таблицу — настраиваем.runScribe(app/actions/scribe.ts, канон runCraft): гейты — членство → party_level задан → уровень спелла ≤ maxSpellLevel(party_level) → Σ(часов участников) ≥ норма → покрытие общака деньгами → expense-строка (общак) + item-строка свитка (получателю) одним батчем + scribe_runs лог + событие ленты'craft'с mode'scribe'(или отдельный тип — решить в Plan по чистоте union).- /tg UI: вход из таба 🏰 Партия (рядом с Крафтом): выбор заклинания (поиск по базе спеллов, фильтр по доступному уровню) → писцы + часы (дефолт поровну) → день/старт (опц.) → получатель → превью «Свиток: Огненный шар (3 ур.) · 40 ч · −500 зм» → сабмит. Sheet-паттерн R2.
Этап 3 — Переподготовка заклинания (глагол; НАША механика)
Происхождение: house rule — в D&D такого нет, правила определяем мы. Отдельный самостоятельный глагол, не часть свитков и не связан с копированием.
- Мгновенно. Цена =
50 зм × уровень НОВОГО заклинания(50 — настройкаspell_settings.reprepGpPerLevel). Меняет одно известное заклинание на другое. Class-agnostic (любой заклинатель, не только волшебник). - Не трекаем «известные заклинания» (движок 045/лист 022 не построен) → v1 = экономическое действие по модели доверия: пикер НОВОГО заклинания из базы (этап 1, для уровня → цены) + опц. старое (нарратив) → списание + событие ленты «🔄 Переподготовка: <старое> → <новое> (N ур.)». Без энфорса состояния.
- Новый глагол (spec-058 хаб действий / спелл-контекст).
- Дефолты (форки — уточнить): заговор (ур.0) → 0 зм = бесплатно (дефолт);
деньги — с кошелька PC (личная активность, дефолт) vs общак; гейт уровня
партии — да (как свитки,
≤ maxSpellLevel(party_level)).
Этап 4 — Копирование заклинаний в книгу (глагол; БАЗОВЫЕ правила D&D)
Происхождение: стандартные правила D&D для ВОЛШЕБНИКОВ (классовая фича, PHB «Your Spellbook» + DMG про свитки). Следовать RAW дословно, не улучшать. Class-flavour: волшебник (гейт класса не энфорсим — модель доверия, но в текстах/иконках это «книга заклинаний волшебника»). Отдельный глагол.
- Два глагола: свиток → книга и книга → книга.
- Стандартные числа RAW (настройки
spell_settings.copy*):50 зм × уровень+2 ч × уровень. - Свиток → книга: списать свиток из инвентаря PC (item-out −1, канон disassemble/sellPcItem) — свиток уничтожается (правило DMG) + деньги. Заклинание и его уровень берутся из свитка.
- Книга → книга (у другого волшебника): выбираешь ИСТОЧНИК — PC-волшебника
(«у кого переписываешь») + заклинание из базы («что переписываешь») → деньги
- время, ничего не расходуется. Проверки, что у источника реально есть это заклинание, НЕТ (модель доверия, слова Andrey). Источник — нарратив (событие ленты «📖 <PC> переписал(а) <заклинание> у <источник>»).
- «Книга заклинаний» как данные НЕ трекается → результат нарративный (событие ленты); реально меняется только: расход свитка (трекается) + деньги. Бросок Аркана — пропускаем (модель доверия, всегда успех; ДМ судит вручную).
- Время
2ч×уровень— лог/нарратив (как крафт wall-clock), НЕ гейт. - Дефолты (форки): деньги — кошелёк PC (дефолт) vs общак; событие ленты —
режим
'copy'в 'craft'-типе vs отдельный.
Success Criteria
- SC-001: скрапер выкачивает все официальные заклинания dnd.su в обеих редакциях (где 2024 есть); повторный прогон идемпотентен (кэш+дедуп).
- SC-002: нода спелла показывает обе редакции переключателем (десктоп + /tg), у спеллов без 2024 переключателя нет/2024 задизейблен.
- SC-003: написание свитка 3 ур. при партии 9: выбрали спелл + 2 писцов (20+20 ч) → −500 зм с общака, свиток в общак/PC, событие в ленте.
- SC-004: заклинание 6+ ур. при партии 9 — гейт с внятной ошибкой.
- SC-005: все числа таблиц/цен правятся в настройках кампании без деплоя.
- SC-006: переподготовка — выбрал новое заклинание N ур. → списано 50×N с кошелька PC, событие в ленте, мгновенно; гейт уровня партии работает.
- SC-007: копирование свитка в книгу — свиток уничтожен (−1 в инвентаре), списано 50×N зм; книга→книга без расхода свитка.
Порядок этапов
1 (база спеллов) — фундамент. 2/3/4 зависят от неё (пикер заклинания → уровень). 2 (свитки), 3 (переподготовка), 4 (копирование) — независимы друг от друга, собираются параллельно поверх базы. Все три — «новые глаголы» spec-058-хаба.
Open (в Plan)
- Точная раскладка полей spell-ноды по фактам dnd.su (см. dndsu-recon.md).
- Механика редакций 2014/2024 на dnd.su — закрыта разведкой: 2014 = dnd.su, 2024 = next.dnd.su, пары по slug, у части нет 2024 (см. dndsu-recon.md).
- Событие ленты: mode 'scribe'/'reprep'/'copy' в 'craft'-типе vs отдельные.
- Связь свиток→спелл (и переподготовка→спелл): attrs-колонка vs fields.
- Деньги переподготовки/копирования: кошелёк PC (дефолт) vs общак — 1 вопрос.
- Заговор при переподготовке: 0 зм (дефолт) vs минимум.
- Хвост: продажа свитков уже работает (sellPcItem/каталог) бесплатно.