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-суть)

Новая функция — свитки. Два этапа:

  1. Парсер с dnd.su (прецедент — предметы, spec-018): выкачать ВСЕ заклинания https://dnd.su/spells/ (никакого homebrew), одновременно ДВЕ версии — основную базовую редакцию и правила 2024 года. В разделе spell (это НОДА) версия — переключателем. Не у всех спеллов есть версия 2024. Итог: база данных всех спеллов.
  2. Правила написания свитков — похожи на крафт, но вложения считаются иначе. Проверки «заклинание в списке класса персонажа» НЕТ — только гейт уровня: привязка к уровню партии (сейчас 9-й → максимум 5-й уровень заклинаний). Можно писать один свиток вместе. День = 8 часов, рабочая неделя = 40 часов.

Таблица написания (канон Andrey)

УровеньВремяЧасы (8ч/день, 40ч/нед)Цена, зм
Заговор1 день815
11 день825
23 дня24250
31 рабочая неделя40500
42 недели802 500
54 недели1605 000
68 недель32015 000
716 недель64025 000
832 недели1 28050 000
948 недель1 920250 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)

  1. Точная раскладка полей spell-ноды по фактам dnd.su (см. dndsu-recon.md).
  2. Механика редакций 2014/2024 на dnd.su — закрыта разведкой: 2014 = dnd.su, 2024 = next.dnd.su, пары по slug, у части нет 2024 (см. dndsu-recon.md).
  3. Событие ленты: mode 'scribe'/'reprep'/'copy' в 'craft'-типе vs отдельные.
  4. Связь свиток→спелл (и переподготовка→спелл): attrs-колонка vs fields.
  5. Деньги переподготовки/копирования: кошелёк PC (дефолт) vs общак — 1 вопрос.
  6. Заговор при переподготовке: 0 зм (дефолт) vs минимум.
  7. Хвост: продажа свитков уже работает (sellPcItem/каталог) бесплатно.