Составлен по итогам полного аудита сессии 2026-08-05 (audit.md, Часть II — там каждое утверждение с file:line). Сортировка — по решению владельца: что фундаментальнее для всего будущего контента, то выше, а не что ближе к «часу геймплея». У каждой работы: суть, варианты модели (выбирает владелец), объём (S — до дня, M — дни, L — неделя+, XL — многонедельная магистраль), зависимости.
Фундаментально сломано три вещи, и все три — не баги, а отсутствующие модели. Первая: макромир состоит из двух симуляций — поселенческой экономики и передвижения NPC, — которые не разделяют ни одной строки состояния: крестьяне «работают» таймером, караваны ходят пустыми, отношения фракций заморожены с boot, и всё, что ты назвал недостающим (войны, лорды, культы, царь-крестьянин, грабежи), лежит ровно в этом зазоре — их некуда воткнуть, пока зазор не закрыт моделью «работа = перенос ресурса сущностью». Вторая: шина событий — на деле потиковый буфер с нулём подписчиков в проде и 15 мёртвыми тегами из 40; квесты, которые должны спавнить врагов, эмитят событие в пустоту. Пока шина не станет честной (в любую из двух сторон), контекстные квесты и генератор историй строить не на чем. Третья: боевой урон не имеет модели — пять несинхронных мест вычитания hp, ноль митигации (ни брони, ни крита, ни уклонения), четыре несовместимых правила враждебности; сюда же упирается экипировка: надевать предмет некуда, потому что удару нечего модифицировать. Всё остальное — включая размер main.cpp (60 % которого — смоук-харнесс), рукописные дистанции в 22 файлах и мёртвые каноничные функции при живых самоделках в UI — важная, но косметика: она замедляет работу, а не блокирует контент.
Принятые решения владельца, на которых стоит этот вектор: атрибутов ровно 16 (po2, показываем только имеющие формулу); инвентарь 16×16 = 256 слотов; RNG-контракт уже починен в этой сессии (мир seed 12345 перебазирован); масштаб макромира: кап активных сквадов = 16384 (2^14) — то же золотое число, что универсальный кап сущностей субмира (один потолок = один закон мира), несколько тысяч стабильно; следствия: бюджетный ИИ-свип обязан стать гарантированной ротацией с фиксированным периодом (не backlog-LOD), блипам нужен грид-куллинг, ростеры сквадов — данные (SoldierRecord), не сущности. Также решены и ОТГРУЖЕНЫ (a93018d): навод меча «враги приоритетно», запрет бесплатного фулл-хила при трате очка (хил остаётся на левелапе), wis-дивиденд ко всем наградам XP, единый закон цены торговли (канон харизма+торг × контекст-множитель настроения/трейта).
Формулировка владельца: строить системы и симуляцию так, чтобы игра «сама в себя играла» и ПОРОЖДАЛА ФАКТЫ — экономика, караваны, бандиты, политика. Слой событий (№3, факт→память→правило) — потребитель этого сырья. Отсюда инвариант конструкции для КАЖДОЙ новой системы симуляции: система обязана объявить, какие факты она эмитит — молчащая система невидима для слоя историй (бандит, который грабит, но не эмитит «ограблено», построен наполовину).
Видение владельца (траектория = вариант A, честное производство, поэтапно): при генерации — ресурсы (минимум: лес, камень, железо, дерево, глина; расширяемо); деревня собирает сырьё, город комбинаторно «крафтит» товары; товары в трёх ярусах — жизненно необходимые (еда, кирпичи, одежда) / инструментально-развитие (инструменты, мебель) / роскошь (украшения, статуи); потребности + спрос-предложение + локальные рынки; у городов НАСТОЯЩЕЕ население с потребностями/ростом/счастьем/ богатством; фракция-владелец берёт налоги в свою казну контекстно из своей политики; караван = агент ГОРОДА (цель: продать излишки, купить нужное, вернуться с прибылью); крестьянин = агент деревни, патруль = агент стражи, бандиты = агенты ЛОГОВА, лунатики = агенты руин — агенты принадлежат ландмаркам (реф M&B, но честнее — там упрощено).
Принятые формой предложения агента (детали обсуждаемы):
- Агент = {домашний ландмарк, роль, цикл цели} — колонки таблицы ландмарков вместо захардкоженных спавн-блоков npc_spawn.cpp. Рейд банды = «караван» логова с насильственным правилом обмена: ОДНА механика «пойти с целью → обмен → вернуться» для торговли, грабежа, патруля, сбора. (Логова как ландмарка сегодня НЕ существует — бандиты бездомны, спавнятся в радиусе от городов.)
- ОДИН словарь товаров
{id, ярус(сырьё/жизненное/инструмент/ роскошь), вес, базовая цена, рецепт?}— убивает ТРИ несовместимых вокабуляра кода (economy resources[6]+goods[15] с float-«Weapons», каталог предметов игрока с wpn_dagger). Хлеб города и хлеб в инвентаре — одна строка. - Рецепт как строка: входы[] → выход + человеко-дни + требуемый ВИД ландмарка. Зданий-симуляции в v1 нет — «где» = вид ландмарка, здания вырастают позже без ломки таблицы.
- Потребности — лестница по ярусам (Anno/Banished-модель): нехватка жизненного → голод → падение населения/счастья + ФАКТ; инструменты → множители производства/роста; роскошь → счастье/богатство. Честность = сохранение потоков на агрегатах поселения, не поимённая симуляция.
- Казна фракции + налоговая политика из темперамента (у Kingdom сегодня нет поля казны): налоги ← богатство городов; казна → жалование гарнизонов/армий → «не выплачено» → дезертиры (петля в №3). Стоки (роскошь, войны, стройки, жалование) обязательны против раздувания.
Методика баланса: якорная единица — человеко-день (1 еда/житель/день, круглые числа по дому); производительность крестьянина задаёт долю крестьян, остальное выводится. Главный инструмент — headless-прогон «мир играет сам в себя» на N игровых лет с инвариантами-законами (сохранение ресурса: произведено = потреблено + Δскладов; население в коридоре; цены не разлетаются; ни одна деревня не вымерла без факта-причины в хронике) + свип по сидам — по образцу смоука macro_travel_sp, уже доказывающего закон сохранения SP на живой игре.
Словарь фактов v1 (~12): собрано, произведено, караван вышел/прибыл/ потерян, ограблено, голод начался, налог собран, казна пуста, жалование не выплачено, население выросло/упало, урожай/неурожай.
Решения владельца (2026-08-05, вторая итерация раунда):
- (а) РЕШЕНО: единый универсальный словарь товаров — ДА. Уточнения формы: словарь — СУЩЕСТВИТЕЛЬНЫЕ мира; лут-профили / рецепты / потребности / ассортименты — ОТНОШЕНИЯ над ними (лут-таблица не умирает, ссылается на те же id — она уже строково-ссылочная). Процедурные предметы = архетип в словаре + экземпляр {archetypeId, materialId, quality, seed}, всё процедурное выводится из сида (тот же паттерн, что таблица монстров и резолвер спрайтов: строка каталога + пер-экземплярный контекст; арт авто-перекрывает). Материалы = строки яруса «сырьё» ТОГО ЖЕ словаря; закладка под будущий крафт СЕЙЧАС = колонка допустимых материалов у архетипа (маска), позже — рецепты + таблица множителей материал×архетип.
- (б) РЕШЕНО: логово бандитов — ландмарк (владелец рисует арт, по аналогии с городом/деревней; резолвер спрайтов перекроет процедурное без правки движка). Размещение — контекстные колонки строки: леса, близость дорог, МИНИМАЛЬНАЯ дистанция от городов и замков. В модели агентов: дом бандитов, «экономика» — награбленное, «караван» — рейд.
- (б+) РЕШЕНО: замки — ландмарк СИЛЫ. Дом военных агентов (патрули/гарнизон/армии), денежный узел (налог сюда, жалование отсюда — петля «казна пуста → дезертиры»), сиденье лорда (стыкуется с possession «выйти лордом»). Размещение — территория/дороги/границы королевства.
- (в) РЕШЕНО: объём v1 = 5 сырья + 3 товара на ярус (14 строк); (г) РЕШЕНО: здания абстрактны в v1 (рецепт называет ВИД места).
macro/commodity.h — единый словарь (14 строк, ярусы, колонка
materialMask-закладка под крафт); macro/econ_day.{h,cpp} — честный день
в трёх чистых шагах (gather из конечных Deposits / produce по таблице
рецептов / consume по лестнице потребностей, целые единицы, факты через
POD-синк — macro L1 не видит шину L3, наверх их поднимет app при
подключении); tests/econ_v1_test.cpp — арбитр баланса в зародыше:
64 дня самоигры пары деревня+город, закон сохранения сведён ДО ЕДИНИЦЫ
по каждому товару, месторождения дренируются ровно на собранное, голодные
факты стреляют по переходам. 46/46 ctest, ноль ворнингов, игра собирается,
смоук зелёный (костяк инертен).
Следующие инкременты подключения (по порядку):
- Stockpile на Settlement/Village вместо resources[6]+goods[15]
(save bump), депозиты из генерации мира, gather кормят агенты-крестьяне
(№1 агентная половина). Дизайн депозитов (раунд с владельцем
2026-08-05): у депозита ОДНА колонка «модель пополнения» (enum+темп,
данные, не механика-на-ресурс):
- отрастание к базе — дерево: депозит = СЛОЙ ДЕРЕВЬЕВ (u16 счётчик + set_tree_count уже отгружены; отрастание «годы к базовой линии с ленивыми оверрайдами» уже спроектировано в resource-squads);
- сезонный урожай — зерно: новая рукотворная фича FT_Field (владелец: «давно хотел», по аналогии с дорогами) — размещение при генерации контекстно: клетки у деревень с высокой плодородностью (плодородность = канал влажности u_master.G, УЖЕ данные); рендер = один GLSL-оверлей по штатному пути фич; субмир видит через CellContext; рукотворность ⇒ создаваемо в рантайме (деревня выросла → распахала, факт «распахано»); урожай — осенний пульс × влажность (сезоны отгружены);
- медленная аллювиальная — глина у рек;
- квази-бесконечный — камень (огромный remaining);
- конечный + открытие жил — железо (рекомендация агента, владелец: «надо думать»): шахта ИССЯКАЕТ → факт «жила кончилась» → драма (город хиреет, миграция); новые жилы открываются правилом слоя №3: память «разведка региона» от работы рудокопов в горном контексте → факт «найдена жила» → новый депозит. РЕШЕНИЕ ПО ЖЕЛЕЗУ ОТКРЫТО.
- Караван-агент: цикл «излишки→город, купить нужное, прибыль домой» поверх уже существующей математики цен; чинит по пути три бага торговой сети (№2: коллизия ID, тор, гарнизоны).
- Налог → казна фракции (+поле казны), жалование, петля к дезертирам.
Headless-прогон «мир играет сам в себя N лет»SHIPPED 2026-08-30 какbalance_run(см. методику выше) — не ctest, а измерительный инструмент; регрессионный смоук-закон поверх — открытый хвост.
Что сейчас (переписано 2026-08-23, сверено с кодом). Костяк построен и
ПОДКЛЮЧЁН: Stockpile — единый склад поселения (macro/econ_day.h),
честный день gather/produce/consume идёт в мировом тике
(macro/world_tick.cpp), собиратели — ОДИН ai_gatherer +
kGathererDefs-строки (дровосек/фермер/майнер/каменотёс/глинокоп,
macro/npc_ai.cpp), деревья/депозиты — carrier-строки реестра полей.
Живой остаток: караван КОНФИСКУЕТ; казны фракции нет — ЗАКРЫТО
2026-08-30/31 (трек баланса): сделки платят через transfer_value
(станционная и вендорская, npc_ai.h), налоговый граф деревня→город→столица
ходит носителями, жалованье+питание = один закон содержания, казна
фракции = склад столицы (Kingdom.capitalLandmarkId). THE-состояние —
economy.md + CANON S10/S24.
Объект модели: «работа» — перенос количества ресурса между двумя хранилищами, выполняемый сущностью за время. Инвариант: ресурс сохраняется (создаётся только в источниках-месторождениях, уничтожается только в потреблении); никакая формула не творит товар из населения напрямую. Предельные случаи: крестьянин с корзиной и караван с обозом — одна и та же работа с разными числами; грабёж — та же работа с чужим хранилищем-источником и враждебной сущностью-исполнителем.
- Вариант A — сущности становятся экономикой (честный, дорогой): производство поселения = сумма завершённых работ его жителей; woodcutter реально декрементирует tree count и инкрементирует склад. Плюс: мир честен, грабёж каравана автоматически бьёт по экономике, «царь-крестьянин» — просто сущность с работой. Минус: баланс придётся перекалибровать с нуля; для тысяч NPC нужна дешёвая запись «работа завершена» (агрегатами, не потиково). Объём XL.
- Вариант B — экономика остаётся формулой, сущности — её курьеры: формула производит, но ДОСТАВКА (деревня→город, город→город) идёт только телами; убил караван — груз не доехал. Плюс: меньше перекалибровки, сразу даёт бандитам добычу и игроку перехват. Минус: производство всё ещё фикция, лесоруб так и не рубит. Объём L.
- Вариант C — гибрид по ресурсам: дерево/руда — честные (по слоям, инфраструктура есть), зерно/товары — формулой до лучших времён. Объём L, путь к A по одной строке таблицы ресурсов.
Зависимости: работа №2 (торговая сеть) — её лучше чинить внутри этой модели; NPC-стоимость местности (один закон movement_cost на всех — сейчас только игрок) — сюда же. Отношения фракций получают первый живой вход (грабёж = событие отношений) — см. №3.
- №2 торговая сеть — все три бага починены (
e6e87e6, save v21, тест-первый trade_law_test с живым красным прогоном): дистанция торговли ТОРОВАЯ (цена и дни пути), маршрут несёт ВИД сторон (originIsVillage/destIsVillage + resolve_route_parties), гарнизон капнут (kMaxGarrisonPerSettlement=64, static_assert под сейв-гардом). - FT_Field отгружен (
9a33990): байт 3 (осознанное переиспользование — слой не сериализуется), контекстное размещение по влажности вокруг деревень, горы/вода/дороги исключены, фурроу-рендер в macro.frag, вес хода 2.0, parity-тест расширен, ДО/ПОСЛЕ кадры отсмотрены глазами. - Разведка gigahrush записана (audit II.14): портируемый wrap.h, правило «никогда остовное дерево над тором», per-source BFS c сентинелем 0xFF, вынос шва из внутреннего цикла для полей, «оборачивай якорь, не вершины» — справочник для караванов/армий/полей влияния.
- Находка для следующего шага (та же болезнь, что коллизия ID): деревенский дровосек спавнится с homeSettlementId = nearestCityId (npc_spawn.cpp) — «дом» агента указывает на ГОРОД, а не на деревню. Модель «агент ландмарка» требует честного дома: {kind, id} или ссылка на ландмарк. Чинить при W2.
W2 (следующий инкремент подключения): Stockpile + настоящий сбор. Добавить Stockpile на Settlement/Village (сериализация, save v22); MacroAiContext получает TreeLayer/оверрайды + доступ к складу ДОМА агента (после починки home-ссылки); ai_woodcutter в Working реально декрементит слой деревьев и инкрементит склад (закон сохранения через set_tree_count-путь, как рубка в субмире); крестьянин у FT_Field собирает зерно (сезонный пульс — следом). Факты Gathered поднимаются в app.
Шина — нервная система МИРА, а не канал уведомлений UI. Решения владельца:
- факт мира = ПЛОСКАЯ ЗАПИСЬ ИЗ ОРДИНАЛОВ («что, кто, с кем, где, когда, сколько»); строка внутри записи запрещена — она ломает и индекс, и сейв, и скан, то есть делает живой мир невозможным (проверочный пример владельца: ведьмак, который САМ находит монстра по следам);
- кольцо фактов + индекс по КЛЕТКЕ (та же счётная сетка, что носит сквады); «что случилось рядом» — запрос к 3×3, а не скан летописи; кольцо сохраняется;
- слова выводятся из факта при показе — журнал игрока становится ВИДОМ на летопись вместо 8192 строк в сейве, локализация делается даром;
- ДВА ЯРУСА, одна запись: кольцо (индекс по клетке, забывает — ярус ведьмака) и АННАЛЫ (только дописываются, без индекса). В вечное попадает ПО УЧАСТНИКУ: у кого есть ординал личности (сквад = лорд, ландмарк, фракция), у безымянной массовки его нет вообще;
- сейв несёт анналы ЦЕЛИКОМ — они не кэш, а часть мира: на них будет РЕЖИМ ЛЕГЕНД;
- микромир пишет в ТУ ЖЕ летопись (истина мира — макро, S1);
- личность = монотонный персистентный ординал (приём
nextMacroSpawnOrdinal), и квесты получают такие же номера; ХЭШ СТРОКИ ЛИЧНОСТЬЮ НЕ БЫВАЕТ.
СДЕЛАНО К ЭТОМУ МОМЕНТУ (982f0ca): история шины — кольцо (был erase(begin) =
memmove 4096 записей КАЖДЫЙ ТИК), payload'ы кадра (dialogChoices/storyResult) в
историю больше не копируются — оттуда их не читал никто, мёртвая запись
SpellCast.s2 удалена.
ОСТАЛОСЬ: собственно POD — строки на ординалы. Разведка 2026-08-27 нашла, что
БОЛЬШИНСТВО строк дублируют ординал, который УЖЕ лежит в том же событии (id
квеста в a, id спелла в a, id поселения в a и ix; SpellLearned вообще
имел ординал в b и разворачивал его в строку). Хвосты, требующие работы:
персистентные строковые списки (completedQuestIds, codexUnlocked, книга
заклинаний), каталог кодекса живёт в UI-слое, и NodeContext не видит мира —
слою логических узлов нужен читающий доступ к макромиру (это то же самое, что
понадобится ведьмаку, чтобы НАЙТИ деревню).
ПОСТРОЕНО 2026-08-27/28 (полный разбор — chronicle.md):
macro/chronicle.h/.cpp — POD-факт 32 байта, два яруса (кольцо с индексом по
клетке + анналы), самотрункирующиеся цепочки, слава как величина ВСЕХ
макро-сущностей с ценой от ЖЕРТВЫ, слова из фактов, окна из строк родов,
зоны субмира, обе двери (макро и микро), сейв v53 несёт историю целиком.
Писатели: смерть в автобое, переходы голода и бунта, осушённый шпиль,
пересечение зоны.
ОСТАЛОСЬ: GameEvent ещё не срез летописи (большинство его строк дублируют
ординал, лежащий в том же событии; хвосты — персистентные строковые списки и
слой логических узлов, не видящий мира); журнал игрока ещё не вид на летопись;
захвата поселения нет как МЕХАНИКИ, поэтому и писателя нет.
ОТКРЫТО ?27 — универсальная система ТЕКСТОВ (диалоги авторские и процедурные). Закон «слова из факта» отвечает про журнал, но не про диалоги. Отдельный трек.
Сделано (16999f2 + 03e7205, save v20): 16 мёртвых тегов и 5 мёртвых
методов шины удалены с доказательствами; enum плотный; заголовок шины
объявляет опросный контракт; SpawnEntity получил потребителя — kill-
контракты впервые спавнят свои цели (данные-лукап типа по лейблу реестра,
фракция bandits/земля, ординал possession цел, kill-N шлёт N событий);
всё покрыто новым spawn_entity_event_test, ctest 45/45.
Новое правило владельца (2026-08-05), несущая часть этого пункта:
события возникают ТОЛЬКО из игрового контекста и состояния; безусловный
рандом из списка — болезнь. Случайный триггер энкаунтеров (enc_random)
удалён (ffdd978); таблица энкаунтеров и путь диалог→эффекты сохранены как
презентационная половина. Полный разбор шины — что позволяет, что нет,
альтернативы A–E с ценами — в audit.md II.13; рекомендация агента: A
(узаконенный буфер) + E (контекстные правила как таблица данных: условие из
GameState → события; рандом только как ВЕС внутри отфильтрованного
контекстом набора). Выбор за владельцем.
Уточнённая форма E (обсуждена с владельцем 2026-08-05 на его примерах: карательный отряд за убийства, квест защиты часто грабимой деревни, дезертиры от разбитой/неоплаченной армии, культы, неурожаи; «А+Е выглядит норм», финальное добро — при проектировании). Конвейер из ТРЁХ слоёв:
- ФАКТ — точка во времени, эмитится симуляцией, живёт на шине и в хронике («игрок убил члена фракции», «деревню ограбили», «жалование не выплачено»). Лакмус: о нём мир может захотеть вспомнить.
- ПАМЯТЬ — колонки состояния, которые растут от фактов и затухают в игровых тиках (faction.убийстваИгроком, village.грабежиЗаОкно, army.дниБезЖалования; мёртвый deserterPool — готовая такая колонка). ОДИН механизм «факт → инкремент» с таблицей (факт, колонка, вес, затухание) — НЕ отдельная механика под каждый случай (прямой ответ на вопрос владельца про дезертиров).
- ПРАВИЛО — строка таблицы «условие над памятью+контекстом → события-команды» (SpawnEntity уже работает; «квест доступен»; сдвиг отношений), с кулдауном/областью (деревня/фракция/глобально).
Оговорка по канону лора (уточнено с владельцем): память как ВНУТРЕННЕЕ состояние каноном не запрещена — запрещён игровой ИНДИКАТОР там, где lore.md требует, чтобы игрок заметил явление сам (влияние культов растёт без счётчика на экране). То есть колонка памяти жить может, метр в UI — нет. Перед работой с чёрной энергией/культами читать lore.md §9.
Лакмусы разграничения: растущее число → память; «когда X, случись Y» → правило; непрерывный процесс (война царя-крестьянина, захват территории) → симуляция №1, событийке достаются только её факты; периодика (сезоны) → функция времени, а «неурожай» — правило над ней.
Инварианты конструкции: правила читают ТОЛЬКО память и контекст, никогда друг друга; затухание в игровых тиках; веса из мирового сида (воспроизводимость); проверка правил — по приходу факта или раз в игровой день, не каждый тик по всем субъектам.
Границы честно: E только РЕАГИРУЕТ — сырьё-факты производит №1 (сегодня макро-сим почти нем: грабить деревню некому). Первые правила, работающие уже сейчас: карательный отряд (NpcDeath эмитится, спавн-потребитель есть) и дезертиры от субмирных разгромов. «Квест могут взять не только игрок» — следствие №1 (агенты с целями), не №3: в ближайшей версии квест защиты доступен игроку.
Остальной остаток (по-прежнему M): тест-инвариант «у каждого тега есть продюсер И потребитель»; контракт полей per-tag таблицей, не комментарием; Objective как таблица (switch в 3 местах); вызвать generate_quests_for_village (написан, не вызывается — весь класс «деревенских» квестов заперт); решить судьбу недостижимого второго рендера энкаунтеров (draw_encounter_modal); полуживые теги (QuestStart/QuestUpdate эмитятся без потребителя; SettlementVisit/SpellLearned/LandmarkChangeOwner/ WorldCellChange ждут продюсеров). Квест-цепочки (prerequisite/next/chain) — слой поверх. Это фундамент «генератора историй».
ОТГРУЖЕНО целиком, шесть инкрементов (
3a82a12..38abcd3), THE-док трека — combat.md.apply_damage(sub/damage.h) — единственная дверь; вид урона = строка; митигация внутри двери (сегодня тождество — сокет под №5); враждебность = ОДИН предикат (factions_hostile) + его запекание и проекция; фракция ушла из таблицы существ (свойство экземпляра); меч бьёт по ПОВЕРХНОСТИ тела через сетку battlePick_; kill-rep и кошелёк = колонки реестров; авторезолв перестал быть немым и платит теми же дверями, что и рукопашная; NPC-лидер растёт от боя внизу (S14). Тесты: damage_door_test, melee_reach_test, auto_resolve_speaks_test; ctest 81/81.Ниже — исходная постановка, оставлена как история замысла.
Что сейчас. hp вычитают 5 мест с разошедшимися протоколами смерти (плюс 3 смоук-копии); митигации нет вовсе; crit вычислен и не читается; 4 правила враждебности; меч игрока бьёт ближайшее НЕ-игрокское тело без фракции и без body_radius (тролль и лягушка рубятся с одной дистанции — недобитый §18); sight — колонка, которую не заполняет ни одна строка (у всех 200); спелл-контакт O(P·N) при готовой сетке battlePick_ в том же тике.
Объект модели: удар. apply_damage(reg, target, source, amount, kind) —
ЕДИНСТВЕННАЯ дверь: она и только она вычитает hp, ставит
LastHit/HitFlash/DamageFx/Dead, эмитит NpcDeath. Инвариант: смерть от любого
оружия неотличима по протоколу; XP-исключения (гравитация) — параметр, не
пропуск компонента. Митигация — вторым шагом внутри той же двери:
amount' = f(amount, target.defense…), где f для существа без брони — тождество
(предельный случай, не ветка). Крит — там же (lck наконец читается). Сюда же:
меле игрока читает body_radius (как прицел и снаряды уже делают) и целится по
ВРАЖДЕБНОСТИ, а не по «не-игрок»; враждебность сводится к двум правилам
(бинарная маска battle + непрерывный stance ВЫВОДИТСЯ из неё); спелл-контакт
переезжает на battlePick_.
Зависимость: экипировка (№5) вешает модификаторы именно на эту дверь — поэтому №4 фундаментальнее №5. Ещё здесь: kill-rep исключения (wildlife/demons/bandits strcmp) → колонка реестра фракций «убийство не преступление».
Решения приняты: атрибутов 16 (хранение фикс, в UI/генерацию — только имеющие формулу), инвентарь 16×16.
Три яруса хранения, фикс нужен не везде:
- ГОРЯЧИЙ (тикается на 16k сквадов): Position/MacroNpcRuntime/боевые скаляры — УЖЕ фиксированный POD, лист в макро-тике не читается (запекается в Combat при спавне). 16k не ждут решения по скиллам.
- ХОЛОДНЫЙ (лист): фиксировать ради сейва/памяти/будущей GPU-проекции.
- GPU (будущее): едет не лист, а упакованная проекция горячего подмножества (правила толпы из ARCHITECTURE) — полный лист GPU-плотным быть не обязан.
ФИКСИРОВАННЫЙ КОНВЕРТ + таблицы смыслов (тот же приём, что принят для атрибутов, на весь лист): хранилище = фикс-массив с po2-капом, смыслы = таблица строками ПОД капом. Предложенные числа: атрибуты 16×u8 (16 Б); скиллы 32 слота×u8-ранг (32 Б; сегодня 8 скиллов); перки БИТСЕТ 64 бита (8 Б; сегодня 24 id) — параметры перка живут в СТРОКЕ ТАБЛИЦЫ ПЕРКОВ, не в теле, носимые счётчики если появятся — из резерва; уровень/XP ~16 Б; резерв до границы; итого конверт 128 Б (2^7). Свойство-цель: новый скилл/перк = строка таблицы смыслов, ФОРМАТ СЕЙВА НЕ ШЕВЕЛИТСЯ (убивает класс «новый скилл = 6 файлов + бамп»). 16k листов = 2 МиБ.
Инвентарь двух уровней одного словаря: сетка 16×16=256 — интерфейс ИГРОКА; массовый NPC несёт компактный фикс-мешок (напр. 8 слотов {индекс товара u8, счёт u16} = 24 Б) либо только ссылку на лут-профиль + золото. Строковые id из записей уходят, остаются индексы словаря.
Порядок внутри (каждый шаг — таблица, не ветки):
- Атрибуты 9→16: POD расширяется до 16 слотов (save bump); wis подключается (award_exp умножает на expMult — сейчас три пути XP её игнорируют), lck ждёт крита из №4; cha.relationBonus либо подключить к репутации, либо убрать. Новые 7 — зарезервированы, имена дадим по мере формул. [S/M]
- Скиллы: привести 6 нарушителей к собственному закону «1 ранг = 1 %» (это ребаланс! сейчас фактически 5 %/ранг) ЛИБО узаконить пер-скилловый множитель КОЛОНКОЙ таблицы. Сделать skill-таблицу настоящей (сейчас добавление скилла = 6 файлов). [M]
- Инвентарь 256 слотов: green field (сейчас безлимитный vector без понятия слота) — слот u8, стек на слот; вес остаётся (перегруз уже работает через стоимость шагов). [M]
- Экипировка: ItemDef получает колонки slot/damage/armor/…; PlayerEquipment (и для NPC — на уже существующем, ныне мёртвом CharacterSheet-компоненте тела); модификаторы входят через kPlayerBaseMeleeDamage-шов (engine.cpp) и дверь №4. Визуальной половины больше НЕТ: пейпердолл снесён 2026-08-20, спрайт-закон = sprites.md (вид = одна картинка; экипировка на спрайт не проецируется). use_item наконец получает кнопку в UI. [L]
- Перки: 24 id / 7 описаний / 1 эффект. Модель: перк = строка таблицы {id, описание, хук, параметры}; недостижимые 17 либо получают строки, либо вычищаются из enum (save bump). [M]
- Свободный полный хил при трате очка (calculate_combat_stats ставит currentHp=maxHp, UI зовёт его на каждый «+») — починить отдельно от всего, тест-первый. [S]
Зависимости: 4 (дверь урона) для смысла экипировки; 3 (шина) не нужна.
- Общие хелперы: в core/math.h нет плоского dist2/dist3 — потому 22 файла рулят вручную, а три приватных хелпера (dist2/dist3sq в engine.cpp, length2d в battle.cpp) недоступны соседям. Дать sm::dist2_sq/dist3_sq и механически перевести хотя бы боевой кластер (битово тот же код). [S]
- Тор-обёртки в macro: economy (№2), ecs/systems interp, node_registry шаги, npc_ai visualSpeed — не обёрнуты; каждое — тихий шов-баг. wrap_delta экспортировать из core/torus.h (сейчас копии в ui). [S]
- Окно как стена: AI отражается от края окна, battle зажимает, игрок клампится — три разных поведения на несуществующей границе. Модель: единый ответ «что делает мовер у края НЕзагруженного мира» (варианты: замедляющая кайма; поводок к якорю клетки; деспавн-переспавн от клетки). Решение владельца. [M]
- window→cell дубль (ground_faction_at / ground_travel_weight_at) → одна функция. [S]
- 60 % main.cpp — смоук-харнесс (5367 строк, 660 условий), не отгорожен даже ifdef'ом. Один блокер выноса: struct App в анонимном неймспейсе. Промоут App → src/app/app.h, смоук → src/app/smoke/, консоль → console_commands.cpp, дебаг-панели → ui/. После этого main.cpp ≈ 2400 строк настоящего ядра (frame/tick/input). [L, риск низкий]
- UI владеет логикой (14 нарушений в audit.md II.9): интегратор ходьбы step_macro_walk — из ui/ в macro/; 4 копии тел сделки + таверна + прокачка — в macro-функции, после чего мёртвые каноны player_buy_price/player_sell_price/spellbook_can_cast ОЖИВАЮТ (их за этим и писали), а «+» перестаёт лечить. [M]
- RNG-зоопарк (остаток): xorshift32 (rng.h) и SheetRng-LCG (character_sheet.h) живут параллельно; SinRng и flag_generator уже удалены. Свести к rng.h+hash3. [S]
- «Держите синхронно» → static_assert там, где это возможно (kNpcLoot/kNpcLootId; kFactionDefs↔politik id; счёт TS-заупокойных «mirrors X.ts» комментариев — под нож, парити мертво). [S]
- Мёртвые поля вычистить (save bump копится с №5): ecs::Active, NpcCharacter.bodyShape/tint*, CombatStats.*Regen, NpcTypeDef.portrait (при живом хардкод-свитче спрайтов рядом — либо читать колонку, либо удалить), ItemEffect.str/end/agi, deserterPool, ecs::Structure Tree/Rock/House/Wall (+переименовать от коллизии с sub::Structure), выброшенный Voronoi-проход politik. [M]
- 16 особых веток игрока в бою — сносятся постепенно ЧЕРЕЗ №4 (дверь урона снимает половину), финал — Airborne вместо playerVz_.
- solid_count и structure_is_decayed: распад структур пишется и не читается — либо фича (руины ветшают), либо удалить пару целиком.
- ARCHITECTURE.md врёт про квесты (kObjectiveCheckers и др. не существуют) и про «no player special-case»; kCrowdPenalty из доки удалён из кода этой сессией. Докам нужен проход после №3/№4, не раньше (иначе дважды).