squad-proto/src/ui/menu.cpp

1706 lines
78 KiB
C++
Raw Normal View History

Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
#include "ui/menu.h"
#include <raylib.h>
#include <cstdarg>
#include <cstdio>
Материалы и мастерская: вещь можно доработать (M27) Аффиксы сделали находку решением, но решением одноразовым: выпало как выпало. Материалы дают второй способ получить нужное — не «повезло», а «доработал». Пять материалов, пять операций, экран WORKSHOP. Главное правило экономики — материал добывается ТОЛЬКО В СВОЕЙ ПОЛОСЕ: био-ядра с плотских этажей, крио с мёрзлых. Это единственная причина возвращаться на средние глубины, когда рекорд ушёл вниз; без полос весь контент, кроме последнего акта, умирает в день прохождения. Материалы — валюта, а не предметы: не занимают ячеек, не весят, в схроне не лежат. Но теряются как добыча: зачисляются только при эвакуации. Три уточнения операций, каждое против конкретного обхода: перековка меняет только значения (иначе это новый бросок предмета в обход редкости), прививка не ставит уже стоящий тег (иначе «+урон» шесть раз), возвышается случайный аффикс (иначе все предметы сойдутся к одному). --tool craft поймал, что материалы бесплатны. С твари падало по её danger, и вылазка на восьмидесятом этаже приносила тысячу ядер при цене доводки вещи в шестьсот: 0.6 вылазки на предмет вместо восьми. Правило заменено на «одно ядро с мелочи, два с крупной» — стало 6.2. Сам инструмент при этом тоже считал неверно: брал доход из бюджета опасности этажа, хотя на этаж влезает не больше MAX_LEVEL_SPAWNS тварей. Бюджет уходит не в количество тварей, а в их силу, и доход упирается в потолок расстановки с сорокового этажа. Глубина даёт не количество материалов, а качество предметов — и вместе с ним цену доводки. Залом замерен на тысяче попыток: рассыпалось 11.6% при заявленных 12%, плата снимается всегда, слот теряется всегда. «У меня трижды не рассыпалось» не доказательство ни в какую сторону. Пункт меню WORKSHOP проверен --ui-check — той самой проверкой, которая появилась вчера из-за пункта UPGRADES, молча закрывавшего игру. Формат профиля 5: строка mat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 07:07:46 +07:00
#include <cstring>
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
#include "game.h"
Редкости и аффиксы: находка стала решением (M25) Предмет перестал быть строкой каталога. ItemInstance несёт редкость и до шести аффиксов, и «карабин» с первого этажа больше не равен «карабину» с сорокового. Четыре редкости, десять тегов, тир и бюджет от глубины. Аффиксы не заводят новых каналов влияния: каждый тег ложится в то, что уже есть — урон оружия, смесь стихий, пробитие, четыре числа бойца, ресурсы вылазки. У предмета по-прежнему нет ручки к правилу «не бить своих», и появиться ей неоткуда. ItemInstance неявно приводится к ItemId, поэтому полторы сотни мест, которым нужен ВИД вещи, остались как были. Цена решения — присваивание в ItemId молча теряет рулоны; таких мест три (подбор, схрон, запись профиля), и все они работают с экземпляром целиком. Замеры поймали три расхождения дизайн-документа с самим собой: * формула редкости противоречила таблице рядом с ней: 2.7% находок с аффиксами на первом этаже против 22%. Выбрана формула, наклон поднят до схождения с таблицей на дне (12% обычных на сотом); * бюджет пускал восемь высших аффиксов вместо обещанных шести; * предельная вещь собиралась к семидесятому этажу вместо сотого. Числа пересчитаны, документ исправлен: 6·cost(9) = 294 <= B(100) = 295 < 7·cost(9) = 343. Предельная вещь существует ровно на дне и ровно одна. Проверка в --descend меряет не бросок, а ПРОВОДКУ до бойца — там, где дважды умирали вес и стихии: аффиксы: урон 12.0 -> 18.0, здоровье 100 -> 140, броня 0.10 -> 0.25, пробитие 0.20, KIN 1.00 -> 0.60, CRY 0.40, сумма долей 1.000 Последнее число не украшение: конверсия обязана переводить урон, а не добавлять седьмую долю сверх единицы. Попутно, на глубине 30 и только там: * «зачистка поля» в проверках не держалась — у твари есть откат воскрешения, и с нулевым таймером она оживала тем же шагом. Лечение вне схватки и расход света на стоянке молча мерили бой. На восьмом этаже тварей мало, и проверка проходила по удаче; * порог «стоянка бесплатна» стоял в единицах заряда, а тот же шаг стоит дороже с глубиной. Переведён в тайлы — и сразу показал, что отряд в строю проходит 0.8 тайла за две секунды «стоянки». Свет теперь списывается только в ADVANCING: топтание и драка бесплатны, тратит РАЗВЕДКА. Без этого таймер возвращался через чёрный ход. Формат профиля 4: токен предмета вида rifle@relic+dmg7,0:0.63. Миграции не требует — у старых токенов нет хвоста после '@'. --accept PASS без изменений: в песочнице находок нет вовсе. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:35:00 +07:00
#include "sim/affix.h"
Материалы и мастерская: вещь можно доработать (M27) Аффиксы сделали находку решением, но решением одноразовым: выпало как выпало. Материалы дают второй способ получить нужное — не «повезло», а «доработал». Пять материалов, пять операций, экран WORKSHOP. Главное правило экономики — материал добывается ТОЛЬКО В СВОЕЙ ПОЛОСЕ: био-ядра с плотских этажей, крио с мёрзлых. Это единственная причина возвращаться на средние глубины, когда рекорд ушёл вниз; без полос весь контент, кроме последнего акта, умирает в день прохождения. Материалы — валюта, а не предметы: не занимают ячеек, не весят, в схроне не лежат. Но теряются как добыча: зачисляются только при эвакуации. Три уточнения операций, каждое против конкретного обхода: перековка меняет только значения (иначе это новый бросок предмета в обход редкости), прививка не ставит уже стоящий тег (иначе «+урон» шесть раз), возвышается случайный аффикс (иначе все предметы сойдутся к одному). --tool craft поймал, что материалы бесплатны. С твари падало по её danger, и вылазка на восьмидесятом этаже приносила тысячу ядер при цене доводки вещи в шестьсот: 0.6 вылазки на предмет вместо восьми. Правило заменено на «одно ядро с мелочи, два с крупной» — стало 6.2. Сам инструмент при этом тоже считал неверно: брал доход из бюджета опасности этажа, хотя на этаж влезает не больше MAX_LEVEL_SPAWNS тварей. Бюджет уходит не в количество тварей, а в их силу, и доход упирается в потолок расстановки с сорокового этажа. Глубина даёт не количество материалов, а качество предметов — и вместе с ним цену доводки. Залом замерен на тысяче попыток: рассыпалось 11.6% при заявленных 12%, плата снимается всегда, слот теряется всегда. «У меня трижды не рассыпалось» не доказательство ни в какую сторону. Пункт меню WORKSHOP проверен --ui-check — той самой проверкой, которая появилась вчера из-за пункта UPGRADES, молча закрывавшего игру. Формат профиля 5: строка mat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 07:07:46 +07:00
#include "sim/craft.h"
Дерево улучшений вместо четырёх кнопок (M32) Четыре строки покупок (патроны, переноска, обзор, живучесть) удалены целиком. Выбора в них не было: купить в итоге можно было всё, порядок ни на что не влиял, и через две вылазки экран превращался в кнопку "продолжить". Дерево: пять ветвей по восемь узлов, у каждого пять уровней. Ветви - это пять способов закрыть разрыв из закона масштаба: стволы, клинки, стихии, снабжение, отряд. Ветвь открывается цепочкой - узел доступен, когда взят предыдущий в этой же ветви. Свободная сетка читалась бы как список; цепочка читается как путь. Ни один узел не заводит нового канала влияния: каждый кладёт своё число туда, куда уже кладут снаряжение и аффиксы. Дерево будет расти, и первый же "особенный" узел со своей веткой в коде превратил бы SyncLoadout в свалку. ЭКОНОМИКА. --tool economy поймал, что первая версия дерева стоит 9% одного прохода 1..100: опыт растёт с глубиной квадратично, а дерево с фиксированными ценами выкупается целиком за половину глубокого прохода, и выбор кончается к сороковому этажу навсегда. Введено общее удорожание: каждая вложенная точка поднимает цену всех следующих (цена = база · 1.5^уровень · (1 + 0.15 · вложено)). Полное дерево стоит 34 млн против 41 млн, заложенных в дизайне, то есть около шести с половиной полных проходов. Старые покупки из профиля читаются, но не применяются: потраченный на них опыт возвращается в банк. Молча съесть чужие двадцать часов нельзя. Проверка меряет ПРОВОДКУ по всем пяти ветвям, а не таблицу узлов: дерево: куплено уровней 45, закрытый узел закрыт урон ствола 12.0 -> 14.4, здоровье 100 -> 150, скорость 1.06 -> 1.22 парирование 0.34 -> 0.49, свет 420 -> 620 Попутно: в проекте hollow бегуны и хваты уехали с первых этажей глубже - там одиночка с ножом гиб на первом же этаже по той же причине, по которой это было в основном проекте. Формат профиля 7: строки tree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 23:24:50 +07:00
#include "sim/tree.h"
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
namespace
{
// --- палитра -----------------------------------------------------------------
const Color COL_DIM = {6, 6, 8, 218};
const Color COL_TEXT = {206, 200, 190, 255};
const Color COL_FAINT = {110, 106, 100, 255};
const Color COL_PICK = {236, 226, 206, 255};
const Color COL_BLOOD = {150, 44, 40, 255};
const Color COL_DEAD = {60, 58, 56, 255}; // пункт, который сейчас не работает
const Color COL_EDGE = {58, 54, 52, 255}; // рамка спокойной ячейки
const Color COL_HOVER = {74, 40, 34, 130}; // подложка под курсором
const Color COL_REJECT = {150, 40, 34, 150}; // слот не примет то, что тащат
const Color COL_SHADOW = {8, 6, 6, 220}; // контур курсора
// -----------------------------------------------------------------------------
// Кадр 480x270 лежит в окне со сдвигом и ЦЕЛЫМ масштабом (ScreenFitRect),
// поэтому вся разметка ниже задана в пикселях кадра и переводится в окно только
// здесь. Считать в пикселях окна нельзя: на каждом мониторе множитель свой,
// и меню разъедется с картинкой.
// -----------------------------------------------------------------------------
struct Layout
{
int x = 0, y = 0; // левый верхний угол кадра в окне
int u = 1; // размер «пикселя» кадра в пикселях окна
int w = 0, h = 0; // размер кадра в пикселях окна
int fw = 0, fh = 0; // размер кадра в его собственных пикселях
};
Layout Of(const ScreenRect& fit)
{
Layout l;
l.u = (fit.scale > 0) ? fit.scale : 1;
l.x = fit.x;
l.y = fit.y;
l.w = fit.w;
l.h = fit.h;
l.fw = fit.w / l.u;
l.fh = fit.h / l.u;
return l;
}
// Текст в координатах КАДРА, а не окна: меню обязано жить по той же сетке,
// что и картинка, иначе на разных мониторах оно разъезжается.
void Text(const Layout& l, const char* s, int fx, int fy, int size, Color c)
{
DrawText(s, l.x + fx * l.u, l.y + fy * l.u, size * l.u, c);
}
// Ширина строки В ПИКСЕЛЯХ КАДРА. Меряем на том же кегле, каким будем рисовать,
// и делим обратно: у встроенного шрифта raylib межбуквенный интервал зависит
// от размера, и «померить на 8, нарисовать на 24» дало бы другую ширину.
int TextW(const Layout& l, const char* s, int size)
{
return MeasureText(s, size * l.u) / l.u;
}
void TextCentered(const Layout& l, const char* s, int fy, int size, Color c)
{
Text(l, s, (l.fw - TextW(l, s, size)) / 2, fy, size, c);
}
void Box(const Layout& l, const UiRect& r, Color c)
{
DrawRectangle(l.x + r.x * l.u, l.y + r.y * l.u, r.w * l.u, r.h * l.u, c);
}
void BoxLines(const Layout& l, const UiRect& r, Color c)
{
const Rectangle rr = {float(l.x + r.x * l.u), float(l.y + r.y * l.u), float(r.w * l.u),
float(r.h * l.u)};
DrawRectangleLinesEx(rr, float(l.u), c);
}
void Shade(const Layout& l, Color c)
{
DrawRectangle(l.x, l.y, l.w, l.h, c);
}
// Полоска-разделитель под заголовком.
void Rule(const Layout& l, int fy, int halfWidth)
{
Box(l, UiRect{l.fw / 2 - halfWidth, fy, halfWidth * 2, 1}, COL_BLOOD);
}
// -----------------------------------------------------------------------------
// Сетка экранов. Все числа — в пикселях кадра и живут ровно здесь; сами
// прямоугольники собираются один раз в BuildWidgets, а рисование и попадание
// мыши читают уже готовую таблицу. Разъехаться подсветке и клику негде.
// -----------------------------------------------------------------------------
constexpr int BTN_PAD_X = 10; // поле кнопки вокруг подписи
constexpr int BTN_PAD_Y = 4;
constexpr int MENU_Y0 = 122, MENU_STEP = 20, MENU_SIZE = 12;
constexpr int PAUSE_Y0 = 132, PAUSE_STEP = 20, PAUSE_SIZE = 11;
constexpr int DEFEAT_Y0 = 172, DEFEAT_STEP = 22, DEFEAT_SIZE = 11;
// Экран отряда: карточки бойцов, три слота снаряжения, сетка сумки.
constexpr int SQ_CARD_X = 22, SQ_CARD_Y = 40, SQ_CARD_W = 76, SQ_CARD_H = 30;
constexpr int SQ_CARD_STEP = 88;
constexpr int SQ_EQUIP_X = 34, SQ_EQUIP_Y = 88, SQ_EQUIP_W = 180, SQ_EQUIP_H = 20;
constexpr int SQ_EQUIP_STEP = 23;
constexpr int SQ_LABEL_W = 46; // место под слово HANDS/ARMOR/POCKET внутри слота
constexpr int SQ_STAT_X = 40, SQ_STAT_Y = 158, SQ_STAT_STEP = 9;
// Экран улучшений: четыре строки во всю ширину. Строкой, а не сеткой, потому
// что у каждой есть длинная подпись «что это даёт», и без неё покупка была бы
// покупкой кота в мешке.
Дерево улучшений вместо четырёх кнопок (M32) Четыре строки покупок (патроны, переноска, обзор, живучесть) удалены целиком. Выбора в них не было: купить в итоге можно было всё, порядок ни на что не влиял, и через две вылазки экран превращался в кнопку "продолжить". Дерево: пять ветвей по восемь узлов, у каждого пять уровней. Ветви - это пять способов закрыть разрыв из закона масштаба: стволы, клинки, стихии, снабжение, отряд. Ветвь открывается цепочкой - узел доступен, когда взят предыдущий в этой же ветви. Свободная сетка читалась бы как список; цепочка читается как путь. Ни один узел не заводит нового канала влияния: каждый кладёт своё число туда, куда уже кладут снаряжение и аффиксы. Дерево будет расти, и первый же "особенный" узел со своей веткой в коде превратил бы SyncLoadout в свалку. ЭКОНОМИКА. --tool economy поймал, что первая версия дерева стоит 9% одного прохода 1..100: опыт растёт с глубиной квадратично, а дерево с фиксированными ценами выкупается целиком за половину глубокого прохода, и выбор кончается к сороковому этажу навсегда. Введено общее удорожание: каждая вложенная точка поднимает цену всех следующих (цена = база · 1.5^уровень · (1 + 0.15 · вложено)). Полное дерево стоит 34 млн против 41 млн, заложенных в дизайне, то есть около шести с половиной полных проходов. Старые покупки из профиля читаются, но не применяются: потраченный на них опыт возвращается в банк. Молча съесть чужие двадцать часов нельзя. Проверка меряет ПРОВОДКУ по всем пяти ветвям, а не таблицу узлов: дерево: куплено уровней 45, закрытый узел закрыт урон ствола 12.0 -> 14.4, здоровье 100 -> 150, скорость 1.06 -> 1.22 парирование 0.34 -> 0.49, свет 420 -> 620 Попутно: в проекте hollow бегуны и хваты уехали с первых этажей глубже - там одиночка с ножом гиб на первом же этаже по той же причине, по которой это было в основном проекте. Формат профиля 7: строки tree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 23:24:50 +07:00
// Дерево улучшений: пять столбцов-ветвей по восемь узлов (sim/tree.h).
//
// Сетка, а не свободная раскладка с кривыми связями: узлы читаются столбцами,
// ветвь видна целиком, и «куда я иду» понятно без легенды. Кривые связи из
// больших деревьев красивы на скриншоте и нечитаемы на 480x270.
constexpr int TR_X = 30, TR_Y = 66;
constexpr int TR_COL_W = 86, TR_ROW_H = 23;
constexpr int TR_NODE_W = 78, TR_NODE_H = 19;
inline UiRect TreeNodeRect(int index)
{
const int col = index / NODES_PER_BRANCH;
const int row = index % NODES_PER_BRANCH;
return UiRect{TR_X + col * TR_COL_W, TR_Y + row * TR_ROW_H, TR_NODE_W, TR_NODE_H};
}
Отряд растёт с одного оперативника до пяти (M24) Игра начинается ОДНИМ человеком: тактический нож, слабый фонарь, куртка. Остальные четверо нанимаются за опыт по кривой 600/2400/9600/38400 — самая дорогая покупка в игре и намеренно. Одиночка с ножом это первое впечатление, и продать второго бойца за одну вылазку значило бы выбросить весь хоррор начала: вдвоём уже не страшно. NUM_AGENTS осталась величиной времени сборки и означает теперь ВМЕСТИМОСТЬ, а не размер отряда: делать её динамической это сотня мест ради числа, которое всё равно упирается в пять. Занятость слота это Agent::present, и он отличается от alive. Правила боя читают только alive — для них пустой слот и павший одно, и это верно. Разница нужна там, где показывают человека: у павшего есть тело на полу и перечёркнутая карточка, у пустого слота нет ни того, ни другого. Проверка в --descend меряет цепочку «в профиле -> на этаже» и потолок, а главное — переживает ли одиночка первую схватку: отряд: в профиле 1 -> на этаже 1, после найма 2, цена 600, сверх потолка отказ одиночка с ножом на первом этаже: выжил, 100 -> 41, подошло 2, убил 2 Первая версия этой проверки не измеряла ничего: боец стоял минуту, к нему не подошёл никто, и «выжил» было пустым словом. Теперь отряд ставится рядом с тварью. Попутно найдено и исправлено: Profile::Save три вехи не писала ни версию формата, ни улучшения. Купленный запас патронов действовал до конца запуска и молча пропадал при чтении, а профиль без версии читался как формат 1. Круговой прогон этого не видел, потому что сверял три поля из десяти и делал это на пустом профиле, где всё сходится само. Теперь он сверяет поля списком и на набитом профиле. Формат профиля 3: добавлена строка squad. Старый профиль играл впятером и получает пятерых при миграции — отобрать их значило бы съесть отряд, которого игрок не терял. --accept PASS без изменений: без профиля отряд по-прежнему впятером. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 02:29:19 +07:00
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
constexpr int SQ_STAT_LINES = 4;
// Сумка общая, и ячеек в ней шестнадцать вместо шести на бойца. В два столбца
// они не влезают, поэтому сетка 4x4 клетками поменьше: имя вещи в такую клетку
// не помещается, зато помещается иконка — а что это, теперь говорит подсказка
// под курсором.
constexpr int SQ_BAG_X = 262, SQ_BAG_Y = 100, SQ_BAG_W = 46, SQ_BAG_H = 20;
constexpr int SQ_BAG_GX = 5, SQ_BAG_GY = 4, SQ_BAG_COLS = 4;
constexpr int SQ_HINT_Y = 250, SQ_HINT_STEP = 10;
// Схрон: то, что лежит на базе между вылазками. Двумя рядами во всю ширину
// внизу, а не колонкой сбоку: в схроне десятки предметов, и подпись каждого
// должна читаться — иначе выбор снаряжения превращается в перебор иконок.
constexpr int SQ_STASH_X = 22, SQ_STASH_Y = 208, SQ_STASH_W = 52, SQ_STASH_H = 18;
constexpr int SQ_STASH_GX = 4, SQ_STASH_GY = 3, SQ_STASH_COLS = 8, SQ_STASH_ROWS = 2;
constexpr int SQ_STASH_CELLS = SQ_STASH_COLS * SQ_STASH_ROWS;
Материалы и мастерская: вещь можно доработать (M27) Аффиксы сделали находку решением, но решением одноразовым: выпало как выпало. Материалы дают второй способ получить нужное — не «повезло», а «доработал». Пять материалов, пять операций, экран WORKSHOP. Главное правило экономики — материал добывается ТОЛЬКО В СВОЕЙ ПОЛОСЕ: био-ядра с плотских этажей, крио с мёрзлых. Это единственная причина возвращаться на средние глубины, когда рекорд ушёл вниз; без полос весь контент, кроме последнего акта, умирает в день прохождения. Материалы — валюта, а не предметы: не занимают ячеек, не весят, в схроне не лежат. Но теряются как добыча: зачисляются только при эвакуации. Три уточнения операций, каждое против конкретного обхода: перековка меняет только значения (иначе это новый бросок предмета в обход редкости), прививка не ставит уже стоящий тег (иначе «+урон» шесть раз), возвышается случайный аффикс (иначе все предметы сойдутся к одному). --tool craft поймал, что материалы бесплатны. С твари падало по её danger, и вылазка на восьмидесятом этаже приносила тысячу ядер при цене доводки вещи в шестьсот: 0.6 вылазки на предмет вместо восьми. Правило заменено на «одно ядро с мелочи, два с крупной» — стало 6.2. Сам инструмент при этом тоже считал неверно: брал доход из бюджета опасности этажа, хотя на этаж влезает не больше MAX_LEVEL_SPAWNS тварей. Бюджет уходит не в количество тварей, а в их силу, и доход упирается в потолок расстановки с сорокового этажа. Глубина даёт не количество материалов, а качество предметов — и вместе с ним цену доводки. Залом замерен на тысяче попыток: рассыпалось 11.6% при заявленных 12%, плата снимается всегда, слот теряется всегда. «У меня трижды не рассыпалось» не доказательство ни в какую сторону. Пункт меню WORKSHOP проверен --ui-check — той самой проверкой, которая появилась вчера из-за пункта UPGRADES, молча закрывавшего игру. Формат профиля 5: строка mat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 07:07:46 +07:00
// Мастерская. Слева схрон той же сеткой, что и на экране отряда (привычка
// игрока дороже экономии места), справа пять операций строками с ценой.
constexpr int WS_SLOT_X = 16, WS_SLOT_Y = 74, WS_SLOT_W = 52, WS_SLOT_H = 18;
constexpr int WS_SLOT_GX = 4, WS_SLOT_GY = 3, WS_SLOT_COLS = 4, WS_SLOT_ROWS = 6;
constexpr int WS_SLOT_CELLS = WS_SLOT_COLS * WS_SLOT_ROWS;
Крафт стал выбором: закалка, отпечаток, направленные операции (M33) Прежние пять операций были СЛЕПЫМИ: перековка бросала всё заново, прививка ставила случайный тег, отсечение снимало последний аффикс, возвышение поднимало случайный. Игрок не принимал ни одного решения - жал кнопку и смотрел, что выпало. Это не крафт, это второй бросок предмета за материалы. Теперь половина операций направленные: игрок показывает пальцем, какой аффикс трогать, и платит за это вдвое - вот это уже решение ("перебросить всё дёшево или снять один дорого"). Два новых глагола: ЗАКАЛКА двигает рулон выбранного аффикса к максимуму его тира на треть остатка. Дёшево, понятно, повторяемо - то самое "доработать почти хорошую вещь". Тир не трогает: за ступень платят возвышением. ОТПЕЧАТОК переносит лучший аффикс с ДРУГОЙ вещи, сжигая её. Именно он превращает хлам из схрона в ресурс, а не в мусор, который лежит. Правило "один тег один раз" им не обходится, бюджет проверяется. В мастерской: клик по вещи кладёт её на верстак, повторный клик по той же назначает донором (помечается кровью - он сгорит, и видеть это надо ДО нажатия). Строки аффиксов стали кнопками: по ним выбирается цель. Проверка меряет НАПРАВЛЕННОСТЬ, а не факт срабатывания: направленность: отсечение по цели, возвышение по цели, закалка двигает рулон, отпечаток перенёс и сжёг донора Отсечение пробуется на СРЕДНЕМ аффиксе из трёх: операция, которая "работает", снимая последний, прошла бы и слепую версию. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 23:37:07 +07:00
// Семь операций: строки ужаты, чтобы последняя не налезала на подсказку
// внизу экрана. Кадр 480x270, и место здесь считается по-настоящему.
constexpr int WS_OP_X = 252, WS_OP_Y = 84, WS_OP_W = 212, WS_OP_H = 20, WS_OP_STEP = 22;
Материалы и мастерская: вещь можно доработать (M27) Аффиксы сделали находку решением, но решением одноразовым: выпало как выпало. Материалы дают второй способ получить нужное — не «повезло», а «доработал». Пять материалов, пять операций, экран WORKSHOP. Главное правило экономики — материал добывается ТОЛЬКО В СВОЕЙ ПОЛОСЕ: био-ядра с плотских этажей, крио с мёрзлых. Это единственная причина возвращаться на средние глубины, когда рекорд ушёл вниз; без полос весь контент, кроме последнего акта, умирает в день прохождения. Материалы — валюта, а не предметы: не занимают ячеек, не весят, в схроне не лежат. Но теряются как добыча: зачисляются только при эвакуации. Три уточнения операций, каждое против конкретного обхода: перековка меняет только значения (иначе это новый бросок предмета в обход редкости), прививка не ставит уже стоящий тег (иначе «+урон» шесть раз), возвышается случайный аффикс (иначе все предметы сойдутся к одному). --tool craft поймал, что материалы бесплатны. С твари падало по её danger, и вылазка на восьмидесятом этаже приносила тысячу ядер при цене доводки вещи в шестьсот: 0.6 вылазки на предмет вместо восьми. Правило заменено на «одно ядро с мелочи, два с крупной» — стало 6.2. Сам инструмент при этом тоже считал неверно: брал доход из бюджета опасности этажа, хотя на этаж влезает не больше MAX_LEVEL_SPAWNS тварей. Бюджет уходит не в количество тварей, а в их силу, и доход упирается в потолок расстановки с сорокового этажа. Глубина даёт не количество материалов, а качество предметов — и вместе с ним цену доводки. Залом замерен на тысяче попыток: рассыпалось 11.6% при заявленных 12%, плата снимается всегда, слот теряется всегда. «У меня трижды не рассыпалось» не доказательство ни в какую сторону. Пункт меню WORKSHOP проверен --ui-check — той самой проверкой, которая появилась вчера из-за пункта UPGRADES, молча закрывавшего игру. Формат профиля 5: строка mat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 07:07:46 +07:00
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
// Выбор чекпоинта: столбец кнопок «начать с этажа N».
constexpr int DEPTH_Y0 = 92, DEPTH_STEP = 18, DEPTH_SIZE = 10;
constexpr int DEPTH_MAX_SHOWN = 7; // больше на экран не влезает
constexpr int SUM_Y0 = 214, SUM_STEP = 20, SUM_SIZE = 11;
Материалы и мастерская: вещь можно доработать (M27) Аффиксы сделали находку решением, но решением одноразовым: выпало как выпало. Материалы дают второй способ получить нужное — не «повезло», а «доработал». Пять материалов, пять операций, экран WORKSHOP. Главное правило экономики — материал добывается ТОЛЬКО В СВОЕЙ ПОЛОСЕ: био-ядра с плотских этажей, крио с мёрзлых. Это единственная причина возвращаться на средние глубины, когда рекорд ушёл вниз; без полос весь контент, кроме последнего акта, умирает в день прохождения. Материалы — валюта, а не предметы: не занимают ячеек, не весят, в схроне не лежат. Но теряются как добыча: зачисляются только при эвакуации. Три уточнения операций, каждое против конкретного обхода: перековка меняет только значения (иначе это новый бросок предмета в обход редкости), прививка не ставит уже стоящий тег (иначе «+урон» шесть раз), возвышается случайный аффикс (иначе все предметы сойдутся к одному). --tool craft поймал, что материалы бесплатны. С твари падало по её danger, и вылазка на восьмидесятом этаже приносила тысячу ядер при цене доводки вещи в шестьсот: 0.6 вылазки на предмет вместо восьми. Правило заменено на «одно ядро с мелочи, два с крупной» — стало 6.2. Сам инструмент при этом тоже считал неверно: брал доход из бюджета опасности этажа, хотя на этаж влезает не больше MAX_LEVEL_SPAWNS тварей. Бюджет уходит не в количество тварей, а в их силу, и доход упирается в потолок расстановки с сорокового этажа. Глубина даёт не количество материалов, а качество предметов — и вместе с ним цену доводки. Залом замерен на тысяче попыток: рассыпалось 11.6% при заявленных 12%, плата снимается всегда, слот теряется всегда. «У меня трижды не рассыпалось» не доказательство ни в какую сторону. Пункт меню WORKSHOP проверен --ui-check — той самой проверкой, которая появилась вчера из-за пункта UPGRADES, молча закрывавшего игру. Формат профиля 5: строка mat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 07:07:46 +07:00
const char* const MENU_ITEMS[] = {"CONTINUE", "NEW RAID", "SQUAD",
"UPGRADES", "WORKSHOP", "QUIT"};
constexpr int MENU_COUNT = 6;
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
const char* const PAUSE_ITEMS[] = {"RESUME", "SQUAD", "MENU"};
constexpr int PAUSE_COUNT = 3;
const char* const DEFEAT_ITEMS[] = {"TRY AGAIN", "MENU"};
constexpr int DEFEAT_COUNT = 2;
const char* const SUMMARY_ITEMS[] = {"CONTINUE"};
constexpr int SUMMARY_COUNT = 1;
const char* const EQUIP_LABELS[] = {"HANDS", "ARMOR", "POCKET"};
constexpr UiPart EQUIP_PARTS[] = {UiPart::HANDS, UiPart::ARMOR, UiPart::POCKET};
constexpr int EQUIP_COUNT = 3;
// -----------------------------------------------------------------------------
// Таблица интерактивных элементов текущего экрана.
// -----------------------------------------------------------------------------
struct Widget
{
UiPart part = UiPart::NONE;
int index = 0;
UiRect rect;
const char* text = nullptr; // подпись кнопки; у ячеек её нет
int size = 0; // кегль подписи в пикселях кадра
Редкости и аффиксы: находка стала решением (M25) Предмет перестал быть строкой каталога. ItemInstance несёт редкость и до шести аффиксов, и «карабин» с первого этажа больше не равен «карабину» с сорокового. Четыре редкости, десять тегов, тир и бюджет от глубины. Аффиксы не заводят новых каналов влияния: каждый тег ложится в то, что уже есть — урон оружия, смесь стихий, пробитие, четыре числа бойца, ресурсы вылазки. У предмета по-прежнему нет ручки к правилу «не бить своих», и появиться ей неоткуда. ItemInstance неявно приводится к ItemId, поэтому полторы сотни мест, которым нужен ВИД вещи, остались как были. Цена решения — присваивание в ItemId молча теряет рулоны; таких мест три (подбор, схрон, запись профиля), и все они работают с экземпляром целиком. Замеры поймали три расхождения дизайн-документа с самим собой: * формула редкости противоречила таблице рядом с ней: 2.7% находок с аффиксами на первом этаже против 22%. Выбрана формула, наклон поднят до схождения с таблицей на дне (12% обычных на сотом); * бюджет пускал восемь высших аффиксов вместо обещанных шести; * предельная вещь собиралась к семидесятому этажу вместо сотого. Числа пересчитаны, документ исправлен: 6·cost(9) = 294 <= B(100) = 295 < 7·cost(9) = 343. Предельная вещь существует ровно на дне и ровно одна. Проверка в --descend меряет не бросок, а ПРОВОДКУ до бойца — там, где дважды умирали вес и стихии: аффиксы: урон 12.0 -> 18.0, здоровье 100 -> 140, броня 0.10 -> 0.25, пробитие 0.20, KIN 1.00 -> 0.60, CRY 0.40, сумма долей 1.000 Последнее число не украшение: конверсия обязана переводить урон, а не добавлять седьмую долю сверх единицы. Попутно, на глубине 30 и только там: * «зачистка поля» в проверках не держалась — у твари есть откат воскрешения, и с нулевым таймером она оживала тем же шагом. Лечение вне схватки и расход света на стоянке молча мерили бой. На восьмом этаже тварей мало, и проверка проходила по удаче; * порог «стоянка бесплатна» стоял в единицах заряда, а тот же шаг стоит дороже с глубиной. Переведён в тайлы — и сразу показал, что отряд в строю проходит 0.8 тайла за две секунды «стоянки». Свет теперь списывается только в ADVANCING: топтание и драка бесплатны, тратит РАЗВЕДКА. Без этого таймер возвращался через чёрный ход. Формат профиля 4: токен предмета вида rifle@relic+dmg7,0:0.63. Миграции не требует — у старых токенов нет хвоста после '@'. --accept PASS без изменений: в песочнице находок нет вовсе. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:35:00 +07:00
ItemInstance item; // что лежит в ячейке (с аффиксами)
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
bool enabled = true;
};
struct Widgets
{
// 5 карточек + 3 слота + 16 ячеек общей сумки + 4 строки статов +
// 16 ячеек схрона + кнопки экранов.
//
// Считать это «с запасом» нельзя: при переполнении лишние виджеты молча
// не создаются, и экран теряет ХВОСТ — так после перехода на общую сумку
// из схрона пропали последние ячейки, а выглядело это как ошибка вёрстки.
static constexpr int MAX = 64;
static_assert(MAX >= NUM_AGENTS + 3 + SQUAD_BAG_SLOTS + SQ_STASH_CELLS + 8,
"виджетов экрана отряда больше, чем помещается");
Widget item[MAX];
int count = 0;
Widget& Add(UiPart part, int index, const UiRect& r)
{
Widget& w = item[(count < MAX) ? count : (MAX - 1)];
if (count < MAX) ++count;
w = Widget{};
w.part = part;
w.index = index;
w.rect = r;
return w;
}
const Widget* At(float fx, float fy) const
{
for (int i = 0; i < count; ++i)
if (item[i].rect.Has(fx, fy)) return &item[i];
return nullptr;
}
const Widget* Find(UiPart part, int index) const
{
for (int i = 0; i < count; ++i)
if (item[i].part == part && item[i].index == index) return &item[i];
return nullptr;
}
};
// Кнопка по центру кадра: прямоугольник считается по реальной ширине подписи,
// подпись потом рисуется ИЗ него же (rect + поле), а не отдельным центрированием.
// -----------------------------------------------------------------------------
// Подсказка под курсором.
//
// Без неё предмет — это имя и цветной квадратик, и что даёт VEST против JACKET,
// узнать было неоткуда: числа лежали в каталоге кода. С общей сумкой на
// шестнадцать мелких ячеек это перестало быть терпимым — в ячейку не влезает
// даже имя.
//
// Тексты латиницей: встроенный шрифт raylib кириллицы не знает и рисует её
// вопросами (CLAUDE.md). Это подпись НА ЭКРАНЕ, а не вывод в консоль.
// -----------------------------------------------------------------------------
constexpr int TIP_LINES_MAX = 7;
struct Tip
{
char lines[TIP_LINES_MAX][40] = {};
int count = 0;
void Add(const char* fmt, ...)
{
if (count >= TIP_LINES_MAX) return;
va_list args;
va_start(args, fmt);
vsnprintf(lines[count], sizeof(lines[0]), fmt, args);
va_end(args);
++count;
}
};
const char* KindName(ItemKind k)
{
switch (k)
{
case ItemKind::FIREARM: return "FIREARM";
case ItemKind::BLADE: return "BLADE";
case ItemKind::ARMOR: return "ARMOR";
case ItemKind::TRINKET: return "TRINKET";
case ItemKind::CONSUMABLE: return "SUPPLY";
default: return "-";
}
}
// Что вещь делает. Только те строки, которые у неё не нулевые: список из шести
// «+0%» читается как «ничего не даёт», хотя даёт.
Редкости и аффиксы: находка стала решением (M25) Предмет перестал быть строкой каталога. ItemInstance несёт редкость и до шести аффиксов, и «карабин» с первого этажа больше не равен «карабину» с сорокового. Четыре редкости, десять тегов, тир и бюджет от глубины. Аффиксы не заводят новых каналов влияния: каждый тег ложится в то, что уже есть — урон оружия, смесь стихий, пробитие, четыре числа бойца, ресурсы вылазки. У предмета по-прежнему нет ручки к правилу «не бить своих», и появиться ей неоткуда. ItemInstance неявно приводится к ItemId, поэтому полторы сотни мест, которым нужен ВИД вещи, остались как были. Цена решения — присваивание в ItemId молча теряет рулоны; таких мест три (подбор, схрон, запись профиля), и все они работают с экземпляром целиком. Замеры поймали три расхождения дизайн-документа с самим собой: * формула редкости противоречила таблице рядом с ней: 2.7% находок с аффиксами на первом этаже против 22%. Выбрана формула, наклон поднят до схождения с таблицей на дне (12% обычных на сотом); * бюджет пускал восемь высших аффиксов вместо обещанных шести; * предельная вещь собиралась к семидесятому этажу вместо сотого. Числа пересчитаны, документ исправлен: 6·cost(9) = 294 <= B(100) = 295 < 7·cost(9) = 343. Предельная вещь существует ровно на дне и ровно одна. Проверка в --descend меряет не бросок, а ПРОВОДКУ до бойца — там, где дважды умирали вес и стихии: аффиксы: урон 12.0 -> 18.0, здоровье 100 -> 140, броня 0.10 -> 0.25, пробитие 0.20, KIN 1.00 -> 0.60, CRY 0.40, сумма долей 1.000 Последнее число не украшение: конверсия обязана переводить урон, а не добавлять седьмую долю сверх единицы. Попутно, на глубине 30 и только там: * «зачистка поля» в проверках не держалась — у твари есть откат воскрешения, и с нулевым таймером она оживала тем же шагом. Лечение вне схватки и расход света на стоянке молча мерили бой. На восьмом этаже тварей мало, и проверка проходила по удаче; * порог «стоянка бесплатна» стоял в единицах заряда, а тот же шаг стоит дороже с глубиной. Переведён в тайлы — и сразу показал, что отряд в строю проходит 0.8 тайла за две секунды «стоянки». Свет теперь списывается только в ADVANCING: топтание и драка бесплатны, тратит РАЗВЕДКА. Без этого таймер возвращался через чёрный ход. Формат профиля 4: токен предмета вида rifle@relic+dmg7,0:0.63. Миграции не требует — у старых токенов нет хвоста после '@'. --accept PASS без изменений: в песочнице находок нет вовсе. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:35:00 +07:00
Tip ItemTip(const ItemInstance& it)
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
{
Редкости и аффиксы: находка стала решением (M25) Предмет перестал быть строкой каталога. ItemInstance несёт редкость и до шести аффиксов, и «карабин» с первого этажа больше не равен «карабину» с сорокового. Четыре редкости, десять тегов, тир и бюджет от глубины. Аффиксы не заводят новых каналов влияния: каждый тег ложится в то, что уже есть — урон оружия, смесь стихий, пробитие, четыре числа бойца, ресурсы вылазки. У предмета по-прежнему нет ручки к правилу «не бить своих», и появиться ей неоткуда. ItemInstance неявно приводится к ItemId, поэтому полторы сотни мест, которым нужен ВИД вещи, остались как были. Цена решения — присваивание в ItemId молча теряет рулоны; таких мест три (подбор, схрон, запись профиля), и все они работают с экземпляром целиком. Замеры поймали три расхождения дизайн-документа с самим собой: * формула редкости противоречила таблице рядом с ней: 2.7% находок с аффиксами на первом этаже против 22%. Выбрана формула, наклон поднят до схождения с таблицей на дне (12% обычных на сотом); * бюджет пускал восемь высших аффиксов вместо обещанных шести; * предельная вещь собиралась к семидесятому этажу вместо сотого. Числа пересчитаны, документ исправлен: 6·cost(9) = 294 <= B(100) = 295 < 7·cost(9) = 343. Предельная вещь существует ровно на дне и ровно одна. Проверка в --descend меряет не бросок, а ПРОВОДКУ до бойца — там, где дважды умирали вес и стихии: аффиксы: урон 12.0 -> 18.0, здоровье 100 -> 140, броня 0.10 -> 0.25, пробитие 0.20, KIN 1.00 -> 0.60, CRY 0.40, сумма долей 1.000 Последнее число не украшение: конверсия обязана переводить урон, а не добавлять седьмую долю сверх единицы. Попутно, на глубине 30 и только там: * «зачистка поля» в проверках не держалась — у твари есть откат воскрешения, и с нулевым таймером она оживала тем же шагом. Лечение вне схватки и расход света на стоянке молча мерили бой. На восьмом этаже тварей мало, и проверка проходила по удаче; * порог «стоянка бесплатна» стоял в единицах заряда, а тот же шаг стоит дороже с глубиной. Переведён в тайлы — и сразу показал, что отряд в строю проходит 0.8 тайла за две секунды «стоянки». Свет теперь списывается только в ADVANCING: топтание и драка бесплатны, тратит РАЗВЕДКА. Без этого таймер возвращался через чёрный ход. Формат профиля 4: токен предмета вида rifle@relic+dmg7,0:0.63. Миграции не требует — у старых токенов нет хвоста после '@'. --accept PASS без изменений: в песочнице находок нет вовсе. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:35:00 +07:00
const ItemDef d = ItemDefOf(it.id);
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
Tip t;
Редкости и аффиксы: находка стала решением (M25) Предмет перестал быть строкой каталога. ItemInstance несёт редкость и до шести аффиксов, и «карабин» с первого этажа больше не равен «карабину» с сорокового. Четыре редкости, десять тегов, тир и бюджет от глубины. Аффиксы не заводят новых каналов влияния: каждый тег ложится в то, что уже есть — урон оружия, смесь стихий, пробитие, четыре числа бойца, ресурсы вылазки. У предмета по-прежнему нет ручки к правилу «не бить своих», и появиться ей неоткуда. ItemInstance неявно приводится к ItemId, поэтому полторы сотни мест, которым нужен ВИД вещи, остались как были. Цена решения — присваивание в ItemId молча теряет рулоны; таких мест три (подбор, схрон, запись профиля), и все они работают с экземпляром целиком. Замеры поймали три расхождения дизайн-документа с самим собой: * формула редкости противоречила таблице рядом с ней: 2.7% находок с аффиксами на первом этаже против 22%. Выбрана формула, наклон поднят до схождения с таблицей на дне (12% обычных на сотом); * бюджет пускал восемь высших аффиксов вместо обещанных шести; * предельная вещь собиралась к семидесятому этажу вместо сотого. Числа пересчитаны, документ исправлен: 6·cost(9) = 294 <= B(100) = 295 < 7·cost(9) = 343. Предельная вещь существует ровно на дне и ровно одна. Проверка в --descend меряет не бросок, а ПРОВОДКУ до бойца — там, где дважды умирали вес и стихии: аффиксы: урон 12.0 -> 18.0, здоровье 100 -> 140, броня 0.10 -> 0.25, пробитие 0.20, KIN 1.00 -> 0.60, CRY 0.40, сумма долей 1.000 Последнее число не украшение: конверсия обязана переводить урон, а не добавлять седьмую долю сверх единицы. Попутно, на глубине 30 и только там: * «зачистка поля» в проверках не держалась — у твари есть откат воскрешения, и с нулевым таймером она оживала тем же шагом. Лечение вне схватки и расход света на стоянке молча мерили бой. На восьмом этаже тварей мало, и проверка проходила по удаче; * порог «стоянка бесплатна» стоял в единицах заряда, а тот же шаг стоит дороже с глубиной. Переведён в тайлы — и сразу показал, что отряд в строю проходит 0.8 тайла за две секунды «стоянки». Свет теперь списывается только в ADVANCING: топтание и драка бесплатны, тратит РАЗВЕДКА. Без этого таймер возвращался через чёрный ход. Формат профиля 4: токен предмета вида rifle@relic+dmg7,0:0.63. Миграции не требует — у старых токенов нет хвоста после '@'. --accept PASS без изменений: в песочнице находок нет вовсе. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:35:00 +07:00
// Редкость в заголовке, а не отдельной строкой: игрок читает подсказку
// сверху вниз и первое, что ему нужно знать про найденную вещь, — стоит ли
// читать дальше.
if (it.rarity != Rarity::COMMON)
t.Add("%s %s (%s)", RarityName(it.rarity), d.name, KindName(d.kind));
else
t.Add("%s (%s)", d.name, KindName(d.kind));
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
t.Add("WEIGHT %.1f", double(d.weight));
if (d.armor > 0.0f) t.Add("ARMOR +%d%%", int(d.armor * 100.0f + 0.5f));
if (d.speedMul != 1.0f) t.Add("SPEED %+d%%", int((d.speedMul - 1.0f) * 100.0f));
if (d.hpBonus > 0.0f) t.Add("HP +%d", int(d.hpBonus));
if (d.sightMul != 1.0f) t.Add("SIGHT %+d%%", int((d.sightMul - 1.0f) * 100.0f));
if (d.heal > 0.0f) t.Add("HEALS %d WHEN SAFE", int(d.heal));
if (d.rounds > 0) t.Add("%d ROUNDS WHEN DRY", d.rounds);
Свет как расходник: заряд тратится за путь, а не за время (M23) Второй ресурс вылазки после патронов. Фонарь садится, погас — обзор падает до 35%, а твари замечают отряд в 1.35 раза дальше: отряд не слепнет, он меняется с тварями местами. Батарея встаёт сама, как бинт. Главное решение — расход за ПРОЙДЕННЫЙ ПУТЬ, а не за время. Первая версия считала по секундам, то есть была таймером, которого в игре нет по прямому требованию к жанру: садящийся сам собой фонарь торопил бы игрока ровно как секундомер, только без циферблата, и думать над этажом стало бы дорого. За расстояние получается тот же размен, что и с весом, но с другой стороны: вес делает дорогим «унести всё», свет — «обшарить всё». Заряд общий на отряд и едет вниз вместе с ним: обновляйся он на этаже, это был бы ресурс этажа, а не вылазки, и никуда бы не гнал. Чем глубже, тем дороже шаг, а этажи внизу длиннее — глубина жмёт на свет дважды. Проверка в --descend меряет правило целиком, и первая её строка не про баланс, а про жанр: заряд на стоянке обязан стоять. Сам измеритель тоже был неправ — делил потраченное на смещение отряда, а не на пройденный путь, и показывал 1.55 за тайл при 1.10 в каталоге. свет: стоянка 0.2, ход 1.10 за тайл при 1.10 в каталоге глубина 8: заряда на 383 тайла, это 4.0 прямых пути до спуска обзор 1.00 -> 0.35 в темноте, батарея встала сама Хватает ли заряда на этаж считается в этажах и по средней длине забега: путь до спуска скачет вдвое от сида, и порог по одному этажу ловил бы удачу, а не баланс. --accept PASS без изменений: в песочнице света бесконечно, как и патронов. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 02:12:05 +07:00
if (d.charge > 0.0f) t.Add("RELIGHTS LANTERN: %d PACES", int(d.charge));
Редкости и аффиксы: находка стала решением (M25) Предмет перестал быть строкой каталога. ItemInstance несёт редкость и до шести аффиксов, и «карабин» с первого этажа больше не равен «карабину» с сорокового. Четыре редкости, десять тегов, тир и бюджет от глубины. Аффиксы не заводят новых каналов влияния: каждый тег ложится в то, что уже есть — урон оружия, смесь стихий, пробитие, четыре числа бойца, ресурсы вылазки. У предмета по-прежнему нет ручки к правилу «не бить своих», и появиться ей неоткуда. ItemInstance неявно приводится к ItemId, поэтому полторы сотни мест, которым нужен ВИД вещи, остались как были. Цена решения — присваивание в ItemId молча теряет рулоны; таких мест три (подбор, схрон, запись профиля), и все они работают с экземпляром целиком. Замеры поймали три расхождения дизайн-документа с самим собой: * формула редкости противоречила таблице рядом с ней: 2.7% находок с аффиксами на первом этаже против 22%. Выбрана формула, наклон поднят до схождения с таблицей на дне (12% обычных на сотом); * бюджет пускал восемь высших аффиксов вместо обещанных шести; * предельная вещь собиралась к семидесятому этажу вместо сотого. Числа пересчитаны, документ исправлен: 6·cost(9) = 294 <= B(100) = 295 < 7·cost(9) = 343. Предельная вещь существует ровно на дне и ровно одна. Проверка в --descend меряет не бросок, а ПРОВОДКУ до бойца — там, где дважды умирали вес и стихии: аффиксы: урон 12.0 -> 18.0, здоровье 100 -> 140, броня 0.10 -> 0.25, пробитие 0.20, KIN 1.00 -> 0.60, CRY 0.40, сумма долей 1.000 Последнее число не украшение: конверсия обязана переводить урон, а не добавлять седьмую долю сверх единицы. Попутно, на глубине 30 и только там: * «зачистка поля» в проверках не держалась — у твари есть откат воскрешения, и с нулевым таймером она оживала тем же шагом. Лечение вне схватки и расход света на стоянке молча мерили бой. На восьмом этаже тварей мало, и проверка проходила по удаче; * порог «стоянка бесплатна» стоял в единицах заряда, а тот же шаг стоит дороже с глубиной. Переведён в тайлы — и сразу показал, что отряд в строю проходит 0.8 тайла за две секунды «стоянки». Свет теперь списывается только в ADVANCING: топтание и драка бесплатны, тратит РАЗВЕДКА. Без этого таймер возвращался через чёрный ход. Формат профиля 4: токен предмета вида rifle@relic+dmg7,0:0.63. Миграции не требует — у старых токенов нет хвоста после '@'. --accept PASS без изменений: в песочнице находок нет вовсе. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:35:00 +07:00
// Аффиксы — отдельным блоком после базовых чисел. Смешивать их со статами
// предмета нельзя: базовое приходит с видом вещи и одинаково у всех, а
// выкатанное — то, ради чего эту конкретную вещь подобрали.
for (int i = 0; i < it.Count(); ++i)
{
char line[64];
AffixLabel(it.affix[i], line, int(sizeof(line)));
t.Add("%s", line);
}
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
return t;
}
Tip StatTip(int line, const Agent& a)
{
Tip t;
switch (line)
{
case 0:
t.Add("HEALTH");
t.Add("CARRIES DOWN BETWEEN FLOORS.");
t.Add("BANDAGE IS USED WHEN SAFE.");
break;
case 1:
t.Add("ARMOR");
t.Add("SHARE OF DAMAGE ABSORBED,");
t.Add("CAPPED AT %d%%.", int(ARMOR_CAP * 100.0f));
break;
case 2:
t.Add("SPEED AND LOAD");
t.Add("GEAR AND WEIGHT BOTH SLOW YOU.");
t.Add("FREE UNTIL %.1f, CRAWL AT %.0f.", double(CARRY_LIMIT), double(CARRY_MAX));
t.Add("SQUAD BAG SPLITS OVER SURVIVORS.");
break;
default:
t.Add("SIGHT");
t.Add("HOW FAR THIS FIGHTER SEES.");
t.Add("DEPTH SHRINKS IT FOR EVERYONE.");
break;
}
(void)a;
return t;
}
void DrawTip(const Layout& l, const Tip& t, int mx, int my)
{
if (t.count <= 0) return;
const int pad = 4;
const int step = 9;
int wide = 0;
for (int i = 0; i < t.count; ++i) wide = std::max(wide, TextW(l, t.lines[i], 8));
UiRect box{mx + 8, my + 8, wide + pad * 2, t.count * step + pad * 2 - 2};
// Подсказка не должна вылезать за кадр: у края экрана она переезжает на
// другую сторону курсора, а не обрезается.
if (box.x + box.w > INTERNAL_W - 2) box.x = mx - 8 - box.w;
if (box.y + box.h > INTERNAL_H - 2) box.y = INTERNAL_H - 2 - box.h;
if (box.x < 2) box.x = 2;
if (box.y < 2) box.y = 2;
Box(l, box, Color{10, 10, 13, 245});
BoxLines(l, box, COL_EDGE);
for (int i = 0; i < t.count; ++i)
Text(l, t.lines[i], box.x + pad, box.y + pad + i * step, 8,
(i == 0) ? COL_TEXT : COL_FAINT);
}
UiRect Button(const Layout& l, const char* s, int fy, int size)
{
UiRect r;
r.w = TextW(l, s, size) + BTN_PAD_X * 2;
r.h = size + BTN_PAD_Y * 2;
r.x = (l.fw - r.w) / 2;
r.y = fy - BTN_PAD_Y;
return r;
}
UiRect BagRect(int slot)
{
return UiRect{SQ_BAG_X + (slot % SQ_BAG_COLS) * (SQ_BAG_W + SQ_BAG_GX),
SQ_BAG_Y + (slot / SQ_BAG_COLS) * (SQ_BAG_H + SQ_BAG_GY), SQ_BAG_W, SQ_BAG_H};
}
UiRect StashRect(int cell)
{
return UiRect{SQ_STASH_X + (cell % SQ_STASH_COLS) * (SQ_STASH_W + SQ_STASH_GX),
SQ_STASH_Y + (cell / SQ_STASH_COLS) * (SQ_STASH_H + SQ_STASH_GY), SQ_STASH_W,
SQ_STASH_H};
}
Материалы и мастерская: вещь можно доработать (M27) Аффиксы сделали находку решением, но решением одноразовым: выпало как выпало. Материалы дают второй способ получить нужное — не «повезло», а «доработал». Пять материалов, пять операций, экран WORKSHOP. Главное правило экономики — материал добывается ТОЛЬКО В СВОЕЙ ПОЛОСЕ: био-ядра с плотских этажей, крио с мёрзлых. Это единственная причина возвращаться на средние глубины, когда рекорд ушёл вниз; без полос весь контент, кроме последнего акта, умирает в день прохождения. Материалы — валюта, а не предметы: не занимают ячеек, не весят, в схроне не лежат. Но теряются как добыча: зачисляются только при эвакуации. Три уточнения операций, каждое против конкретного обхода: перековка меняет только значения (иначе это новый бросок предмета в обход редкости), прививка не ставит уже стоящий тег (иначе «+урон» шесть раз), возвышается случайный аффикс (иначе все предметы сойдутся к одному). --tool craft поймал, что материалы бесплатны. С твари падало по её danger, и вылазка на восьмидесятом этаже приносила тысячу ядер при цене доводки вещи в шестьсот: 0.6 вылазки на предмет вместо восьми. Правило заменено на «одно ядро с мелочи, два с крупной» — стало 6.2. Сам инструмент при этом тоже считал неверно: брал доход из бюджета опасности этажа, хотя на этаж влезает не больше MAX_LEVEL_SPAWNS тварей. Бюджет уходит не в количество тварей, а в их силу, и доход упирается в потолок расстановки с сорокового этажа. Глубина даёт не количество материалов, а качество предметов — и вместе с ним цену доводки. Залом замерен на тысяче попыток: рассыпалось 11.6% при заявленных 12%, плата снимается всегда, слот теряется всегда. «У меня трижды не рассыпалось» не доказательство ни в какую сторону. Пункт меню WORKSHOP проверен --ui-check — той самой проверкой, которая появилась вчера из-за пункта UPGRADES, молча закрывавшего игру. Формат профиля 5: строка mat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 07:07:46 +07:00
UiRect CraftSlotRect(int cell)
{
return UiRect{WS_SLOT_X + (cell % WS_SLOT_COLS) * (WS_SLOT_W + WS_SLOT_GX),
WS_SLOT_Y + (cell / WS_SLOT_COLS) * (WS_SLOT_H + WS_SLOT_GY), WS_SLOT_W,
WS_SLOT_H};
}
UiRect CraftOpRect(int op) { return UiRect{WS_OP_X, WS_OP_Y + op * WS_OP_STEP, WS_OP_W, WS_OP_H}; }
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
void AddButtons(Widgets& out, const Layout& l, UiPart part, const char* const* names, int count,
int y0, int step, int size)
{
for (int i = 0; i < count; ++i)
{
Widget& w = out.Add(part, i, Button(l, names[i], y0 + i * step, size));
w.text = names[i];
w.size = size;
}
}
// Единственное место, где рождаются прямоугольники. И Update, и Draw зовут его
// с одним и тем же Layout, поэтому видят ровно одну разметку.
void BuildWidgets(const Ui& ui, const Game& game, const Layout& l, Widgets& out)
{
out.count = 0;
switch (ui.screen)
{
case Screen::GAME:
return;
case Screen::MENU:
AddButtons(out, l, UiPart::MENU_ITEM, MENU_ITEMS, MENU_COUNT, MENU_Y0, MENU_STEP,
MENU_SIZE);
// «Продолжить» до начала вылазки ведёт в никуда: пункт лучше
// погасить, чем показывать игроку тупик.
out.item[0].enabled = ui.started;
return;
case Screen::PAUSE:
AddButtons(out, l, UiPart::PAUSE_ITEM, PAUSE_ITEMS, PAUSE_COUNT, PAUSE_Y0, PAUSE_STEP,
PAUSE_SIZE);
return;
case Screen::DEFEAT:
AddButtons(out, l, UiPart::DEFEAT_ITEM, DEFEAT_ITEMS, DEFEAT_COUNT, DEFEAT_Y0,
DEFEAT_STEP, DEFEAT_SIZE);
return;
case Screen::SUMMARY:
AddButtons(out, l, UiPart::SUMMARY_ITEM, SUMMARY_ITEMS, SUMMARY_COUNT, SUM_Y0, SUM_STEP,
SUM_SIZE);
return;
case Screen::UPGRADE:
Дерево улучшений вместо четырёх кнопок (M32) Четыре строки покупок (патроны, переноска, обзор, живучесть) удалены целиком. Выбора в них не было: купить в итоге можно было всё, порядок ни на что не влиял, и через две вылазки экран превращался в кнопку "продолжить". Дерево: пять ветвей по восемь узлов, у каждого пять уровней. Ветви - это пять способов закрыть разрыв из закона масштаба: стволы, клинки, стихии, снабжение, отряд. Ветвь открывается цепочкой - узел доступен, когда взят предыдущий в этой же ветви. Свободная сетка читалась бы как список; цепочка читается как путь. Ни один узел не заводит нового канала влияния: каждый кладёт своё число туда, куда уже кладут снаряжение и аффиксы. Дерево будет расти, и первый же "особенный" узел со своей веткой в коде превратил бы SyncLoadout в свалку. ЭКОНОМИКА. --tool economy поймал, что первая версия дерева стоит 9% одного прохода 1..100: опыт растёт с глубиной квадратично, а дерево с фиксированными ценами выкупается целиком за половину глубокого прохода, и выбор кончается к сороковому этажу навсегда. Введено общее удорожание: каждая вложенная точка поднимает цену всех следующих (цена = база · 1.5^уровень · (1 + 0.15 · вложено)). Полное дерево стоит 34 млн против 41 млн, заложенных в дизайне, то есть около шести с половиной полных проходов. Старые покупки из профиля читаются, но не применяются: потраченный на них опыт возвращается в банк. Молча съесть чужие двадцать часов нельзя. Проверка меряет ПРОВОДКУ по всем пяти ветвям, а не таблицу узлов: дерево: куплено уровней 45, закрытый узел закрыт урон ствола 12.0 -> 14.4, здоровье 100 -> 150, скорость 1.06 -> 1.22 парирование 0.34 -> 0.49, свет 420 -> 620 Попутно: в проекте hollow бегуны и хваты уехали с первых этажей глубже - там одиночка с ножом гиб на первом же этаже по той же причине, по которой это было в основном проекте. Формат профиля 7: строки tree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 23:24:50 +07:00
for (int i = 0; i < TREE_NODES; ++i)
{
Widget& w = out.Add(UiPart::UPGRADE_ITEM, i, TreeNodeRect(i));
// Закрытый узел виден, но не нажимается: игрок должен видеть,
// КУДА ведёт ветвь, до того как в неё вложится.
w.enabled = game.profile && game.profile->NodeUnlocked(i);
}
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
return;
case Screen::DEPTH:
{
// Чекпоинты открываются достигнутой глубиной. Показываются самые
// глубокие: начинать сверху при открытом двадцатом этаже никто не
// будет, а место на экране конечно.
const int total = game.profile ? game.profile->CheckpointCount() : 1;
const int shown = (total < DEPTH_MAX_SHOWN) ? total : DEPTH_MAX_SHOWN;
const int first = total - shown;
for (int i = 0; i < shown; ++i)
{
const int d = Profile::CheckpointDepth(first + i);
static char labels[DEPTH_MAX_SHOWN][16];
std::snprintf(labels[i], sizeof(labels[i]), "DEPTH %d", d);
Widget& wg = out.Add(UiPart::DEPTH_ITEM, d,
Button(l, labels[i], DEPTH_Y0 + i * DEPTH_STEP, DEPTH_SIZE));
wg.text = labels[i];
wg.size = DEPTH_SIZE;
}
return;
}
case Screen::SQUAD:
{
Отряд растёт с одного оперативника до пяти (M24) Игра начинается ОДНИМ человеком: тактический нож, слабый фонарь, куртка. Остальные четверо нанимаются за опыт по кривой 600/2400/9600/38400 — самая дорогая покупка в игре и намеренно. Одиночка с ножом это первое впечатление, и продать второго бойца за одну вылазку значило бы выбросить весь хоррор начала: вдвоём уже не страшно. NUM_AGENTS осталась величиной времени сборки и означает теперь ВМЕСТИМОСТЬ, а не размер отряда: делать её динамической это сотня мест ради числа, которое всё равно упирается в пять. Занятость слота это Agent::present, и он отличается от alive. Правила боя читают только alive — для них пустой слот и павший одно, и это верно. Разница нужна там, где показывают человека: у павшего есть тело на полу и перечёркнутая карточка, у пустого слота нет ни того, ни другого. Проверка в --descend меряет цепочку «в профиле -> на этаже» и потолок, а главное — переживает ли одиночка первую схватку: отряд: в профиле 1 -> на этаже 1, после найма 2, цена 600, сверх потолка отказ одиночка с ножом на первом этаже: выжил, 100 -> 41, подошло 2, убил 2 Первая версия этой проверки не измеряла ничего: боец стоял минуту, к нему не подошёл никто, и «выжил» было пустым словом. Теперь отряд ставится рядом с тварью. Попутно найдено и исправлено: Profile::Save три вехи не писала ни версию формата, ни улучшения. Купленный запас патронов действовал до конца запуска и молча пропадал при чтении, а профиль без версии читался как формат 1. Круговой прогон этого не видел, потому что сверял три поля из десяти и делал это на пустом профиле, где всё сходится само. Теперь он сверяет поля списком и на набитом профиле. Формат профиля 3: добавлена строка squad. Старый профиль играл впятером и получает пятерых при миграции — отобрать их значило бы съесть отряд, которого игрок не терял. --accept PASS без изменений: без профиля отряд по-прежнему впятером. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 02:29:19 +07:00
// Карточки только на занятых бойцов: пустой слот — это не боец без
// снаряжения, это никто.
int card = 0;
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
for (int i = 0; i < NUM_AGENTS; ++i)
{
Отряд растёт с одного оперативника до пяти (M24) Игра начинается ОДНИМ человеком: тактический нож, слабый фонарь, куртка. Остальные четверо нанимаются за опыт по кривой 600/2400/9600/38400 — самая дорогая покупка в игре и намеренно. Одиночка с ножом это первое впечатление, и продать второго бойца за одну вылазку значило бы выбросить весь хоррор начала: вдвоём уже не страшно. NUM_AGENTS осталась величиной времени сборки и означает теперь ВМЕСТИМОСТЬ, а не размер отряда: делать её динамической это сотня мест ради числа, которое всё равно упирается в пять. Занятость слота это Agent::present, и он отличается от alive. Правила боя читают только alive — для них пустой слот и павший одно, и это верно. Разница нужна там, где показывают человека: у павшего есть тело на полу и перечёркнутая карточка, у пустого слота нет ни того, ни другого. Проверка в --descend меряет цепочку «в профиле -> на этаже» и потолок, а главное — переживает ли одиночка первую схватку: отряд: в профиле 1 -> на этаже 1, после найма 2, цена 600, сверх потолка отказ одиночка с ножом на первом этаже: выжил, 100 -> 41, подошло 2, убил 2 Первая версия этой проверки не измеряла ничего: боец стоял минуту, к нему не подошёл никто, и «выжил» было пустым словом. Теперь отряд ставится рядом с тварью. Попутно найдено и исправлено: Profile::Save три вехи не писала ни версию формата, ни улучшения. Купленный запас патронов действовал до конца запуска и молча пропадал при чтении, а профиль без версии читался как формат 1. Круговой прогон этого не видел, потому что сверял три поля из десяти и делал это на пустом профиле, где всё сходится само. Теперь он сверяет поля списком и на набитом профиле. Формат профиля 3: добавлена строка squad. Старый профиль играл впятером и получает пятерых при миграции — отобрать их значило бы съесть отряд, которого игрок не терял. --accept PASS без изменений: без профиля отряд по-прежнему впятером. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 02:29:19 +07:00
if (!game.squad.agents[i].present) continue;
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
Widget& w = out.Add(UiPart::AGENT, i,
Отряд растёт с одного оперативника до пяти (M24) Игра начинается ОДНИМ человеком: тактический нож, слабый фонарь, куртка. Остальные четверо нанимаются за опыт по кривой 600/2400/9600/38400 — самая дорогая покупка в игре и намеренно. Одиночка с ножом это первое впечатление, и продать второго бойца за одну вылазку значило бы выбросить весь хоррор начала: вдвоём уже не страшно. NUM_AGENTS осталась величиной времени сборки и означает теперь ВМЕСТИМОСТЬ, а не размер отряда: делать её динамической это сотня мест ради числа, которое всё равно упирается в пять. Занятость слота это Agent::present, и он отличается от alive. Правила боя читают только alive — для них пустой слот и павший одно, и это верно. Разница нужна там, где показывают человека: у павшего есть тело на полу и перечёркнутая карточка, у пустого слота нет ни того, ни другого. Проверка в --descend меряет цепочку «в профиле -> на этаже» и потолок, а главное — переживает ли одиночка первую схватку: отряд: в профиле 1 -> на этаже 1, после найма 2, цена 600, сверх потолка отказ одиночка с ножом на первом этаже: выжил, 100 -> 41, подошло 2, убил 2 Первая версия этой проверки не измеряла ничего: боец стоял минуту, к нему не подошёл никто, и «выжил» было пустым словом. Теперь отряд ставится рядом с тварью. Попутно найдено и исправлено: Profile::Save три вехи не писала ни версию формата, ни улучшения. Купленный запас патронов действовал до конца запуска и молча пропадал при чтении, а профиль без версии читался как формат 1. Круговой прогон этого не видел, потому что сверял три поля из десяти и делал это на пустом профиле, где всё сходится само. Теперь он сверяет поля списком и на набитом профиле. Формат профиля 3: добавлена строка squad. Старый профиль играл впятером и получает пятерых при миграции — отобрать их значило бы съесть отряд, которого игрок не терял. --accept PASS без изменений: без профиля отряд по-прежнему впятером. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 02:29:19 +07:00
UiRect{SQ_CARD_X + (card++) * SQ_CARD_STEP, SQ_CARD_Y,
SQ_CARD_W, SQ_CARD_H});
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
w.item = game.squad.agents[i].held;
}
const Agent& a = game.squad.agents[ui.agentIndex];
const Inventory* inv = game.world.Get<Inventory>(a.self);
if (!inv) return;
const ItemId worn[EQUIP_COUNT] = {inv->weapon, inv->armor, inv->trinket};
for (int i = 0; i < EQUIP_COUNT; ++i)
{
Widget& w = out.Add(EQUIP_PARTS[i], 0,
UiRect{SQ_EQUIP_X, SQ_EQUIP_Y + i * SQ_EQUIP_STEP, SQ_EQUIP_W,
SQ_EQUIP_H});
w.text = EQUIP_LABELS[i];
w.item = worn[i];
}
// Сумка ОБЩАЯ: она не зависит от выбранного бойца, и переложить
// из неё может любой (sim/items.h).
for (int s = 0; s < SQUAD_BAG_SLOTS; ++s)
{
Widget& w = out.Add(UiPart::BAG, s, BagRect(s));
w.item = game.squad.bag.slots[s];
}
// Строки характеристик тоже наводимы. Читать «SPEED 95%» и гадать,
// почему 95, — это не информация, а загадка; подсказка отвечает.
for (int i = 0; i < SQ_STAT_LINES; ++i)
out.Add(UiPart::STAT, i,
UiRect{SQ_STAT_X - 3, SQ_STAT_Y + i * SQ_STAT_STEP - 1, 150,
SQ_STAT_STEP});
// Схрон. Ячейки за концом списка тоже интерактивны: именно в них
// предмет КЛАДЁТСЯ, и без них схрон можно было бы только разбирать.
Материалы и мастерская: вещь можно доработать (M27) Аффиксы сделали находку решением, но решением одноразовым: выпало как выпало. Материалы дают второй способ получить нужное — не «повезло», а «доработал». Пять материалов, пять операций, экран WORKSHOP. Главное правило экономики — материал добывается ТОЛЬКО В СВОЕЙ ПОЛОСЕ: био-ядра с плотских этажей, крио с мёрзлых. Это единственная причина возвращаться на средние глубины, когда рекорд ушёл вниз; без полос весь контент, кроме последнего акта, умирает в день прохождения. Материалы — валюта, а не предметы: не занимают ячеек, не весят, в схроне не лежат. Но теряются как добыча: зачисляются только при эвакуации. Три уточнения операций, каждое против конкретного обхода: перековка меняет только значения (иначе это новый бросок предмета в обход редкости), прививка не ставит уже стоящий тег (иначе «+урон» шесть раз), возвышается случайный аффикс (иначе все предметы сойдутся к одному). --tool craft поймал, что материалы бесплатны. С твари падало по её danger, и вылазка на восьмидесятом этаже приносила тысячу ядер при цене доводки вещи в шестьсот: 0.6 вылазки на предмет вместо восьми. Правило заменено на «одно ядро с мелочи, два с крупной» — стало 6.2. Сам инструмент при этом тоже считал неверно: брал доход из бюджета опасности этажа, хотя на этаж влезает не больше MAX_LEVEL_SPAWNS тварей. Бюджет уходит не в количество тварей, а в их силу, и доход упирается в потолок расстановки с сорокового этажа. Глубина даёт не количество материалов, а качество предметов — и вместе с ним цену доводки. Залом замерен на тысяче попыток: рассыпалось 11.6% при заявленных 12%, плата снимается всегда, слот теряется всегда. «У меня трижды не рассыпалось» не доказательство ни в какую сторону. Пункт меню WORKSHOP проверен --ui-check — той самой проверкой, которая появилась вчера из-за пункта UPGRADES, молча закрывавшего игру. Формат профиля 5: строка mat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 07:07:46 +07:00
// (продолжение ниже)
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
if (game.profile)
{
for (int c = 0; c < SQ_STASH_CELLS; ++c)
{
const int k = ui.stashScroll + c;
Widget& w = out.Add(UiPart::STASH, k, StashRect(c));
w.item = (k < game.profile->StashCount()) ? game.profile->stash[size_t(k)]
Редкости и аффиксы: находка стала решением (M25) Предмет перестал быть строкой каталога. ItemInstance несёт редкость и до шести аффиксов, и «карабин» с первого этажа больше не равен «карабину» с сорокового. Четыре редкости, десять тегов, тир и бюджет от глубины. Аффиксы не заводят новых каналов влияния: каждый тег ложится в то, что уже есть — урон оружия, смесь стихий, пробитие, четыре числа бойца, ресурсы вылазки. У предмета по-прежнему нет ручки к правилу «не бить своих», и появиться ей неоткуда. ItemInstance неявно приводится к ItemId, поэтому полторы сотни мест, которым нужен ВИД вещи, остались как были. Цена решения — присваивание в ItemId молча теряет рулоны; таких мест три (подбор, схрон, запись профиля), и все они работают с экземпляром целиком. Замеры поймали три расхождения дизайн-документа с самим собой: * формула редкости противоречила таблице рядом с ней: 2.7% находок с аффиксами на первом этаже против 22%. Выбрана формула, наклон поднят до схождения с таблицей на дне (12% обычных на сотом); * бюджет пускал восемь высших аффиксов вместо обещанных шести; * предельная вещь собиралась к семидесятому этажу вместо сотого. Числа пересчитаны, документ исправлен: 6·cost(9) = 294 <= B(100) = 295 < 7·cost(9) = 343. Предельная вещь существует ровно на дне и ровно одна. Проверка в --descend меряет не бросок, а ПРОВОДКУ до бойца — там, где дважды умирали вес и стихии: аффиксы: урон 12.0 -> 18.0, здоровье 100 -> 140, броня 0.10 -> 0.25, пробитие 0.20, KIN 1.00 -> 0.60, CRY 0.40, сумма долей 1.000 Последнее число не украшение: конверсия обязана переводить урон, а не добавлять седьмую долю сверх единицы. Попутно, на глубине 30 и только там: * «зачистка поля» в проверках не держалась — у твари есть откат воскрешения, и с нулевым таймером она оживала тем же шагом. Лечение вне схватки и расход света на стоянке молча мерили бой. На восьмом этаже тварей мало, и проверка проходила по удаче; * порог «стоянка бесплатна» стоял в единицах заряда, а тот же шаг стоит дороже с глубиной. Переведён в тайлы — и сразу показал, что отряд в строю проходит 0.8 тайла за две секунды «стоянки». Свет теперь списывается только в ADVANCING: топтание и драка бесплатны, тратит РАЗВЕДКА. Без этого таймер возвращался через чёрный ход. Формат профиля 4: токен предмета вида rifle@relic+dmg7,0:0.63. Миграции не требует — у старых токенов нет хвоста после '@'. --accept PASS без изменений: в песочнице находок нет вовсе. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:35:00 +07:00
: ItemInstance{};
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
}
}
return;
}
Материалы и мастерская: вещь можно доработать (M27) Аффиксы сделали находку решением, но решением одноразовым: выпало как выпало. Материалы дают второй способ получить нужное — не «повезло», а «доработал». Пять материалов, пять операций, экран WORKSHOP. Главное правило экономики — материал добывается ТОЛЬКО В СВОЕЙ ПОЛОСЕ: био-ядра с плотских этажей, крио с мёрзлых. Это единственная причина возвращаться на средние глубины, когда рекорд ушёл вниз; без полос весь контент, кроме последнего акта, умирает в день прохождения. Материалы — валюта, а не предметы: не занимают ячеек, не весят, в схроне не лежат. Но теряются как добыча: зачисляются только при эвакуации. Три уточнения операций, каждое против конкретного обхода: перековка меняет только значения (иначе это новый бросок предмета в обход редкости), прививка не ставит уже стоящий тег (иначе «+урон» шесть раз), возвышается случайный аффикс (иначе все предметы сойдутся к одному). --tool craft поймал, что материалы бесплатны. С твари падало по её danger, и вылазка на восьмидесятом этаже приносила тысячу ядер при цене доводки вещи в шестьсот: 0.6 вылазки на предмет вместо восьми. Правило заменено на «одно ядро с мелочи, два с крупной» — стало 6.2. Сам инструмент при этом тоже считал неверно: брал доход из бюджета опасности этажа, хотя на этаж влезает не больше MAX_LEVEL_SPAWNS тварей. Бюджет уходит не в количество тварей, а в их силу, и доход упирается в потолок расстановки с сорокового этажа. Глубина даёт не количество материалов, а качество предметов — и вместе с ним цену доводки. Залом замерен на тысяче попыток: рассыпалось 11.6% при заявленных 12%, плата снимается всегда, слот теряется всегда. «У меня трижды не рассыпалось» не доказательство ни в какую сторону. Пункт меню WORKSHOP проверен --ui-check — той самой проверкой, которая появилась вчера из-за пункта UPGRADES, молча закрывавшего игру. Формат профиля 5: строка mat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 07:07:46 +07:00
case Screen::WORKSHOP:
{
if (!game.profile) return;
for (int c = 0; c < WS_SLOT_CELLS; ++c)
{
const int k = ui.stashScroll + c;
Widget& w = out.Add(UiPart::CRAFT_SLOT, k, CraftSlotRect(c));
w.item = (k < game.profile->StashCount()) ? game.profile->stash[size_t(k)]
: ItemInstance{};
w.enabled = k < game.profile->StashCount();
}
for (int o = 0; o < int(CraftOp::COUNT); ++o)
{
Widget& w = out.Add(UiPart::CRAFT_ITEM, o, CraftOpRect(o));
w.text = CraftOpName(CraftOp(o));
}
Крафт стал выбором: закалка, отпечаток, направленные операции (M33) Прежние пять операций были СЛЕПЫМИ: перековка бросала всё заново, прививка ставила случайный тег, отсечение снимало последний аффикс, возвышение поднимало случайный. Игрок не принимал ни одного решения - жал кнопку и смотрел, что выпало. Это не крафт, это второй бросок предмета за материалы. Теперь половина операций направленные: игрок показывает пальцем, какой аффикс трогать, и платит за это вдвое - вот это уже решение ("перебросить всё дёшево или снять один дорого"). Два новых глагола: ЗАКАЛКА двигает рулон выбранного аффикса к максимуму его тира на треть остатка. Дёшево, понятно, повторяемо - то самое "доработать почти хорошую вещь". Тир не трогает: за ступень платят возвышением. ОТПЕЧАТОК переносит лучший аффикс с ДРУГОЙ вещи, сжигая её. Именно он превращает хлам из схрона в ресурс, а не в мусор, который лежит. Правило "один тег один раз" им не обходится, бюджет проверяется. В мастерской: клик по вещи кладёт её на верстак, повторный клик по той же назначает донором (помечается кровью - он сгорит, и видеть это надо ДО нажатия). Строки аффиксов стали кнопками: по ним выбирается цель. Проверка меряет НАПРАВЛЕННОСТЬ, а не факт срабатывания: направленность: отсечение по цели, возвышение по цели, закалка двигает рулон, отпечаток перенёс и сжёг донора Отсечение пробуется на СРЕДНЕМ аффиксе из трёх: операция, которая "работает", снимая последний, прошла бы и слепую версию. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 23:37:07 +07:00
// Строки аффиксов на верстаке — тоже кнопки: по ним выбирают ЦЕЛЬ
// направленной операции (sim/craft.h).
if (ui.craftIndex >= 0 && ui.craftIndex < game.profile->StashCount())
{
const ItemInstance& bench = game.profile->stash[size_t(ui.craftIndex)];
for (int a = 0; a < bench.Count(); ++a)
out.Add(UiPart::CRAFT_AFFIX, a,
UiRect{WS_SLOT_X, 198 + a * 9, 210, 9});
}
Материалы и мастерская: вещь можно доработать (M27) Аффиксы сделали находку решением, но решением одноразовым: выпало как выпало. Материалы дают второй способ получить нужное — не «повезло», а «доработал». Пять материалов, пять операций, экран WORKSHOP. Главное правило экономики — материал добывается ТОЛЬКО В СВОЕЙ ПОЛОСЕ: био-ядра с плотских этажей, крио с мёрзлых. Это единственная причина возвращаться на средние глубины, когда рекорд ушёл вниз; без полос весь контент, кроме последнего акта, умирает в день прохождения. Материалы — валюта, а не предметы: не занимают ячеек, не весят, в схроне не лежат. Но теряются как добыча: зачисляются только при эвакуации. Три уточнения операций, каждое против конкретного обхода: перековка меняет только значения (иначе это новый бросок предмета в обход редкости), прививка не ставит уже стоящий тег (иначе «+урон» шесть раз), возвышается случайный аффикс (иначе все предметы сойдутся к одному). --tool craft поймал, что материалы бесплатны. С твари падало по её danger, и вылазка на восьмидесятом этаже приносила тысячу ядер при цене доводки вещи в шестьсот: 0.6 вылазки на предмет вместо восьми. Правило заменено на «одно ядро с мелочи, два с крупной» — стало 6.2. Сам инструмент при этом тоже считал неверно: брал доход из бюджета опасности этажа, хотя на этаж влезает не больше MAX_LEVEL_SPAWNS тварей. Бюджет уходит не в количество тварей, а в их силу, и доход упирается в потолок расстановки с сорокового этажа. Глубина даёт не количество материалов, а качество предметов — и вместе с ним цену доводки. Залом замерен на тысяче попыток: рассыпалось 11.6% при заявленных 12%, плата снимается всегда, слот теряется всегда. «У меня трижды не рассыпалось» не доказательство ни в какую сторону. Пункт меню WORKSHOP проверен --ui-check — той самой проверкой, которая появилась вчера из-за пункта UPGRADES, молча закрывавшего игру. Формат профиля 5: строка mat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 07:07:46 +07:00
return;
}
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
}
}
bool Hovered(const Ui& ui, const Widget& w)
{
return ui.hoverPart == w.part && ui.hoverIndex == w.index;
}
// -----------------------------------------------------------------------------
// Правила инвентаря. Класс предмета должен подходить слоту, и это проверяется
// в ОБЕ стороны: предмет уезжает в новую ячейку, а лежавшее там — в старую.
// Без второй проверки перетаскивание карабина на бинт оставило бы бойца
// с бинтом в руках.
// -----------------------------------------------------------------------------
bool IsSlot(UiPart p)
{
return p == UiPart::HANDS || p == UiPart::ARMOR || p == UiPart::POCKET ||
p == UiPart::BAG || p == UiPart::STASH;
}
bool SlotAccepts(UiPart part, ItemId id)
{
switch (part)
{
// Пустых рук не бывает: отряд не носит воздух. ItemId::NONE — не оружие,
// поэтому проверка на класс заодно закрывает и этот случай.
case UiPart::HANDS: return IsWeaponItem(id);
case UiPart::ARMOR: return id == ItemId::NONE || ItemDefOf(id).kind == ItemKind::ARMOR;
case UiPart::POCKET: return id == ItemId::NONE || ItemDefOf(id).kind == ItemKind::TRINKET;
case UiPart::BAG: return true; // сумка берёт что угодно, на то она и сумка
case UiPart::STASH: return true; // схрон тем более
default: return false;
}
}
bool CanDrop(UiPart from, ItemId moved, UiPart to, ItemId there)
{
return SlotAccepts(to, moved) && SlotAccepts(from, there);
}
// Надетое живёт в инвентаре бойца, сумка — в отряде, поэтому указатель на
// ячейку берётся из двух разных мест. Вызывающему это знать незачем: для него
// всё это по-прежнему «слот, в котором лежит вещь».
Редкости и аффиксы: находка стала решением (M25) Предмет перестал быть строкой каталога. ItemInstance несёт редкость и до шести аффиксов, и «карабин» с первого этажа больше не равен «карабину» с сорокового. Четыре редкости, десять тегов, тир и бюджет от глубины. Аффиксы не заводят новых каналов влияния: каждый тег ложится в то, что уже есть — урон оружия, смесь стихий, пробитие, четыре числа бойца, ресурсы вылазки. У предмета по-прежнему нет ручки к правилу «не бить своих», и появиться ей неоткуда. ItemInstance неявно приводится к ItemId, поэтому полторы сотни мест, которым нужен ВИД вещи, остались как были. Цена решения — присваивание в ItemId молча теряет рулоны; таких мест три (подбор, схрон, запись профиля), и все они работают с экземпляром целиком. Замеры поймали три расхождения дизайн-документа с самим собой: * формула редкости противоречила таблице рядом с ней: 2.7% находок с аффиксами на первом этаже против 22%. Выбрана формула, наклон поднят до схождения с таблицей на дне (12% обычных на сотом); * бюджет пускал восемь высших аффиксов вместо обещанных шести; * предельная вещь собиралась к семидесятому этажу вместо сотого. Числа пересчитаны, документ исправлен: 6·cost(9) = 294 <= B(100) = 295 < 7·cost(9) = 343. Предельная вещь существует ровно на дне и ровно одна. Проверка в --descend меряет не бросок, а ПРОВОДКУ до бойца — там, где дважды умирали вес и стихии: аффиксы: урон 12.0 -> 18.0, здоровье 100 -> 140, броня 0.10 -> 0.25, пробитие 0.20, KIN 1.00 -> 0.60, CRY 0.40, сумма долей 1.000 Последнее число не украшение: конверсия обязана переводить урон, а не добавлять седьмую долю сверх единицы. Попутно, на глубине 30 и только там: * «зачистка поля» в проверках не держалась — у твари есть откат воскрешения, и с нулевым таймером она оживала тем же шагом. Лечение вне схватки и расход света на стоянке молча мерили бой. На восьмом этаже тварей мало, и проверка проходила по удаче; * порог «стоянка бесплатна» стоял в единицах заряда, а тот же шаг стоит дороже с глубиной. Переведён в тайлы — и сразу показал, что отряд в строю проходит 0.8 тайла за две секунды «стоянки». Свет теперь списывается только в ADVANCING: топтание и драка бесплатны, тратит РАЗВЕДКА. Без этого таймер возвращался через чёрный ход. Формат профиля 4: токен предмета вида rifle@relic+dmg7,0:0.63. Миграции не требует — у старых токенов нет хвоста после '@'. --accept PASS без изменений: в песочнице находок нет вовсе. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:35:00 +07:00
ItemInstance* SlotPtr(Inventory& inv, SquadBag& bag, UiPart part, int index)
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
{
switch (part)
{
case UiPart::HANDS: return &inv.weapon;
case UiPart::ARMOR: return &inv.armor;
case UiPart::POCKET: return &inv.trinket;
case UiPart::BAG:
return (index >= 0 && index < SQUAD_BAG_SLOTS) ? &bag.slots[index] : nullptr;
default: return nullptr;
}
}
// Схрон живёт не в Inventory, а в профиле, и ячейка за концом списка — это
// место, куда предмет КЛАДЁТСЯ. Поэтому обмен со схроном разобран отдельно:
// указателя на несуществующую ячейку не бывает.
Редкости и аффиксы: находка стала решением (M25) Предмет перестал быть строкой каталога. ItemInstance несёт редкость и до шести аффиксов, и «карабин» с первого этажа больше не равен «карабину» с сорокового. Четыре редкости, десять тегов, тир и бюджет от глубины. Аффиксы не заводят новых каналов влияния: каждый тег ложится в то, что уже есть — урон оружия, смесь стихий, пробитие, четыре числа бойца, ресурсы вылазки. У предмета по-прежнему нет ручки к правилу «не бить своих», и появиться ей неоткуда. ItemInstance неявно приводится к ItemId, поэтому полторы сотни мест, которым нужен ВИД вещи, остались как были. Цена решения — присваивание в ItemId молча теряет рулоны; таких мест три (подбор, схрон, запись профиля), и все они работают с экземпляром целиком. Замеры поймали три расхождения дизайн-документа с самим собой: * формула редкости противоречила таблице рядом с ней: 2.7% находок с аффиксами на первом этаже против 22%. Выбрана формула, наклон поднят до схождения с таблицей на дне (12% обычных на сотом); * бюджет пускал восемь высших аффиксов вместо обещанных шести; * предельная вещь собиралась к семидесятому этажу вместо сотого. Числа пересчитаны, документ исправлен: 6·cost(9) = 294 <= B(100) = 295 < 7·cost(9) = 343. Предельная вещь существует ровно на дне и ровно одна. Проверка в --descend меряет не бросок, а ПРОВОДКУ до бойца — там, где дважды умирали вес и стихии: аффиксы: урон 12.0 -> 18.0, здоровье 100 -> 140, броня 0.10 -> 0.25, пробитие 0.20, KIN 1.00 -> 0.60, CRY 0.40, сумма долей 1.000 Последнее число не украшение: конверсия обязана переводить урон, а не добавлять седьмую долю сверх единицы. Попутно, на глубине 30 и только там: * «зачистка поля» в проверках не держалась — у твари есть откат воскрешения, и с нулевым таймером она оживала тем же шагом. Лечение вне схватки и расход света на стоянке молча мерили бой. На восьмом этаже тварей мало, и проверка проходила по удаче; * порог «стоянка бесплатна» стоял в единицах заряда, а тот же шаг стоит дороже с глубиной. Переведён в тайлы — и сразу показал, что отряд в строю проходит 0.8 тайла за две секунды «стоянки». Свет теперь списывается только в ADVANCING: топтание и драка бесплатны, тратит РАЗВЕДКА. Без этого таймер возвращался через чёрный ход. Формат профиля 4: токен предмета вида rifle@relic+dmg7,0:0.63. Миграции не требует — у старых токенов нет хвоста после '@'. --accept PASS без изменений: в песочнице находок нет вовсе. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:35:00 +07:00
ItemInstance StashTake(Profile& pr, int index)
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
{
Редкости и аффиксы: находка стала решением (M25) Предмет перестал быть строкой каталога. ItemInstance несёт редкость и до шести аффиксов, и «карабин» с первого этажа больше не равен «карабину» с сорокового. Четыре редкости, десять тегов, тир и бюджет от глубины. Аффиксы не заводят новых каналов влияния: каждый тег ложится в то, что уже есть — урон оружия, смесь стихий, пробитие, четыре числа бойца, ресурсы вылазки. У предмета по-прежнему нет ручки к правилу «не бить своих», и появиться ей неоткуда. ItemInstance неявно приводится к ItemId, поэтому полторы сотни мест, которым нужен ВИД вещи, остались как были. Цена решения — присваивание в ItemId молча теряет рулоны; таких мест три (подбор, схрон, запись профиля), и все они работают с экземпляром целиком. Замеры поймали три расхождения дизайн-документа с самим собой: * формула редкости противоречила таблице рядом с ней: 2.7% находок с аффиксами на первом этаже против 22%. Выбрана формула, наклон поднят до схождения с таблицей на дне (12% обычных на сотом); * бюджет пускал восемь высших аффиксов вместо обещанных шести; * предельная вещь собиралась к семидесятому этажу вместо сотого. Числа пересчитаны, документ исправлен: 6·cost(9) = 294 <= B(100) = 295 < 7·cost(9) = 343. Предельная вещь существует ровно на дне и ровно одна. Проверка в --descend меряет не бросок, а ПРОВОДКУ до бойца — там, где дважды умирали вес и стихии: аффиксы: урон 12.0 -> 18.0, здоровье 100 -> 140, броня 0.10 -> 0.25, пробитие 0.20, KIN 1.00 -> 0.60, CRY 0.40, сумма долей 1.000 Последнее число не украшение: конверсия обязана переводить урон, а не добавлять седьмую долю сверх единицы. Попутно, на глубине 30 и только там: * «зачистка поля» в проверках не держалась — у твари есть откат воскрешения, и с нулевым таймером она оживала тем же шагом. Лечение вне схватки и расход света на стоянке молча мерили бой. На восьмом этаже тварей мало, и проверка проходила по удаче; * порог «стоянка бесплатна» стоял в единицах заряда, а тот же шаг стоит дороже с глубиной. Переведён в тайлы — и сразу показал, что отряд в строю проходит 0.8 тайла за две секунды «стоянки». Свет теперь списывается только в ADVANCING: топтание и драка бесплатны, тратит РАЗВЕДКА. Без этого таймер возвращался через чёрный ход. Формат профиля 4: токен предмета вида rifle@relic+dmg7,0:0.63. Миграции не требует — у старых токенов нет хвоста после '@'. --accept PASS без изменений: в песочнице находок нет вовсе. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:35:00 +07:00
if (index < 0 || index >= pr.StashCount()) return ItemInstance{};
const ItemInstance out = pr.stash[size_t(index)];
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
pr.StashRemoveAt(index);
return out;
}
// false — схрон полон и предмет остался на месте. Молча терять его нельзя.
Редкости и аффиксы: находка стала решением (M25) Предмет перестал быть строкой каталога. ItemInstance несёт редкость и до шести аффиксов, и «карабин» с первого этажа больше не равен «карабину» с сорокового. Четыре редкости, десять тегов, тир и бюджет от глубины. Аффиксы не заводят новых каналов влияния: каждый тег ложится в то, что уже есть — урон оружия, смесь стихий, пробитие, четыре числа бойца, ресурсы вылазки. У предмета по-прежнему нет ручки к правилу «не бить своих», и появиться ей неоткуда. ItemInstance неявно приводится к ItemId, поэтому полторы сотни мест, которым нужен ВИД вещи, остались как были. Цена решения — присваивание в ItemId молча теряет рулоны; таких мест три (подбор, схрон, запись профиля), и все они работают с экземпляром целиком. Замеры поймали три расхождения дизайн-документа с самим собой: * формула редкости противоречила таблице рядом с ней: 2.7% находок с аффиксами на первом этаже против 22%. Выбрана формула, наклон поднят до схождения с таблицей на дне (12% обычных на сотом); * бюджет пускал восемь высших аффиксов вместо обещанных шести; * предельная вещь собиралась к семидесятому этажу вместо сотого. Числа пересчитаны, документ исправлен: 6·cost(9) = 294 <= B(100) = 295 < 7·cost(9) = 343. Предельная вещь существует ровно на дне и ровно одна. Проверка в --descend меряет не бросок, а ПРОВОДКУ до бойца — там, где дважды умирали вес и стихии: аффиксы: урон 12.0 -> 18.0, здоровье 100 -> 140, броня 0.10 -> 0.25, пробитие 0.20, KIN 1.00 -> 0.60, CRY 0.40, сумма долей 1.000 Последнее число не украшение: конверсия обязана переводить урон, а не добавлять седьмую долю сверх единицы. Попутно, на глубине 30 и только там: * «зачистка поля» в проверках не держалась — у твари есть откат воскрешения, и с нулевым таймером она оживала тем же шагом. Лечение вне схватки и расход света на стоянке молча мерили бой. На восьмом этаже тварей мало, и проверка проходила по удаче; * порог «стоянка бесплатна» стоял в единицах заряда, а тот же шаг стоит дороже с глубиной. Переведён в тайлы — и сразу показал, что отряд в строю проходит 0.8 тайла за две секунды «стоянки». Свет теперь списывается только в ADVANCING: топтание и драка бесплатны, тратит РАЗВЕДКА. Без этого таймер возвращался через чёрный ход. Формат профиля 4: токен предмета вида rifle@relic+dmg7,0:0.63. Миграции не требует — у старых токенов нет хвоста после '@'. --accept PASS без изменений: в песочнице находок нет вовсе. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:35:00 +07:00
bool StashPut(Profile& pr, int index, const ItemInstance& id)
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
{
Редкости и аффиксы: находка стала решением (M25) Предмет перестал быть строкой каталога. ItemInstance несёт редкость и до шести аффиксов, и «карабин» с первого этажа больше не равен «карабину» с сорокового. Четыре редкости, десять тегов, тир и бюджет от глубины. Аффиксы не заводят новых каналов влияния: каждый тег ложится в то, что уже есть — урон оружия, смесь стихий, пробитие, четыре числа бойца, ресурсы вылазки. У предмета по-прежнему нет ручки к правилу «не бить своих», и появиться ей неоткуда. ItemInstance неявно приводится к ItemId, поэтому полторы сотни мест, которым нужен ВИД вещи, остались как были. Цена решения — присваивание в ItemId молча теряет рулоны; таких мест три (подбор, схрон, запись профиля), и все они работают с экземпляром целиком. Замеры поймали три расхождения дизайн-документа с самим собой: * формула редкости противоречила таблице рядом с ней: 2.7% находок с аффиксами на первом этаже против 22%. Выбрана формула, наклон поднят до схождения с таблицей на дне (12% обычных на сотом); * бюджет пускал восемь высших аффиксов вместо обещанных шести; * предельная вещь собиралась к семидесятому этажу вместо сотого. Числа пересчитаны, документ исправлен: 6·cost(9) = 294 <= B(100) = 295 < 7·cost(9) = 343. Предельная вещь существует ровно на дне и ровно одна. Проверка в --descend меряет не бросок, а ПРОВОДКУ до бойца — там, где дважды умирали вес и стихии: аффиксы: урон 12.0 -> 18.0, здоровье 100 -> 140, броня 0.10 -> 0.25, пробитие 0.20, KIN 1.00 -> 0.60, CRY 0.40, сумма долей 1.000 Последнее число не украшение: конверсия обязана переводить урон, а не добавлять седьмую долю сверх единицы. Попутно, на глубине 30 и только там: * «зачистка поля» в проверках не держалась — у твари есть откат воскрешения, и с нулевым таймером она оживала тем же шагом. Лечение вне схватки и расход света на стоянке молча мерили бой. На восьмом этаже тварей мало, и проверка проходила по удаче; * порог «стоянка бесплатна» стоял в единицах заряда, а тот же шаг стоит дороже с глубиной. Переведён в тайлы — и сразу показал, что отряд в строю проходит 0.8 тайла за две секунды «стоянки». Свет теперь списывается только в ADVANCING: топтание и драка бесплатны, тратит РАЗВЕДКА. Без этого таймер возвращался через чёрный ход. Формат профиля 4: токен предмета вида rifle@relic+dmg7,0:0.63. Миграции не требует — у старых токенов нет хвоста после '@'. --accept PASS без изменений: в песочнице находок нет вовсе. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:35:00 +07:00
if (id.id == ItemId::NONE) return true;
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
if (index >= 0 && index < pr.StashCount())
{
pr.stash.insert(pr.stash.begin() + long(index), id);
return int(pr.stash.size()) <= STASH_CAP || (pr.stash.pop_back(), false);
}
return pr.StashPush(id);
}
// Выбросить вещь под ноги бойцу. Без этого решение о нагрузке одностороннее:
// набрал — и тащи, а «что бросить» превращается в «нечего было брать».
//
// Из РУК бросать нельзя: безоружный боец не бежит быстрее, он просто перестаёт
// быть бойцом. Ствол сначала меняют на что-то другое, и только потом бросают.
// Броню и карман — можно: снять плиту, чтобы дойти до лестницы, это ровно то
// решение, ради которого вес и заводился.
void DropToFloor(Ui& ui, Game& game, UiPart part, int index)
{
if (part == UiPart::HANDS || part == UiPart::STASH) return;
Agent& a = game.squad.agents[ui.agentIndex];
Inventory* inv = game.world.Get<Inventory>(a.self);
if (!inv) return;
Редкости и аффиксы: находка стала решением (M25) Предмет перестал быть строкой каталога. ItemInstance несёт редкость и до шести аффиксов, и «карабин» с первого этажа больше не равен «карабину» с сорокового. Четыре редкости, десять тегов, тир и бюджет от глубины. Аффиксы не заводят новых каналов влияния: каждый тег ложится в то, что уже есть — урон оружия, смесь стихий, пробитие, четыре числа бойца, ресурсы вылазки. У предмета по-прежнему нет ручки к правилу «не бить своих», и появиться ей неоткуда. ItemInstance неявно приводится к ItemId, поэтому полторы сотни мест, которым нужен ВИД вещи, остались как были. Цена решения — присваивание в ItemId молча теряет рулоны; таких мест три (подбор, схрон, запись профиля), и все они работают с экземпляром целиком. Замеры поймали три расхождения дизайн-документа с самим собой: * формула редкости противоречила таблице рядом с ней: 2.7% находок с аффиксами на первом этаже против 22%. Выбрана формула, наклон поднят до схождения с таблицей на дне (12% обычных на сотом); * бюджет пускал восемь высших аффиксов вместо обещанных шести; * предельная вещь собиралась к семидесятому этажу вместо сотого. Числа пересчитаны, документ исправлен: 6·cost(9) = 294 <= B(100) = 295 < 7·cost(9) = 343. Предельная вещь существует ровно на дне и ровно одна. Проверка в --descend меряет не бросок, а ПРОВОДКУ до бойца — там, где дважды умирали вес и стихии: аффиксы: урон 12.0 -> 18.0, здоровье 100 -> 140, броня 0.10 -> 0.25, пробитие 0.20, KIN 1.00 -> 0.60, CRY 0.40, сумма долей 1.000 Последнее число не украшение: конверсия обязана переводить урон, а не добавлять седьмую долю сверх единицы. Попутно, на глубине 30 и только там: * «зачистка поля» в проверках не держалась — у твари есть откат воскрешения, и с нулевым таймером она оживала тем же шагом. Лечение вне схватки и расход света на стоянке молча мерили бой. На восьмом этаже тварей мало, и проверка проходила по удаче; * порог «стоянка бесплатна» стоял в единицах заряда, а тот же шаг стоит дороже с глубиной. Переведён в тайлы — и сразу показал, что отряд в строю проходит 0.8 тайла за две секунды «стоянки». Свет теперь списывается только в ADVANCING: топтание и драка бесплатны, тратит РАЗВЕДКА. Без этого таймер возвращался через чёрный ход. Формат профиля 4: токен предмета вида rifle@relic+dmg7,0:0.63. Миграции не требует — у старых токенов нет хвоста после '@'. --accept PASS без изменений: в песочнице находок нет вовсе. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:35:00 +07:00
ItemInstance* slot = SlotPtr(*inv, game.squad.bag, part, index);
if (!slot || slot->Empty()) return;
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
// Задержка подбора обязательна: вещь ложится там же, где стоит боец, и
// без неё тот же шаг поднял бы её обратно.
SpawnFloorItem(game.world, *slot, a.pos, 2.0f);
Редкости и аффиксы: находка стала решением (M25) Предмет перестал быть строкой каталога. ItemInstance несёт редкость и до шести аффиксов, и «карабин» с первого этажа больше не равен «карабину» с сорокового. Четыре редкости, десять тегов, тир и бюджет от глубины. Аффиксы не заводят новых каналов влияния: каждый тег ложится в то, что уже есть — урон оружия, смесь стихий, пробитие, четыре числа бойца, ресурсы вылазки. У предмета по-прежнему нет ручки к правилу «не бить своих», и появиться ей неоткуда. ItemInstance неявно приводится к ItemId, поэтому полторы сотни мест, которым нужен ВИД вещи, остались как были. Цена решения — присваивание в ItemId молча теряет рулоны; таких мест три (подбор, схрон, запись профиля), и все они работают с экземпляром целиком. Замеры поймали три расхождения дизайн-документа с самим собой: * формула редкости противоречила таблице рядом с ней: 2.7% находок с аффиксами на первом этаже против 22%. Выбрана формула, наклон поднят до схождения с таблицей на дне (12% обычных на сотом); * бюджет пускал восемь высших аффиксов вместо обещанных шести; * предельная вещь собиралась к семидесятому этажу вместо сотого. Числа пересчитаны, документ исправлен: 6·cost(9) = 294 <= B(100) = 295 < 7·cost(9) = 343. Предельная вещь существует ровно на дне и ровно одна. Проверка в --descend меряет не бросок, а ПРОВОДКУ до бойца — там, где дважды умирали вес и стихии: аффиксы: урон 12.0 -> 18.0, здоровье 100 -> 140, броня 0.10 -> 0.25, пробитие 0.20, KIN 1.00 -> 0.60, CRY 0.40, сумма долей 1.000 Последнее число не украшение: конверсия обязана переводить урон, а не добавлять седьмую долю сверх единицы. Попутно, на глубине 30 и только там: * «зачистка поля» в проверках не держалась — у твари есть откат воскрешения, и с нулевым таймером она оживала тем же шагом. Лечение вне схватки и расход света на стоянке молча мерили бой. На восьмом этаже тварей мало, и проверка проходила по удаче; * порог «стоянка бесплатна» стоял в единицах заряда, а тот же шаг стоит дороже с глубиной. Переведён в тайлы — и сразу показал, что отряд в строю проходит 0.8 тайла за две секунды «стоянки». Свет теперь списывается только в ADVANCING: топтание и драка бесплатны, тратит РАЗВЕДКА. Без этого таймер возвращался через чёрный ход. Формат профиля 4: токен предмета вида rifle@relic+dmg7,0:0.63. Миграции не требует — у старых токенов нет хвоста после '@'. --accept PASS без изменений: в песочнице находок нет вовсе. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:35:00 +07:00
*slot = ItemInstance{};
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
}
void ApplyDrop(Ui& ui, Game& game, UiPart to, int toIndex)
{
Agent& a = game.squad.agents[ui.agentIndex];
Inventory* inv = game.world.Get<Inventory>(a.self);
if (!inv) return;
const bool fromStash = (ui.dragFrom == UiPart::STASH);
const bool toStash = (to == UiPart::STASH);
if (fromStash || toStash)
{
if (!game.profile) return;
Profile& pr = *game.profile;
if (fromStash && toStash)
{
// Перекладывание внутри схрона: порядок ячеек — единственное, чем
// игрок может им управлять, и ломать его незачем.
Редкости и аффиксы: находка стала решением (M25) Предмет перестал быть строкой каталога. ItemInstance несёт редкость и до шести аффиксов, и «карабин» с первого этажа больше не равен «карабину» с сорокового. Четыре редкости, десять тегов, тир и бюджет от глубины. Аффиксы не заводят новых каналов влияния: каждый тег ложится в то, что уже есть — урон оружия, смесь стихий, пробитие, четыре числа бойца, ресурсы вылазки. У предмета по-прежнему нет ручки к правилу «не бить своих», и появиться ей неоткуда. ItemInstance неявно приводится к ItemId, поэтому полторы сотни мест, которым нужен ВИД вещи, остались как были. Цена решения — присваивание в ItemId молча теряет рулоны; таких мест три (подбор, схрон, запись профиля), и все они работают с экземпляром целиком. Замеры поймали три расхождения дизайн-документа с самим собой: * формула редкости противоречила таблице рядом с ней: 2.7% находок с аффиксами на первом этаже против 22%. Выбрана формула, наклон поднят до схождения с таблицей на дне (12% обычных на сотом); * бюджет пускал восемь высших аффиксов вместо обещанных шести; * предельная вещь собиралась к семидесятому этажу вместо сотого. Числа пересчитаны, документ исправлен: 6·cost(9) = 294 <= B(100) = 295 < 7·cost(9) = 343. Предельная вещь существует ровно на дне и ровно одна. Проверка в --descend меряет не бросок, а ПРОВОДКУ до бойца — там, где дважды умирали вес и стихии: аффиксы: урон 12.0 -> 18.0, здоровье 100 -> 140, броня 0.10 -> 0.25, пробитие 0.20, KIN 1.00 -> 0.60, CRY 0.40, сумма долей 1.000 Последнее число не украшение: конверсия обязана переводить урон, а не добавлять седьмую долю сверх единицы. Попутно, на глубине 30 и только там: * «зачистка поля» в проверках не держалась — у твари есть откат воскрешения, и с нулевым таймером она оживала тем же шагом. Лечение вне схватки и расход света на стоянке молча мерили бой. На восьмом этаже тварей мало, и проверка проходила по удаче; * порог «стоянка бесплатна» стоял в единицах заряда, а тот же шаг стоит дороже с глубиной. Переведён в тайлы — и сразу показал, что отряд в строю проходит 0.8 тайла за две секунды «стоянки». Свет теперь списывается только в ADVANCING: топтание и драка бесплатны, тратит РАЗВЕДКА. Без этого таймер возвращался через чёрный ход. Формат профиля 4: токен предмета вида rifle@relic+dmg7,0:0.63. Миграции не требует — у старых токенов нет хвоста после '@'. --accept PASS без изменений: в песочнице находок нет вовсе. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:35:00 +07:00
const ItemInstance moved = StashTake(pr, ui.dragIndex);
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
StashPut(pr, toIndex, moved);
return;
}
Редкости и аффиксы: находка стала решением (M25) Предмет перестал быть строкой каталога. ItemInstance несёт редкость и до шести аффиксов, и «карабин» с первого этажа больше не равен «карабину» с сорокового. Четыре редкости, десять тегов, тир и бюджет от глубины. Аффиксы не заводят новых каналов влияния: каждый тег ложится в то, что уже есть — урон оружия, смесь стихий, пробитие, четыре числа бойца, ресурсы вылазки. У предмета по-прежнему нет ручки к правилу «не бить своих», и появиться ей неоткуда. ItemInstance неявно приводится к ItemId, поэтому полторы сотни мест, которым нужен ВИД вещи, остались как были. Цена решения — присваивание в ItemId молча теряет рулоны; таких мест три (подбор, схрон, запись профиля), и все они работают с экземпляром целиком. Замеры поймали три расхождения дизайн-документа с самим собой: * формула редкости противоречила таблице рядом с ней: 2.7% находок с аффиксами на первом этаже против 22%. Выбрана формула, наклон поднят до схождения с таблицей на дне (12% обычных на сотом); * бюджет пускал восемь высших аффиксов вместо обещанных шести; * предельная вещь собиралась к семидесятому этажу вместо сотого. Числа пересчитаны, документ исправлен: 6·cost(9) = 294 <= B(100) = 295 < 7·cost(9) = 343. Предельная вещь существует ровно на дне и ровно одна. Проверка в --descend меряет не бросок, а ПРОВОДКУ до бойца — там, где дважды умирали вес и стихии: аффиксы: урон 12.0 -> 18.0, здоровье 100 -> 140, броня 0.10 -> 0.25, пробитие 0.20, KIN 1.00 -> 0.60, CRY 0.40, сумма долей 1.000 Последнее число не украшение: конверсия обязана переводить урон, а не добавлять седьмую долю сверх единицы. Попутно, на глубине 30 и только там: * «зачистка поля» в проверках не держалась — у твари есть откат воскрешения, и с нулевым таймером она оживала тем же шагом. Лечение вне схватки и расход света на стоянке молча мерили бой. На восьмом этаже тварей мало, и проверка проходила по удаче; * порог «стоянка бесплатна» стоял в единицах заряда, а тот же шаг стоит дороже с глубиной. Переведён в тайлы — и сразу показал, что отряд в строю проходит 0.8 тайла за две секунды «стоянки». Свет теперь списывается только в ADVANCING: топтание и драка бесплатны, тратит РАЗВЕДКА. Без этого таймер возвращался через чёрный ход. Формат профиля 4: токен предмета вида rifle@relic+dmg7,0:0.63. Миграции не требует — у старых токенов нет хвоста после '@'. --accept PASS без изменений: в песочнице находок нет вовсе. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:35:00 +07:00
ItemInstance* slot = SlotPtr(*inv, game.squad.bag, fromStash ? to : ui.dragFrom,
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
fromStash ? toIndex : ui.dragIndex);
if (!slot) return;
if (fromStash)
{
// Из схрона в слот: то, что лежало в слоте, уезжает в схрон. Если
// схрон переполнен, обмен не состоится целиком — иначе вещь из
// слота исчезла бы.
Редкости и аффиксы: находка стала решением (M25) Предмет перестал быть строкой каталога. ItemInstance несёт редкость и до шести аффиксов, и «карабин» с первого этажа больше не равен «карабину» с сорокового. Четыре редкости, десять тегов, тир и бюджет от глубины. Аффиксы не заводят новых каналов влияния: каждый тег ложится в то, что уже есть — урон оружия, смесь стихий, пробитие, четыре числа бойца, ресурсы вылазки. У предмета по-прежнему нет ручки к правилу «не бить своих», и появиться ей неоткуда. ItemInstance неявно приводится к ItemId, поэтому полторы сотни мест, которым нужен ВИД вещи, остались как были. Цена решения — присваивание в ItemId молча теряет рулоны; таких мест три (подбор, схрон, запись профиля), и все они работают с экземпляром целиком. Замеры поймали три расхождения дизайн-документа с самим собой: * формула редкости противоречила таблице рядом с ней: 2.7% находок с аффиксами на первом этаже против 22%. Выбрана формула, наклон поднят до схождения с таблицей на дне (12% обычных на сотом); * бюджет пускал восемь высших аффиксов вместо обещанных шести; * предельная вещь собиралась к семидесятому этажу вместо сотого. Числа пересчитаны, документ исправлен: 6·cost(9) = 294 <= B(100) = 295 < 7·cost(9) = 343. Предельная вещь существует ровно на дне и ровно одна. Проверка в --descend меряет не бросок, а ПРОВОДКУ до бойца — там, где дважды умирали вес и стихии: аффиксы: урон 12.0 -> 18.0, здоровье 100 -> 140, броня 0.10 -> 0.25, пробитие 0.20, KIN 1.00 -> 0.60, CRY 0.40, сумма долей 1.000 Последнее число не украшение: конверсия обязана переводить урон, а не добавлять седьмую долю сверх единицы. Попутно, на глубине 30 и только там: * «зачистка поля» в проверках не держалась — у твари есть откат воскрешения, и с нулевым таймером она оживала тем же шагом. Лечение вне схватки и расход света на стоянке молча мерили бой. На восьмом этаже тварей мало, и проверка проходила по удаче; * порог «стоянка бесплатна» стоял в единицах заряда, а тот же шаг стоит дороже с глубиной. Переведён в тайлы — и сразу показал, что отряд в строю проходит 0.8 тайла за две секунды «стоянки». Свет теперь списывается только в ADVANCING: топтание и драка бесплатны, тратит РАЗВЕДКА. Без этого таймер возвращался через чёрный ход. Формат профиля 4: токен предмета вида rifle@relic+dmg7,0:0.63. Миграции не требует — у старых токенов нет хвоста после '@'. --accept PASS без изменений: в песочнице находок нет вовсе. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:35:00 +07:00
const ItemInstance taken = pr.stash.empty() ? ItemInstance{} : StashTake(pr, ui.dragIndex);
if (taken.Empty()) return;
const ItemInstance back = *slot;
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
if (!StashPut(pr, ui.dragIndex, back)) { StashPut(pr, ui.dragIndex, taken); return; }
*slot = taken;
}
else
{
Редкости и аффиксы: находка стала решением (M25) Предмет перестал быть строкой каталога. ItemInstance несёт редкость и до шести аффиксов, и «карабин» с первого этажа больше не равен «карабину» с сорокового. Четыре редкости, десять тегов, тир и бюджет от глубины. Аффиксы не заводят новых каналов влияния: каждый тег ложится в то, что уже есть — урон оружия, смесь стихий, пробитие, четыре числа бойца, ресурсы вылазки. У предмета по-прежнему нет ручки к правилу «не бить своих», и появиться ей неоткуда. ItemInstance неявно приводится к ItemId, поэтому полторы сотни мест, которым нужен ВИД вещи, остались как были. Цена решения — присваивание в ItemId молча теряет рулоны; таких мест три (подбор, схрон, запись профиля), и все они работают с экземпляром целиком. Замеры поймали три расхождения дизайн-документа с самим собой: * формула редкости противоречила таблице рядом с ней: 2.7% находок с аффиксами на первом этаже против 22%. Выбрана формула, наклон поднят до схождения с таблицей на дне (12% обычных на сотом); * бюджет пускал восемь высших аффиксов вместо обещанных шести; * предельная вещь собиралась к семидесятому этажу вместо сотого. Числа пересчитаны, документ исправлен: 6·cost(9) = 294 <= B(100) = 295 < 7·cost(9) = 343. Предельная вещь существует ровно на дне и ровно одна. Проверка в --descend меряет не бросок, а ПРОВОДКУ до бойца — там, где дважды умирали вес и стихии: аффиксы: урон 12.0 -> 18.0, здоровье 100 -> 140, броня 0.10 -> 0.25, пробитие 0.20, KIN 1.00 -> 0.60, CRY 0.40, сумма долей 1.000 Последнее число не украшение: конверсия обязана переводить урон, а не добавлять седьмую долю сверх единицы. Попутно, на глубине 30 и только там: * «зачистка поля» в проверках не держалась — у твари есть откат воскрешения, и с нулевым таймером она оживала тем же шагом. Лечение вне схватки и расход света на стоянке молча мерили бой. На восьмом этаже тварей мало, и проверка проходила по удаче; * порог «стоянка бесплатна» стоял в единицах заряда, а тот же шаг стоит дороже с глубиной. Переведён в тайлы — и сразу показал, что отряд в строю проходит 0.8 тайла за две секунды «стоянки». Свет теперь списывается только в ADVANCING: топтание и драка бесплатны, тратит РАЗВЕДКА. Без этого таймер возвращался через чёрный ход. Формат профиля 4: токен предмета вида rifle@relic+dmg7,0:0.63. Миграции не требует — у старых токенов нет хвоста после '@'. --accept PASS без изменений: в песочнице находок нет вовсе. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:35:00 +07:00
const ItemInstance moved = *slot;
if (moved.Empty()) return;
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
if (!StashPut(pr, toIndex, moved)) return; // схрон полон
Редкости и аффиксы: находка стала решением (M25) Предмет перестал быть строкой каталога. ItemInstance несёт редкость и до шести аффиксов, и «карабин» с первого этажа больше не равен «карабину» с сорокового. Четыре редкости, десять тегов, тир и бюджет от глубины. Аффиксы не заводят новых каналов влияния: каждый тег ложится в то, что уже есть — урон оружия, смесь стихий, пробитие, четыре числа бойца, ресурсы вылазки. У предмета по-прежнему нет ручки к правилу «не бить своих», и появиться ей неоткуда. ItemInstance неявно приводится к ItemId, поэтому полторы сотни мест, которым нужен ВИД вещи, остались как были. Цена решения — присваивание в ItemId молча теряет рулоны; таких мест три (подбор, схрон, запись профиля), и все они работают с экземпляром целиком. Замеры поймали три расхождения дизайн-документа с самим собой: * формула редкости противоречила таблице рядом с ней: 2.7% находок с аффиксами на первом этаже против 22%. Выбрана формула, наклон поднят до схождения с таблицей на дне (12% обычных на сотом); * бюджет пускал восемь высших аффиксов вместо обещанных шести; * предельная вещь собиралась к семидесятому этажу вместо сотого. Числа пересчитаны, документ исправлен: 6·cost(9) = 294 <= B(100) = 295 < 7·cost(9) = 343. Предельная вещь существует ровно на дне и ровно одна. Проверка в --descend меряет не бросок, а ПРОВОДКУ до бойца — там, где дважды умирали вес и стихии: аффиксы: урон 12.0 -> 18.0, здоровье 100 -> 140, броня 0.10 -> 0.25, пробитие 0.20, KIN 1.00 -> 0.60, CRY 0.40, сумма долей 1.000 Последнее число не украшение: конверсия обязана переводить урон, а не добавлять седьмую долю сверх единицы. Попутно, на глубине 30 и только там: * «зачистка поля» в проверках не держалась — у твари есть откат воскрешения, и с нулевым таймером она оживала тем же шагом. Лечение вне схватки и расход света на стоянке молча мерили бой. На восьмом этаже тварей мало, и проверка проходила по удаче; * порог «стоянка бесплатна» стоял в единицах заряда, а тот же шаг стоит дороже с глубиной. Переведён в тайлы — и сразу показал, что отряд в строю проходит 0.8 тайла за две секунды «стоянки». Свет теперь списывается только в ADVANCING: топтание и драка бесплатны, тратит РАЗВЕДКА. Без этого таймер возвращался через чёрный ход. Формат профиля 4: токен предмета вида rifle@relic+dmg7,0:0.63. Миграции не требует — у старых токенов нет хвоста после '@'. --accept PASS без изменений: в песочнице находок нет вовсе. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:35:00 +07:00
*slot = ItemInstance{};
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
}
// Руки могли опустеть или наполниться — сущность-оружие обязана
// поменяться вместе с ними.
if (ui.dragFrom == UiPart::HANDS || to == UiPart::HANDS)
EquipWeapon(game.world, a, inv->weapon);
return;
}
Редкости и аффиксы: находка стала решением (M25) Предмет перестал быть строкой каталога. ItemInstance несёт редкость и до шести аффиксов, и «карабин» с первого этажа больше не равен «карабину» с сорокового. Четыре редкости, десять тегов, тир и бюджет от глубины. Аффиксы не заводят новых каналов влияния: каждый тег ложится в то, что уже есть — урон оружия, смесь стихий, пробитие, четыре числа бойца, ресурсы вылазки. У предмета по-прежнему нет ручки к правилу «не бить своих», и появиться ей неоткуда. ItemInstance неявно приводится к ItemId, поэтому полторы сотни мест, которым нужен ВИД вещи, остались как были. Цена решения — присваивание в ItemId молча теряет рулоны; таких мест три (подбор, схрон, запись профиля), и все они работают с экземпляром целиком. Замеры поймали три расхождения дизайн-документа с самим собой: * формула редкости противоречила таблице рядом с ней: 2.7% находок с аффиксами на первом этаже против 22%. Выбрана формула, наклон поднят до схождения с таблицей на дне (12% обычных на сотом); * бюджет пускал восемь высших аффиксов вместо обещанных шести; * предельная вещь собиралась к семидесятому этажу вместо сотого. Числа пересчитаны, документ исправлен: 6·cost(9) = 294 <= B(100) = 295 < 7·cost(9) = 343. Предельная вещь существует ровно на дне и ровно одна. Проверка в --descend меряет не бросок, а ПРОВОДКУ до бойца — там, где дважды умирали вес и стихии: аффиксы: урон 12.0 -> 18.0, здоровье 100 -> 140, броня 0.10 -> 0.25, пробитие 0.20, KIN 1.00 -> 0.60, CRY 0.40, сумма долей 1.000 Последнее число не украшение: конверсия обязана переводить урон, а не добавлять седьмую долю сверх единицы. Попутно, на глубине 30 и только там: * «зачистка поля» в проверках не держалась — у твари есть откат воскрешения, и с нулевым таймером она оживала тем же шагом. Лечение вне схватки и расход света на стоянке молча мерили бой. На восьмом этаже тварей мало, и проверка проходила по удаче; * порог «стоянка бесплатна» стоял в единицах заряда, а тот же шаг стоит дороже с глубиной. Переведён в тайлы — и сразу показал, что отряд в строю проходит 0.8 тайла за две секунды «стоянки». Свет теперь списывается только в ADVANCING: топтание и драка бесплатны, тратит РАЗВЕДКА. Без этого таймер возвращался через чёрный ход. Формат профиля 4: токен предмета вида rifle@relic+dmg7,0:0.63. Миграции не требует — у старых токенов нет хвоста после '@'. --accept PASS без изменений: в песочнице находок нет вовсе. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:35:00 +07:00
ItemInstance* src = SlotPtr(*inv, game.squad.bag, ui.dragFrom, ui.dragIndex);
ItemInstance* dst = SlotPtr(*inv, game.squad.bag, to, toIndex);
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
if (!src || !dst || src == dst) return;
Редкости и аффиксы: находка стала решением (M25) Предмет перестал быть строкой каталога. ItemInstance несёт редкость и до шести аффиксов, и «карабин» с первого этажа больше не равен «карабину» с сорокового. Четыре редкости, десять тегов, тир и бюджет от глубины. Аффиксы не заводят новых каналов влияния: каждый тег ложится в то, что уже есть — урон оружия, смесь стихий, пробитие, четыре числа бойца, ресурсы вылазки. У предмета по-прежнему нет ручки к правилу «не бить своих», и появиться ей неоткуда. ItemInstance неявно приводится к ItemId, поэтому полторы сотни мест, которым нужен ВИД вещи, остались как были. Цена решения — присваивание в ItemId молча теряет рулоны; таких мест три (подбор, схрон, запись профиля), и все они работают с экземпляром целиком. Замеры поймали три расхождения дизайн-документа с самим собой: * формула редкости противоречила таблице рядом с ней: 2.7% находок с аффиксами на первом этаже против 22%. Выбрана формула, наклон поднят до схождения с таблицей на дне (12% обычных на сотом); * бюджет пускал восемь высших аффиксов вместо обещанных шести; * предельная вещь собиралась к семидесятому этажу вместо сотого. Числа пересчитаны, документ исправлен: 6·cost(9) = 294 <= B(100) = 295 < 7·cost(9) = 343. Предельная вещь существует ровно на дне и ровно одна. Проверка в --descend меряет не бросок, а ПРОВОДКУ до бойца — там, где дважды умирали вес и стихии: аффиксы: урон 12.0 -> 18.0, здоровье 100 -> 140, броня 0.10 -> 0.25, пробитие 0.20, KIN 1.00 -> 0.60, CRY 0.40, сумма долей 1.000 Последнее число не украшение: конверсия обязана переводить урон, а не добавлять седьмую долю сверх единицы. Попутно, на глубине 30 и только там: * «зачистка поля» в проверках не держалась — у твари есть откат воскрешения, и с нулевым таймером она оживала тем же шагом. Лечение вне схватки и расход света на стоянке молча мерили бой. На восьмом этаже тварей мало, и проверка проходила по удаче; * порог «стоянка бесплатна» стоял в единицах заряда, а тот же шаг стоит дороже с глубиной. Переведён в тайлы — и сразу показал, что отряд в строю проходит 0.8 тайла за две секунды «стоянки». Свет теперь списывается только в ADVANCING: топтание и драка бесплатны, тратит РАЗВЕДКА. Без этого таймер возвращался через чёрный ход. Формат профиля 4: токен предмета вида rifle@relic+dmg7,0:0.63. Миграции не требует — у старых токенов нет хвоста после '@'. --accept PASS без изменений: в песочнице находок нет вовсе. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:35:00 +07:00
const ItemInstance moved = *src;
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
*src = *dst;
*dst = moved;
// Руки поменялись — обязана поменяться и сущность-оружие. Иначе имя новое,
// а статы, класс и правила стрельбы остались от прежнего ствола.
if (ui.dragFrom == UiPart::HANDS || to == UiPart::HANDS)
EquipWeapon(game.world, a, inv->weapon);
}
Пункт UPGRADES выходил из игры; меню теперь проверяется (M26) Жалоба была «крашится при открытии апгрейдов». Не крашилось: пункт закрывал игру. В обработчике главного меню стоял `default: ui.quit`, а экран улучшений добавили четвёртым пунктом — обработчик о нём просто не узнал. Снаружи мгновенное закрытие окна неотличимо от падения. Починка на две строки, но интереснее причина, по которой это прожило четыре вехи: в проекте каждая механика меряется прогоном с кодом возврата, а меню не проверял никто. Все инструменты смотрят на СИМУЛЯЦИЮ. Поэтому заведён --ui-check: проходит по всем пунктам меню и требует одного — нажатие ВЕДЁТ КУДА-ТО, а из игры выходит ровно один пункт, QUIT. Плюс проверяет, что кнопки экрана улучшений действительно покупают. Окно при этом не открывается: UiActivate ничего из raylib не трогает. Проверка сперва запущена на СЛОМАННОЙ сборке и дала код 1 с диагнозом «пункт UPGRADES не делает ничего», и только после этого объявлена рабочей. Проверка, которую не видели падающей, ещё ничего не проверяет. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:51:58 +07:00
} // namespace
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
// -----------------------------------------------------------------------------
// Действия кнопок. Один вход и для клика, и для ENTER: иначе мышь и клавиатура
// начнут делать «почти одно и то же».
Пункт UPGRADES выходил из игры; меню теперь проверяется (M26) Жалоба была «крашится при открытии апгрейдов». Не крашилось: пункт закрывал игру. В обработчике главного меню стоял `default: ui.quit`, а экран улучшений добавили четвёртым пунктом — обработчик о нём просто не узнал. Снаружи мгновенное закрытие окна неотличимо от падения. Починка на две строки, но интереснее причина, по которой это прожило четыре вехи: в проекте каждая механика меряется прогоном с кодом возврата, а меню не проверял никто. Все инструменты смотрят на СИМУЛЯЦИЮ. Поэтому заведён --ui-check: проходит по всем пунктам меню и требует одного — нажатие ВЕДЁТ КУДА-ТО, а из игры выходит ровно один пункт, QUIT. Плюс проверяет, что кнопки экрана улучшений действительно покупают. Окно при этом не открывается: UiActivate ничего из raylib не трогает. Проверка сперва запущена на СЛОМАННОЙ сборке и дала код 1 с диагнозом «пункт UPGRADES не делает ничего», и только после этого объявлена рабочей. Проверка, которую не видели падающей, ещё ничего не проверяет. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:51:58 +07:00
//
// Не в анонимном пространстве: этот же вход дёргает `--ui-check` (ui/menu.h).
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
// -----------------------------------------------------------------------------
Пункт UPGRADES выходил из игры; меню теперь проверяется (M26) Жалоба была «крашится при открытии апгрейдов». Не крашилось: пункт закрывал игру. В обработчике главного меню стоял `default: ui.quit`, а экран улучшений добавили четвёртым пунктом — обработчик о нём просто не узнал. Снаружи мгновенное закрытие окна неотличимо от падения. Починка на две строки, но интереснее причина, по которой это прожило четыре вехи: в проекте каждая механика меряется прогоном с кодом возврата, а меню не проверял никто. Все инструменты смотрят на СИМУЛЯЦИЮ. Поэтому заведён --ui-check: проходит по всем пунктам меню и требует одного — нажатие ВЕДЁТ КУДА-ТО, а из игры выходит ровно один пункт, QUIT. Плюс проверяет, что кнопки экрана улучшений действительно покупают. Окно при этом не открывается: UiActivate ничего из raylib не трогает. Проверка сперва запущена на СЛОМАННОЙ сборке и дала код 1 с диагнозом «пункт UPGRADES не делает ничего», и только после этого объявлена рабочей. Проверка, которую не видели падающей, ещё ничего не проверяет. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:51:58 +07:00
void UiActivate(Ui& ui, Game& game, UiPart part, int index)
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
{
switch (part)
{
case UiPart::UPGRADE_ITEM:
// Покупка молча не проваливается: не хватило опыта — уровень
// остался прежним, и это видно на той же строке.
Дерево улучшений вместо четырёх кнопок (M32) Четыре строки покупок (патроны, переноска, обзор, живучесть) удалены целиком. Выбора в них не было: купить в итоге можно было всё, порядок ни на что не влиял, и через две вылазки экран превращался в кнопку "продолжить". Дерево: пять ветвей по восемь узлов, у каждого пять уровней. Ветви - это пять способов закрыть разрыв из закона масштаба: стволы, клинки, стихии, снабжение, отряд. Ветвь открывается цепочкой - узел доступен, когда взят предыдущий в этой же ветви. Свободная сетка читалась бы как список; цепочка читается как путь. Ни один узел не заводит нового канала влияния: каждый кладёт своё число туда, куда уже кладут снаряжение и аффиксы. Дерево будет расти, и первый же "особенный" узел со своей веткой в коде превратил бы SyncLoadout в свалку. ЭКОНОМИКА. --tool economy поймал, что первая версия дерева стоит 9% одного прохода 1..100: опыт растёт с глубиной квадратично, а дерево с фиксированными ценами выкупается целиком за половину глубокого прохода, и выбор кончается к сороковому этажу навсегда. Введено общее удорожание: каждая вложенная точка поднимает цену всех следующих (цена = база · 1.5^уровень · (1 + 0.15 · вложено)). Полное дерево стоит 34 млн против 41 млн, заложенных в дизайне, то есть около шести с половиной полных проходов. Старые покупки из профиля читаются, но не применяются: потраченный на них опыт возвращается в банк. Молча съесть чужие двадцать часов нельзя. Проверка меряет ПРОВОДКУ по всем пяти ветвям, а не таблицу узлов: дерево: куплено уровней 45, закрытый узел закрыт урон ствола 12.0 -> 14.4, здоровье 100 -> 150, скорость 1.06 -> 1.22 парирование 0.34 -> 0.49, свет 420 -> 620 Попутно: в проекте hollow бегуны и хваты уехали с первых этажей глубже - там одиночка с ножом гиб на первом же этаже по той же причине, по которой это было в основном проекте. Формат профиля 7: строки tree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 23:24:50 +07:00
// Покупка узла дерева. Молча не проваливается: не хватило опыта
// или узел закрыт — уровень остался прежним, и это видно на самом
// узле (sim/tree.h).
if (game.profile) game.profile->BuyNode(index);
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
return;
case UiPart::MENU_ITEM:
switch (index)
{
case 0: if (ui.started) ui.screen = Screen::GAME; break;
case 1:
// Вылазка начинается с выбора глубины: с открытых
// чекпоинтов. Это и есть ответ на «а если этажей десять
// тысяч» — проходить их заново каждый раз не нужно.
ui.depthIndex = 0;
ui.screen = Screen::DEPTH;
break;
case 2: ui.screen = Screen::SQUAD; break;
Пункт UPGRADES выходил из игры; меню теперь проверяется (M26) Жалоба была «крашится при открытии апгрейдов». Не крашилось: пункт закрывал игру. В обработчике главного меню стоял `default: ui.quit`, а экран улучшений добавили четвёртым пунктом — обработчик о нём просто не узнал. Снаружи мгновенное закрытие окна неотличимо от падения. Починка на две строки, но интереснее причина, по которой это прожило четыре вехи: в проекте каждая механика меряется прогоном с кодом возврата, а меню не проверял никто. Все инструменты смотрят на СИМУЛЯЦИЮ. Поэтому заведён --ui-check: проходит по всем пунктам меню и требует одного — нажатие ВЕДЁТ КУДА-ТО, а из игры выходит ровно один пункт, QUIT. Плюс проверяет, что кнопки экрана улучшений действительно покупают. Окно при этом не открывается: UiActivate ничего из raylib не трогает. Проверка сперва запущена на СЛОМАННОЙ сборке и дала код 1 с диагнозом «пункт UPGRADES не делает ничего», и только после этого объявлена рабочей. Проверка, которую не видели падающей, ещё ничего не проверяет. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:51:58 +07:00
case 3: ui.screen = Screen::UPGRADE; break;
Материалы и мастерская: вещь можно доработать (M27) Аффиксы сделали находку решением, но решением одноразовым: выпало как выпало. Материалы дают второй способ получить нужное — не «повезло», а «доработал». Пять материалов, пять операций, экран WORKSHOP. Главное правило экономики — материал добывается ТОЛЬКО В СВОЕЙ ПОЛОСЕ: био-ядра с плотских этажей, крио с мёрзлых. Это единственная причина возвращаться на средние глубины, когда рекорд ушёл вниз; без полос весь контент, кроме последнего акта, умирает в день прохождения. Материалы — валюта, а не предметы: не занимают ячеек, не весят, в схроне не лежат. Но теряются как добыча: зачисляются только при эвакуации. Три уточнения операций, каждое против конкретного обхода: перековка меняет только значения (иначе это новый бросок предмета в обход редкости), прививка не ставит уже стоящий тег (иначе «+урон» шесть раз), возвышается случайный аффикс (иначе все предметы сойдутся к одному). --tool craft поймал, что материалы бесплатны. С твари падало по её danger, и вылазка на восьмидесятом этаже приносила тысячу ядер при цене доводки вещи в шестьсот: 0.6 вылазки на предмет вместо восьми. Правило заменено на «одно ядро с мелочи, два с крупной» — стало 6.2. Сам инструмент при этом тоже считал неверно: брал доход из бюджета опасности этажа, хотя на этаж влезает не больше MAX_LEVEL_SPAWNS тварей. Бюджет уходит не в количество тварей, а в их силу, и доход упирается в потолок расстановки с сорокового этажа. Глубина даёт не количество материалов, а качество предметов — и вместе с ним цену доводки. Залом замерен на тысяче попыток: рассыпалось 11.6% при заявленных 12%, плата снимается всегда, слот теряется всегда. «У меня трижды не рассыпалось» не доказательство ни в какую сторону. Пункт меню WORKSHOP проверен --ui-check — той самой проверкой, которая появилась вчера из-за пункта UPGRADES, молча закрывавшего игру. Формат профиля 5: строка mat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 07:07:46 +07:00
case 4: ui.screen = Screen::WORKSHOP; break;
Пункт UPGRADES выходил из игры; меню теперь проверяется (M26) Жалоба была «крашится при открытии апгрейдов». Не крашилось: пункт закрывал игру. В обработчике главного меню стоял `default: ui.quit`, а экран улучшений добавили четвёртым пунктом — обработчик о нём просто не узнал. Снаружи мгновенное закрытие окна неотличимо от падения. Починка на две строки, но интереснее причина, по которой это прожило четыре вехи: в проекте каждая механика меряется прогоном с кодом возврата, а меню не проверял никто. Все инструменты смотрят на СИМУЛЯЦИЮ. Поэтому заведён --ui-check: проходит по всем пунктам меню и требует одного — нажатие ВЕДЁТ КУДА-ТО, а из игры выходит ровно один пункт, QUIT. Плюс проверяет, что кнопки экрана улучшений действительно покупают. Окно при этом не открывается: UiActivate ничего из raylib не трогает. Проверка сперва запущена на СЛОМАННОЙ сборке и дала код 1 с диагнозом «пункт UPGRADES не делает ничего», и только после этого объявлена рабочей. Проверка, которую не видели падающей, ещё ничего не проверяет. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:51:58 +07:00
// QUIT — последний пункт, и попасть сюда можно только им.
// Раньше здесь стоял default, и любой НЕ перечисленный пункт
// выходил из игры: экран улучшений появился четвёртым, а
// обработчик о нём не узнал — нажатие UPGRADES закрывало игру,
// и снаружи это выглядело как падение.
Материалы и мастерская: вещь можно доработать (M27) Аффиксы сделали находку решением, но решением одноразовым: выпало как выпало. Материалы дают второй способ получить нужное — не «повезло», а «доработал». Пять материалов, пять операций, экран WORKSHOP. Главное правило экономики — материал добывается ТОЛЬКО В СВОЕЙ ПОЛОСЕ: био-ядра с плотских этажей, крио с мёрзлых. Это единственная причина возвращаться на средние глубины, когда рекорд ушёл вниз; без полос весь контент, кроме последнего акта, умирает в день прохождения. Материалы — валюта, а не предметы: не занимают ячеек, не весят, в схроне не лежат. Но теряются как добыча: зачисляются только при эвакуации. Три уточнения операций, каждое против конкретного обхода: перековка меняет только значения (иначе это новый бросок предмета в обход редкости), прививка не ставит уже стоящий тег (иначе «+урон» шесть раз), возвышается случайный аффикс (иначе все предметы сойдутся к одному). --tool craft поймал, что материалы бесплатны. С твари падало по её danger, и вылазка на восьмидесятом этаже приносила тысячу ядер при цене доводки вещи в шестьсот: 0.6 вылазки на предмет вместо восьми. Правило заменено на «одно ядро с мелочи, два с крупной» — стало 6.2. Сам инструмент при этом тоже считал неверно: брал доход из бюджета опасности этажа, хотя на этаж влезает не больше MAX_LEVEL_SPAWNS тварей. Бюджет уходит не в количество тварей, а в их силу, и доход упирается в потолок расстановки с сорокового этажа. Глубина даёт не количество материалов, а качество предметов — и вместе с ним цену доводки. Залом замерен на тысяче попыток: рассыпалось 11.6% при заявленных 12%, плата снимается всегда, слот теряется всегда. «У меня трижды не рассыпалось» не доказательство ни в какую сторону. Пункт меню WORKSHOP проверен --ui-check — той самой проверкой, которая появилась вчера из-за пункта UPGRADES, молча закрывавшего игру. Формат профиля 5: строка mat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 07:07:46 +07:00
case 5: ui.quit = true; break;
Пункт UPGRADES выходил из игры; меню теперь проверяется (M26) Жалоба была «крашится при открытии апгрейдов». Не крашилось: пункт закрывал игру. В обработчике главного меню стоял `default: ui.quit`, а экран улучшений добавили четвёртым пунктом — обработчик о нём просто не узнал. Снаружи мгновенное закрытие окна неотличимо от падения. Починка на две строки, но интереснее причина, по которой это прожило четыре вехи: в проекте каждая механика меряется прогоном с кодом возврата, а меню не проверял никто. Все инструменты смотрят на СИМУЛЯЦИЮ. Поэтому заведён --ui-check: проходит по всем пунктам меню и требует одного — нажатие ВЕДЁТ КУДА-ТО, а из игры выходит ровно один пункт, QUIT. Плюс проверяет, что кнопки экрана улучшений действительно покупают. Окно при этом не открывается: UiActivate ничего из raylib не трогает. Проверка сперва запущена на СЛОМАННОЙ сборке и дала код 1 с диагнозом «пункт UPGRADES не делает ничего», и только после этого объявлена рабочей. Проверка, которую не видели падающей, ещё ничего не проверяет. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:51:58 +07:00
default: break;
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
}
return;
case UiPart::PAUSE_ITEM:
if (index == 0) ui.screen = Screen::GAME;
else if (index == 1) ui.screen = Screen::SQUAD;
else ui.screen = Screen::MENU;
return;
case UiPart::DEPTH_ITEM:
game.combatMode = true;
game.NewRun(index); // index — глубина чекпоинта, а не номер кнопки
ui.worldReset = true;
ui.started = true;
ui.screen = Screen::GAME;
return;
Материалы и мастерская: вещь можно доработать (M27) Аффиксы сделали находку решением, но решением одноразовым: выпало как выпало. Материалы дают второй способ получить нужное — не «повезло», а «доработал». Пять материалов, пять операций, экран WORKSHOP. Главное правило экономики — материал добывается ТОЛЬКО В СВОЕЙ ПОЛОСЕ: био-ядра с плотских этажей, крио с мёрзлых. Это единственная причина возвращаться на средние глубины, когда рекорд ушёл вниз; без полос весь контент, кроме последнего акта, умирает в день прохождения. Материалы — валюта, а не предметы: не занимают ячеек, не весят, в схроне не лежат. Но теряются как добыча: зачисляются только при эвакуации. Три уточнения операций, каждое против конкретного обхода: перековка меняет только значения (иначе это новый бросок предмета в обход редкости), прививка не ставит уже стоящий тег (иначе «+урон» шесть раз), возвышается случайный аффикс (иначе все предметы сойдутся к одному). --tool craft поймал, что материалы бесплатны. С твари падало по её danger, и вылазка на восьмидесятом этаже приносила тысячу ядер при цене доводки вещи в шестьсот: 0.6 вылазки на предмет вместо восьми. Правило заменено на «одно ядро с мелочи, два с крупной» — стало 6.2. Сам инструмент при этом тоже считал неверно: брал доход из бюджета опасности этажа, хотя на этаж влезает не больше MAX_LEVEL_SPAWNS тварей. Бюджет уходит не в количество тварей, а в их силу, и доход упирается в потолок расстановки с сорокового этажа. Глубина даёт не количество материалов, а качество предметов — и вместе с ним цену доводки. Залом замерен на тысяче попыток: рассыпалось 11.6% при заявленных 12%, плата снимается всегда, слот теряется всегда. «У меня трижды не рассыпалось» не доказательство ни в какую сторону. Пункт меню WORKSHOP проверен --ui-check — той самой проверкой, которая появилась вчера из-за пункта UPGRADES, молча закрывавшего игру. Формат профиля 5: строка mat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 07:07:46 +07:00
case UiPart::CRAFT_SLOT:
Крафт стал выбором: закалка, отпечаток, направленные операции (M33) Прежние пять операций были СЛЕПЫМИ: перековка бросала всё заново, прививка ставила случайный тег, отсечение снимало последний аффикс, возвышение поднимало случайный. Игрок не принимал ни одного решения - жал кнопку и смотрел, что выпало. Это не крафт, это второй бросок предмета за материалы. Теперь половина операций направленные: игрок показывает пальцем, какой аффикс трогать, и платит за это вдвое - вот это уже решение ("перебросить всё дёшево или снять один дорого"). Два новых глагола: ЗАКАЛКА двигает рулон выбранного аффикса к максимуму его тира на треть остатка. Дёшево, понятно, повторяемо - то самое "доработать почти хорошую вещь". Тир не трогает: за ступень платят возвышением. ОТПЕЧАТОК переносит лучший аффикс с ДРУГОЙ вещи, сжигая её. Именно он превращает хлам из схрона в ресурс, а не в мусор, который лежит. Правило "один тег один раз" им не обходится, бюджет проверяется. В мастерской: клик по вещи кладёт её на верстак, повторный клик по той же назначает донором (помечается кровью - он сгорит, и видеть это надо ДО нажатия). Строки аффиксов стали кнопками: по ним выбирается цель. Проверка меряет НАПРАВЛЕННОСТЬ, а не факт срабатывания: направленность: отсечение по цели, возвышение по цели, закалка двигает рулон, отпечаток перенёс и сжёг донора Отсечение пробуется на СРЕДНЕМ аффиксе из трёх: операция, которая "работает", снимая последний, прошла бы и слепую версию. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 23:37:07 +07:00
// Клик по ячейке схрона: обычный — положить вещь на верстак,
// повторный по той же — назначить её ДОНОРОМ для отпечатка.
// Второй клик, а не второй список: экран и так плотный.
if (index == ui.craftIndex && ui.craftDonor != index) ui.craftDonor = index;
else
{
ui.craftIndex = index;
ui.craftAffix = 0;
if (ui.craftDonor == index) ui.craftDonor = -1;
}
ui.craftMessage = 0;
return;
case UiPart::CRAFT_AFFIX:
// Выбор цели направленной операции. Ничего не тратит.
ui.craftAffix = index;
Материалы и мастерская: вещь можно доработать (M27) Аффиксы сделали находку решением, но решением одноразовым: выпало как выпало. Материалы дают второй способ получить нужное — не «повезло», а «доработал». Пять материалов, пять операций, экран WORKSHOP. Главное правило экономики — материал добывается ТОЛЬКО В СВОЕЙ ПОЛОСЕ: био-ядра с плотских этажей, крио с мёрзлых. Это единственная причина возвращаться на средние глубины, когда рекорд ушёл вниз; без полос весь контент, кроме последнего акта, умирает в день прохождения. Материалы — валюта, а не предметы: не занимают ячеек, не весят, в схроне не лежат. Но теряются как добыча: зачисляются только при эвакуации. Три уточнения операций, каждое против конкретного обхода: перековка меняет только значения (иначе это новый бросок предмета в обход редкости), прививка не ставит уже стоящий тег (иначе «+урон» шесть раз), возвышается случайный аффикс (иначе все предметы сойдутся к одному). --tool craft поймал, что материалы бесплатны. С твари падало по её danger, и вылазка на восьмидесятом этаже приносила тысячу ядер при цене доводки вещи в шестьсот: 0.6 вылазки на предмет вместо восьми. Правило заменено на «одно ядро с мелочи, два с крупной» — стало 6.2. Сам инструмент при этом тоже считал неверно: брал доход из бюджета опасности этажа, хотя на этаж влезает не больше MAX_LEVEL_SPAWNS тварей. Бюджет уходит не в количество тварей, а в их силу, и доход упирается в потолок расстановки с сорокового этажа. Глубина даёт не количество материалов, а качество предметов — и вместе с ним цену доводки. Залом замерен на тысяче попыток: рассыпалось 11.6% при заявленных 12%, плата снимается всегда, слот теряется всегда. «У меня трижды не рассыпалось» не доказательство ни в какую сторону. Пункт меню WORKSHOP проверен --ui-check — той самой проверкой, которая появилась вчера из-за пункта UPGRADES, молча закрывавшего игру. Формат профиля 5: строка mat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 07:07:46 +07:00
ui.craftMessage = 0;
return;
case UiPart::CRAFT_ITEM:
{
if (!game.profile) return;
Profile& pr = *game.profile;
if (ui.craftIndex < 0 || ui.craftIndex >= pr.StashCount()) return;
// Сид от состояния профиля: два одинаковых клика подряд не дают
// один и тот же бросок, но и от системного времени игра не зависит
// — прогоны обязаны повторяться.
uint32_t rng = uint32_t(pr.runs * 7919 + pr.StashCount() * 104729 +
int(pr.bankedXp) + ui.craftIndex * 31 + index * 17);
Крафт стал выбором: закалка, отпечаток, направленные операции (M33) Прежние пять операций были СЛЕПЫМИ: перековка бросала всё заново, прививка ставила случайный тег, отсечение снимало последний аффикс, возвышение поднимало случайный. Игрок не принимал ни одного решения - жал кнопку и смотрел, что выпало. Это не крафт, это второй бросок предмета за материалы. Теперь половина операций направленные: игрок показывает пальцем, какой аффикс трогать, и платит за это вдвое - вот это уже решение ("перебросить всё дёшево или снять один дорого"). Два новых глагола: ЗАКАЛКА двигает рулон выбранного аффикса к максимуму его тира на треть остатка. Дёшево, понятно, повторяемо - то самое "доработать почти хорошую вещь". Тир не трогает: за ступень платят возвышением. ОТПЕЧАТОК переносит лучший аффикс с ДРУГОЙ вещи, сжигая её. Именно он превращает хлам из схрона в ресурс, а не в мусор, который лежит. Правило "один тег один раз" им не обходится, бюджет проверяется. В мастерской: клик по вещи кладёт её на верстак, повторный клик по той же назначает донором (помечается кровью - он сгорит, и видеть это надо ДО нажатия). Строки аффиксов стали кнопками: по ним выбирается цель. Проверка меряет НАПРАВЛЕННОСТЬ, а не факт срабатывания: направленность: отсечение по цели, возвышение по цели, закалка двигает рулон, отпечаток перенёс и сжёг донора Отсечение пробуется на СРЕДНЕМ аффиксе из трёх: операция, которая "работает", снимая последний, прошла бы и слепую версию. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 23:37:07 +07:00
// Донор обязан быть ДРУГОЙ вещью: переплавить предмет в самого
// себя нельзя, и молча брать «первый попавшийся» тоже.
ItemInstance* donor = nullptr;
if (CraftNeedsDonor(CraftOp(index)) && ui.craftDonor >= 0 &&
ui.craftDonor < pr.StashCount() && ui.craftDonor != ui.craftIndex)
donor = &pr.stash[size_t(ui.craftDonor)];
Материалы и мастерская: вещь можно доработать (M27) Аффиксы сделали находку решением, но решением одноразовым: выпало как выпало. Материалы дают второй способ получить нужное — не «повезло», а «доработал». Пять материалов, пять операций, экран WORKSHOP. Главное правило экономики — материал добывается ТОЛЬКО В СВОЕЙ ПОЛОСЕ: био-ядра с плотских этажей, крио с мёрзлых. Это единственная причина возвращаться на средние глубины, когда рекорд ушёл вниз; без полос весь контент, кроме последнего акта, умирает в день прохождения. Материалы — валюта, а не предметы: не занимают ячеек, не весят, в схроне не лежат. Но теряются как добыча: зачисляются только при эвакуации. Три уточнения операций, каждое против конкретного обхода: перековка меняет только значения (иначе это новый бросок предмета в обход редкости), прививка не ставит уже стоящий тег (иначе «+урон» шесть раз), возвышается случайный аффикс (иначе все предметы сойдутся к одному). --tool craft поймал, что материалы бесплатны. С твари падало по её danger, и вылазка на восьмидесятом этаже приносила тысячу ядер при цене доводки вещи в шестьсот: 0.6 вылазки на предмет вместо восьми. Правило заменено на «одно ядро с мелочи, два с крупной» — стало 6.2. Сам инструмент при этом тоже считал неверно: брал доход из бюджета опасности этажа, хотя на этаж влезает не больше MAX_LEVEL_SPAWNS тварей. Бюджет уходит не в количество тварей, а в их силу, и доход упирается в потолок расстановки с сорокового этажа. Глубина даёт не количество материалов, а качество предметов — и вместе с ним цену доводки. Залом замерен на тысяче попыток: рассыпалось 11.6% при заявленных 12%, плата снимается всегда, слот теряется всегда. «У меня трижды не рассыпалось» не доказательство ни в какую сторону. Пункт меню WORKSHOP проверен --ui-check — той самой проверкой, которая появилась вчера из-за пункта UPGRADES, молча закрывавшего игру. Формат профиля 5: строка mat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 07:07:46 +07:00
ItemInstance& it = pr.stash[size_t(ui.craftIndex)];
Крафт стал выбором: закалка, отпечаток, направленные операции (M33) Прежние пять операций были СЛЕПЫМИ: перековка бросала всё заново, прививка ставила случайный тег, отсечение снимало последний аффикс, возвышение поднимало случайный. Игрок не принимал ни одного решения - жал кнопку и смотрел, что выпало. Это не крафт, это второй бросок предмета за материалы. Теперь половина операций направленные: игрок показывает пальцем, какой аффикс трогать, и платит за это вдвое - вот это уже решение ("перебросить всё дёшево или снять один дорого"). Два новых глагола: ЗАКАЛКА двигает рулон выбранного аффикса к максимуму его тира на треть остатка. Дёшево, понятно, повторяемо - то самое "доработать почти хорошую вещь". Тир не трогает: за ступень платят возвышением. ОТПЕЧАТОК переносит лучший аффикс с ДРУГОЙ вещи, сжигая её. Именно он превращает хлам из схрона в ресурс, а не в мусор, который лежит. Правило "один тег один раз" им не обходится, бюджет проверяется. В мастерской: клик по вещи кладёт её на верстак, повторный клик по той же назначает донором (помечается кровью - он сгорит, и видеть это надо ДО нажатия). Строки аффиксов стали кнопками: по ним выбирается цель. Проверка меряет НАПРАВЛЕННОСТЬ, а не факт срабатывания: направленность: отсечение по цели, возвышение по цели, закалка двигает рулон, отпечаток перенёс и сжёг донора Отсечение пробуется на СРЕДНЕМ аффиксе из трёх: операция, которая "работает", снимая последний, прошла бы и слепую версию. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 23:37:07 +07:00
const CraftResult r = ApplyCraft(CraftOp(index), it, pr.material, pr.maxDepth, rng,
ui.craftAffix, donor);
// Сгоревший донор убирается из схрона: пустая ячейка посреди
// списка читалась бы как ошибка, а не как цена операции.
if (r == CraftResult::OK && donor && donor->Empty())
{
pr.StashRemoveAt(ui.craftDonor);
if (ui.craftIndex > ui.craftDonor) --ui.craftIndex;
ui.craftDonor = -1;
}
Материалы и мастерская: вещь можно доработать (M27) Аффиксы сделали находку решением, но решением одноразовым: выпало как выпало. Материалы дают второй способ получить нужное — не «повезло», а «доработал». Пять материалов, пять операций, экран WORKSHOP. Главное правило экономики — материал добывается ТОЛЬКО В СВОЕЙ ПОЛОСЕ: био-ядра с плотских этажей, крио с мёрзлых. Это единственная причина возвращаться на средние глубины, когда рекорд ушёл вниз; без полос весь контент, кроме последнего акта, умирает в день прохождения. Материалы — валюта, а не предметы: не занимают ячеек, не весят, в схроне не лежат. Но теряются как добыча: зачисляются только при эвакуации. Три уточнения операций, каждое против конкретного обхода: перековка меняет только значения (иначе это новый бросок предмета в обход редкости), прививка не ставит уже стоящий тег (иначе «+урон» шесть раз), возвышается случайный аффикс (иначе все предметы сойдутся к одному). --tool craft поймал, что материалы бесплатны. С твари падало по её danger, и вылазка на восьмидесятом этаже приносила тысячу ядер при цене доводки вещи в шестьсот: 0.6 вылазки на предмет вместо восьми. Правило заменено на «одно ядро с мелочи, два с крупной» — стало 6.2. Сам инструмент при этом тоже считал неверно: брал доход из бюджета опасности этажа, хотя на этаж влезает не больше MAX_LEVEL_SPAWNS тварей. Бюджет уходит не в количество тварей, а в их силу, и доход упирается в потолок расстановки с сорокового этажа. Глубина даёт не количество материалов, а качество предметов — и вместе с ним цену доводки. Залом замерен на тысяче попыток: рассыпалось 11.6% при заявленных 12%, плата снимается всегда, слот теряется всегда. «У меня трижды не рассыпалось» не доказательство ни в какую сторону. Пункт меню WORKSHOP проверен --ui-check — той самой проверкой, которая появилась вчера из-за пункта UPGRADES, молча закрывавшего игру. Формат профиля 5: строка mat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 07:07:46 +07:00
// Рассыпавшаяся вещь убирается из схрона: пустая ячейка посреди
// списка выглядела бы как ошибка, а не как последствие риска.
if (r == CraftResult::DESTROYED)
{
pr.StashRemoveAt(ui.craftIndex);
if (ui.craftIndex >= pr.StashCount()) ui.craftIndex = pr.StashCount() - 1;
if (ui.craftIndex < 0) ui.craftIndex = 0;
}
ui.craftMessage = int(r) + 1;
ui.craftOpDone = index;
ui.craftFlash = 3.0f;
return;
}
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
case UiPart::SUMMARY_ITEM:
// Забег кончился, мира больше нет: «продолжить» ведёт только в меню.
ui.started = false;
ui.screen = Screen::MENU;
return;
case UiPart::DEFEAT_ITEM:
if (index == 0)
{
game.Reset();
ui.worldReset = true;
ui.screen = Screen::GAME;
}
else
ui.screen = Screen::MENU;
return;
default:
return;
}
}
Пункт UPGRADES выходил из игры; меню теперь проверяется (M26) Жалоба была «крашится при открытии апгрейдов». Не крашилось: пункт закрывал игру. В обработчике главного меню стоял `default: ui.quit`, а экран улучшений добавили четвёртым пунктом — обработчик о нём просто не узнал. Снаружи мгновенное закрытие окна неотличимо от падения. Починка на две строки, но интереснее причина, по которой это прожило четыре вехи: в проекте каждая механика меряется прогоном с кодом возврата, а меню не проверял никто. Все инструменты смотрят на СИМУЛЯЦИЮ. Поэтому заведён --ui-check: проходит по всем пунктам меню и требует одного — нажатие ВЕДЁТ КУДА-ТО, а из игры выходит ровно один пункт, QUIT. Плюс проверяет, что кнопки экрана улучшений действительно покупают. Окно при этом не открывается: UiActivate ничего из raylib не трогает. Проверка сперва запущена на СЛОМАННОЙ сборке и дала код 1 с диагнозом «пункт UPGRADES не делает ничего», и только после этого объявлена рабочей. Проверка, которую не видели падающей, ещё ничего не проверяет. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:51:58 +07:00
namespace
{
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
bool Pressed(int key) { return IsKeyPressed(key); }
// Все списки листаются одинаково: стрелки и WS.
int Step()
{
int d = 0;
if (Pressed(KEY_DOWN) || Pressed(KEY_S)) d += 1;
if (Pressed(KEY_UP) || Pressed(KEY_W)) d -= 1;
return d;
}
} // namespace
Пункт UPGRADES выходил из игры; меню теперь проверяется (M26) Жалоба была «крашится при открытии апгрейдов». Не крашилось: пункт закрывал игру. В обработчике главного меню стоял `default: ui.quit`, а экран улучшений добавили четвёртым пунктом — обработчик о нём просто не узнал. Снаружи мгновенное закрытие окна неотличимо от падения. Починка на две строки, но интереснее причина, по которой это прожило четыре вехи: в проекте каждая механика меряется прогоном с кодом возврата, а меню не проверял никто. Все инструменты смотрят на СИМУЛЯЦИЮ. Поэтому заведён --ui-check: проходит по всем пунктам меню и требует одного — нажатие ВЕДЁТ КУДА-ТО, а из игры выходит ровно один пункт, QUIT. Плюс проверяет, что кнопки экрана улучшений действительно покупают. Окно при этом не открывается: UiActivate ничего из raylib не трогает. Проверка сперва запущена на СЛОМАННОЙ сборке и дала код 1 с диагнозом «пункт UPGRADES не делает ничего», и только после этого объявлена рабочей. Проверка, которую не видели падающей, ещё ничего не проверяет. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:51:58 +07:00
int UiMenuItemCount() { return MENU_COUNT; }
const char* UiMenuItemName(int index)
{
return (index >= 0 && index < MENU_COUNT) ? MENU_ITEMS[index] : "?";
}
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
// -----------------------------------------------------------------------------
// Ввод
// -----------------------------------------------------------------------------
bool Ui::Update(Game& game, const ScreenRect& fit, float dt)
{
blink += dt;
const Layout l = Of(fit);
// Окно -> кадр: обратная ScreenFitRect. Вне кадра курсор висит на чёрных
// полях, где рисовать нечего, — значит и наведения там нет.
const Vector2 m = GetMousePosition();
mx = (m.x - float(l.x)) / float(l.u);
my = (m.y - float(l.y)) / float(l.u);
mouseIn = mx >= 0.0f && my >= 0.0f && mx < float(l.fw) && my < float(l.fh);
// Экран сменился на лету (ESC из отряда) — предмет с курсора возвращается
// сам: тащить его больше некуда.
if (screen != Screen::SQUAD) dragging = false;
Widgets w;
BuildWidgets(*this, game, l, w);
const Widget* hot = mouseIn ? w.At(mx, my) : nullptr;
hoverPart = hot ? hot->part : UiPart::NONE;
hoverIndex = hot ? hot->index : -1;
const bool moved = GetMouseDelta().x != 0.0f || GetMouseDelta().y != 0.0f;
const bool lmbDown = IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && mouseIn;
const bool lmbUp = IsMouseButtonReleased(MOUSE_BUTTON_LEFT);
// Системная стрелка поверх кадра, растянутого в три-четыре раза, выглядит
// чужой вещью, поэтому внутри кадра курсор рисуем свой. На чёрных полях
// системный нужен обратно: там мышь снова принадлежит окну, а не игре.
if (mouseIn != cursorHidden)
{
cursorHidden = mouseIn;
if (mouseIn) HideCursor();
else ShowCursor();
}
switch (screen)
{
case Screen::GAME:
if (Pressed(KEY_ESCAPE)) { screen = Screen::PAUSE; return true; }
// Только I: TAB в бою переключает цель (core/input.h), и отбирать
// его под инвентарь нельзя.
if (Pressed(KEY_I)) { screen = Screen::SQUAD; return true; }
// Отряд полёг — бой закончился, и решать это должен не игрок.
// Забег закрывается ЗДЕСЬ, до показа итога: вынесенного нет,
// снаряжение сбрасывается, чекпоинт остаётся.
if (game.SquadWiped())
{
if (!game.RunOver()) game.WipeRun();
screen = Screen::SUMMARY;
return true;
}
return false;
case Screen::MENU:
{
// Наведение ведёт выбор за собой: иначе после мыши клавиатура
// прыгала бы с пункта, на который игрок уже не смотрит.
if (moved && hot && hot->enabled) menuIndex = hot->index;
const int d = Step();
if (d) menuIndex = (menuIndex + d + MENU_COUNT) % MENU_COUNT;
if (menuIndex == 0 && !started && d >= 0) menuIndex = 1;
if (lmbDown && hot && hot->enabled)
Пункт UPGRADES выходил из игры; меню теперь проверяется (M26) Жалоба была «крашится при открытии апгрейдов». Не крашилось: пункт закрывал игру. В обработчике главного меню стоял `default: ui.quit`, а экран улучшений добавили четвёртым пунктом — обработчик о нём просто не узнал. Снаружи мгновенное закрытие окна неотличимо от падения. Починка на две строки, но интереснее причина, по которой это прожило четыре вехи: в проекте каждая механика меряется прогоном с кодом возврата, а меню не проверял никто. Все инструменты смотрят на СИМУЛЯЦИЮ. Поэтому заведён --ui-check: проходит по всем пунктам меню и требует одного — нажатие ВЕДЁТ КУДА-ТО, а из игры выходит ровно один пункт, QUIT. Плюс проверяет, что кнопки экрана улучшений действительно покупают. Окно при этом не открывается: UiActivate ничего из raylib не трогает. Проверка сперва запущена на СЛОМАННОЙ сборке и дала код 1 с диагнозом «пункт UPGRADES не делает ничего», и только после этого объявлена рабочей. Проверка, которую не видели падающей, ещё ничего не проверяет. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:51:58 +07:00
UiActivate(*this, game, hot->part, hot->index);
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
else if (Pressed(KEY_ENTER) || Pressed(KEY_SPACE) || Pressed(KEY_KP_ENTER))
Пункт UPGRADES выходил из игры; меню теперь проверяется (M26) Жалоба была «крашится при открытии апгрейдов». Не крашилось: пункт закрывал игру. В обработчике главного меню стоял `default: ui.quit`, а экран улучшений добавили четвёртым пунктом — обработчик о нём просто не узнал. Снаружи мгновенное закрытие окна неотличимо от падения. Починка на две строки, но интереснее причина, по которой это прожило четыре вехи: в проекте каждая механика меряется прогоном с кодом возврата, а меню не проверял никто. Все инструменты смотрят на СИМУЛЯЦИЮ. Поэтому заведён --ui-check: проходит по всем пунктам меню и требует одного — нажатие ВЕДЁТ КУДА-ТО, а из игры выходит ровно один пункт, QUIT. Плюс проверяет, что кнопки экрана улучшений действительно покупают. Окно при этом не открывается: UiActivate ничего из raylib не трогает. Проверка сперва запущена на СЛОМАННОЙ сборке и дала код 1 с диагнозом «пункт UPGRADES не делает ничего», и только после этого объявлена рабочей. Проверка, которую не видели падающей, ещё ничего не проверяет. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:51:58 +07:00
UiActivate(*this, game, UiPart::MENU_ITEM, menuIndex);
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
return true;
}
case Screen::PAUSE:
Пункт UPGRADES выходил из игры; меню теперь проверяется (M26) Жалоба была «крашится при открытии апгрейдов». Не крашилось: пункт закрывал игру. В обработчике главного меню стоял `default: ui.quit`, а экран улучшений добавили четвёртым пунктом — обработчик о нём просто не узнал. Снаружи мгновенное закрытие окна неотличимо от падения. Починка на две строки, но интереснее причина, по которой это прожило четыре вехи: в проекте каждая механика меряется прогоном с кодом возврата, а меню не проверял никто. Все инструменты смотрят на СИМУЛЯЦИЮ. Поэтому заведён --ui-check: проходит по всем пунктам меню и требует одного — нажатие ВЕДЁТ КУДА-ТО, а из игры выходит ровно один пункт, QUIT. Плюс проверяет, что кнопки экрана улучшений действительно покупают. Окно при этом не открывается: UiActivate ничего из raylib не трогает. Проверка сперва запущена на СЛОМАННОЙ сборке и дала код 1 с диагнозом «пункт UPGRADES не делает ничего», и только после этого объявлена рабочей. Проверка, которую не видели падающей, ещё ничего не проверяет. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:51:58 +07:00
if (lmbDown && hot) { UiActivate(*this, game, hot->part, hot->index); return true; }
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
if (Pressed(KEY_ESCAPE)) { screen = Screen::GAME; return true; }
if (Pressed(KEY_I) || Pressed(KEY_TAB)) { screen = Screen::SQUAD; return true; }
if (Pressed(KEY_Q)) { screen = Screen::MENU; return true; }
return true;
case Screen::DEFEAT:
Пункт UPGRADES выходил из игры; меню теперь проверяется (M26) Жалоба была «крашится при открытии апгрейдов». Не крашилось: пункт закрывал игру. В обработчике главного меню стоял `default: ui.quit`, а экран улучшений добавили четвёртым пунктом — обработчик о нём просто не узнал. Снаружи мгновенное закрытие окна неотличимо от падения. Починка на две строки, но интереснее причина, по которой это прожило четыре вехи: в проекте каждая механика меряется прогоном с кодом возврата, а меню не проверял никто. Все инструменты смотрят на СИМУЛЯЦИЮ. Поэтому заведён --ui-check: проходит по всем пунктам меню и требует одного — нажатие ВЕДЁТ КУДА-ТО, а из игры выходит ровно один пункт, QUIT. Плюс проверяет, что кнопки экрана улучшений действительно покупают. Окно при этом не открывается: UiActivate ничего из raylib не трогает. Проверка сперва запущена на СЛОМАННОЙ сборке и дала код 1 с диагнозом «пункт UPGRADES не делает ничего», и только после этого объявлена рабочей. Проверка, которую не видели падающей, ещё ничего не проверяет. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:51:58 +07:00
if (lmbDown && hot) { UiActivate(*this, game, hot->part, hot->index); return true; }
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
if (Pressed(KEY_ENTER) || Pressed(KEY_SPACE))
Пункт UPGRADES выходил из игры; меню теперь проверяется (M26) Жалоба была «крашится при открытии апгрейдов». Не крашилось: пункт закрывал игру. В обработчике главного меню стоял `default: ui.quit`, а экран улучшений добавили четвёртым пунктом — обработчик о нём просто не узнал. Снаружи мгновенное закрытие окна неотличимо от падения. Починка на две строки, но интереснее причина, по которой это прожило четыре вехи: в проекте каждая механика меряется прогоном с кодом возврата, а меню не проверял никто. Все инструменты смотрят на СИМУЛЯЦИЮ. Поэтому заведён --ui-check: проходит по всем пунктам меню и требует одного — нажатие ВЕДЁТ КУДА-ТО, а из игры выходит ровно один пункт, QUIT. Плюс проверяет, что кнопки экрана улучшений действительно покупают. Окно при этом не открывается: UiActivate ничего из raylib не трогает. Проверка сперва запущена на СЛОМАННОЙ сборке и дала код 1 с диагнозом «пункт UPGRADES не делает ничего», и только после этого объявлена рабочей. Проверка, которую не видели падающей, ещё ничего не проверяет. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:51:58 +07:00
UiActivate(*this, game, UiPart::DEFEAT_ITEM, 0);
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
if (Pressed(KEY_ESCAPE)) screen = Screen::MENU;
return true;
case Screen::SUMMARY:
Пункт UPGRADES выходил из игры; меню теперь проверяется (M26) Жалоба была «крашится при открытии апгрейдов». Не крашилось: пункт закрывал игру. В обработчике главного меню стоял `default: ui.quit`, а экран улучшений добавили четвёртым пунктом — обработчик о нём просто не узнал. Снаружи мгновенное закрытие окна неотличимо от падения. Починка на две строки, но интереснее причина, по которой это прожило четыре вехи: в проекте каждая механика меряется прогоном с кодом возврата, а меню не проверял никто. Все инструменты смотрят на СИМУЛЯЦИЮ. Поэтому заведён --ui-check: проходит по всем пунктам меню и требует одного — нажатие ВЕДЁТ КУДА-ТО, а из игры выходит ровно один пункт, QUIT. Плюс проверяет, что кнопки экрана улучшений действительно покупают. Окно при этом не открывается: UiActivate ничего из raylib не трогает. Проверка сперва запущена на СЛОМАННОЙ сборке и дала код 1 с диагнозом «пункт UPGRADES не делает ничего», и только после этого объявлена рабочей. Проверка, которую не видели падающей, ещё ничего не проверяет. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:51:58 +07:00
if (lmbDown && hot) { UiActivate(*this, game, hot->part, hot->index); return true; }
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
if (Pressed(KEY_ENTER) || Pressed(KEY_SPACE) || Pressed(KEY_ESCAPE))
Пункт UPGRADES выходил из игры; меню теперь проверяется (M26) Жалоба была «крашится при открытии апгрейдов». Не крашилось: пункт закрывал игру. В обработчике главного меню стоял `default: ui.quit`, а экран улучшений добавили четвёртым пунктом — обработчик о нём просто не узнал. Снаружи мгновенное закрытие окна неотличимо от падения. Починка на две строки, но интереснее причина, по которой это прожило четыре вехи: в проекте каждая механика меряется прогоном с кодом возврата, а меню не проверял никто. Все инструменты смотрят на СИМУЛЯЦИЮ. Поэтому заведён --ui-check: проходит по всем пунктам меню и требует одного — нажатие ВЕДЁТ КУДА-ТО, а из игры выходит ровно один пункт, QUIT. Плюс проверяет, что кнопки экрана улучшений действительно покупают. Окно при этом не открывается: UiActivate ничего из raylib не трогает. Проверка сперва запущена на СЛОМАННОЙ сборке и дала код 1 с диагнозом «пункт UPGRADES не делает ничего», и только после этого объявлена рабочей. Проверка, которую не видели падающей, ещё ничего не проверяет. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:51:58 +07:00
UiActivate(*this, game, UiPart::SUMMARY_ITEM, 0);
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
return true;
case Screen::UPGRADE:
Пункт UPGRADES выходил из игры; меню теперь проверяется (M26) Жалоба была «крашится при открытии апгрейдов». Не крашилось: пункт закрывал игру. В обработчике главного меню стоял `default: ui.quit`, а экран улучшений добавили четвёртым пунктом — обработчик о нём просто не узнал. Снаружи мгновенное закрытие окна неотличимо от падения. Починка на две строки, но интереснее причина, по которой это прожило четыре вехи: в проекте каждая механика меряется прогоном с кодом возврата, а меню не проверял никто. Все инструменты смотрят на СИМУЛЯЦИЮ. Поэтому заведён --ui-check: проходит по всем пунктам меню и требует одного — нажатие ВЕДЁТ КУДА-ТО, а из игры выходит ровно один пункт, QUIT. Плюс проверяет, что кнопки экрана улучшений действительно покупают. Окно при этом не открывается: UiActivate ничего из raylib не трогает. Проверка сперва запущена на СЛОМАННОЙ сборке и дала код 1 с диагнозом «пункт UPGRADES не делает ничего», и только после этого объявлена рабочей. Проверка, которую не видели падающей, ещё ничего не проверяет. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:51:58 +07:00
if (lmbDown && hot) UiActivate(*this, game, hot->part, hot->index);
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
if (Pressed(KEY_ESCAPE) || Pressed(KEY_U)) screen = Screen::MENU;
return true;
Материалы и мастерская: вещь можно доработать (M27) Аффиксы сделали находку решением, но решением одноразовым: выпало как выпало. Материалы дают второй способ получить нужное — не «повезло», а «доработал». Пять материалов, пять операций, экран WORKSHOP. Главное правило экономики — материал добывается ТОЛЬКО В СВОЕЙ ПОЛОСЕ: био-ядра с плотских этажей, крио с мёрзлых. Это единственная причина возвращаться на средние глубины, когда рекорд ушёл вниз; без полос весь контент, кроме последнего акта, умирает в день прохождения. Материалы — валюта, а не предметы: не занимают ячеек, не весят, в схроне не лежат. Но теряются как добыча: зачисляются только при эвакуации. Три уточнения операций, каждое против конкретного обхода: перековка меняет только значения (иначе это новый бросок предмета в обход редкости), прививка не ставит уже стоящий тег (иначе «+урон» шесть раз), возвышается случайный аффикс (иначе все предметы сойдутся к одному). --tool craft поймал, что материалы бесплатны. С твари падало по её danger, и вылазка на восьмидесятом этаже приносила тысячу ядер при цене доводки вещи в шестьсот: 0.6 вылазки на предмет вместо восьми. Правило заменено на «одно ядро с мелочи, два с крупной» — стало 6.2. Сам инструмент при этом тоже считал неверно: брал доход из бюджета опасности этажа, хотя на этаж влезает не больше MAX_LEVEL_SPAWNS тварей. Бюджет уходит не в количество тварей, а в их силу, и доход упирается в потолок расстановки с сорокового этажа. Глубина даёт не количество материалов, а качество предметов — и вместе с ним цену доводки. Залом замерен на тысяче попыток: рассыпалось 11.6% при заявленных 12%, плата снимается всегда, слот теряется всегда. «У меня трижды не рассыпалось» не доказательство ни в какую сторону. Пункт меню WORKSHOP проверен --ui-check — той самой проверкой, которая появилась вчера из-за пункта UPGRADES, молча закрывавшего игру. Формат профиля 5: строка mat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 07:07:46 +07:00
case Screen::WORKSHOP:
craftFlash = std::max(0.0f, craftFlash - dt);
if (craftFlash <= 0.0f) craftMessage = 0;
if (lmbDown && hot && hot->enabled) UiActivate(*this, game, hot->part, hot->index);
if (Pressed(KEY_Q)) stashScroll = std::max(0, stashScroll - WS_SLOT_COLS);
if (Pressed(KEY_E)) stashScroll += WS_SLOT_COLS;
if (game.profile)
{
const int cap = std::max(0, game.profile->StashCount() - WS_SLOT_CELLS);
stashScroll = std::min(stashScroll, cap);
craftIndex = std::min(craftIndex, std::max(0, game.profile->StashCount() - 1));
}
if (Pressed(KEY_ESCAPE)) screen = Screen::MENU;
return true;
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
case Screen::DEPTH:
{
const int shown = w.count;
if (shown <= 0) { screen = Screen::MENU; return true; }
if (moved && hot) depthIndex = -1; // мышь ведёт выбор, а не индекс
const int d = Step();
if (d) depthIndex = (depthIndex + d + shown) % shown;
if (depthIndex < 0) depthIndex = 0;
Пункт UPGRADES выходил из игры; меню теперь проверяется (M26) Жалоба была «крашится при открытии апгрейдов». Не крашилось: пункт закрывал игру. В обработчике главного меню стоял `default: ui.quit`, а экран улучшений добавили четвёртым пунктом — обработчик о нём просто не узнал. Снаружи мгновенное закрытие окна неотличимо от падения. Починка на две строки, но интереснее причина, по которой это прожило четыре вехи: в проекте каждая механика меряется прогоном с кодом возврата, а меню не проверял никто. Все инструменты смотрят на СИМУЛЯЦИЮ. Поэтому заведён --ui-check: проходит по всем пунктам меню и требует одного — нажатие ВЕДЁТ КУДА-ТО, а из игры выходит ровно один пункт, QUIT. Плюс проверяет, что кнопки экрана улучшений действительно покупают. Окно при этом не открывается: UiActivate ничего из raylib не трогает. Проверка сперва запущена на СЛОМАННОЙ сборке и дала код 1 с диагнозом «пункт UPGRADES не делает ничего», и только после этого объявлена рабочей. Проверка, которую не видели падающей, ещё ничего не проверяет. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:51:58 +07:00
if (lmbDown && hot) { UiActivate(*this, game, hot->part, hot->index); return true; }
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
if (Pressed(KEY_ENTER) || Pressed(KEY_SPACE))
{
const Widget& pick = w.item[depthIndex];
Пункт UPGRADES выходил из игры; меню теперь проверяется (M26) Жалоба была «крашится при открытии апгрейдов». Не крашилось: пункт закрывал игру. В обработчике главного меню стоял `default: ui.quit`, а экран улучшений добавили четвёртым пунктом — обработчик о нём просто не узнал. Снаружи мгновенное закрытие окна неотличимо от падения. Починка на две строки, но интереснее причина, по которой это прожило четыре вехи: в проекте каждая механика меряется прогоном с кодом возврата, а меню не проверял никто. Все инструменты смотрят на СИМУЛЯЦИЮ. Поэтому заведён --ui-check: проходит по всем пунктам меню и требует одного — нажатие ВЕДЁТ КУДА-ТО, а из игры выходит ровно один пункт, QUIT. Плюс проверяет, что кнопки экрана улучшений действительно покупают. Окно при этом не открывается: UiActivate ничего из raylib не трогает. Проверка сперва запущена на СЛОМАННОЙ сборке и дала код 1 с диагнозом «пункт UPGRADES не делает ничего», и только после этого объявлена рабочей. Проверка, которую не видели падающей, ещё ничего не проверяет. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:51:58 +07:00
UiActivate(*this, game, pick.part, pick.index);
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
return true;
}
if (Pressed(KEY_ESCAPE)) screen = Screen::MENU;
return true;
}
case Screen::SQUAD:
{
// --- мышь: выбор бойца и перетаскивание предметов ---
if (lmbDown && hot)
{
if (hot->part == UiPart::AGENT)
{
agentIndex = hot->index;
}
else if (IsSlot(hot->part))
{
// Клавиатура и мышь смотрят на одну ячейку: после клика
// ENTER сработает именно по ней.
if (hot->part == UiPart::BAG) slotIndex = hot->index;
if (hot->item != ItemId::NONE)
{
dragging = true;
dragFrom = hot->part;
dragIndex = hot->index;
dragItem = hot->item;
}
}
}
// Прокрутка схрона. Q/E, а не колесо: колесо на экране отряда
// ничего не значит, а руки уже лежат на клавишах выбора бойца.
if (game.profile)
{
const int last = game.profile->StashCount() - SQ_STASH_CELLS;
if (Pressed(KEY_E) && stashScroll < last) ++stashScroll;
if (Pressed(KEY_Q) && stashScroll > 0) --stashScroll;
if (stashScroll > last) stashScroll = (last > 0) ? last : 0;
}
// Правая кнопка — отказ от переноса: предмет возвращается на место.
if (dragging && IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) dragging = false;
// Она же по занятой ячейке, когда ничего не тащат, — выбросить.
else if (!dragging && IsMouseButtonPressed(MOUSE_BUTTON_RIGHT) && hot &&
IsSlot(hot->part) && hot->item != ItemId::NONE)
DropToFloor(*this, game, hot->part, hot->index);
if (dragging && lmbUp)
{
if (dropOk && hot) ApplyDrop(*this, game, hot->part, hot->index);
dragging = false;
}
if (!dragging)
{
dragFrom = UiPart::NONE;
dragIndex = -1;
dragItem = ItemId::NONE;
dropOk = false;
}
// --- клавиатура ---
if (Pressed(KEY_ESCAPE) || Pressed(KEY_I) || Pressed(KEY_TAB))
{
screen = started ? Screen::GAME : Screen::MENU;
dragging = false;
return true;
}
const int d = Step();
if (d) slotIndex = (slotIndex + d + SQUAD_BAG_SLOTS) % SQUAD_BAG_SLOTS;
// Пока предмет висит на курсоре, отряд не листается: взят он был
// из инвентаря этого бойца, а лёг бы в инвентарь другого.
if (!dragging)
{
Отряд растёт с одного оперативника до пяти (M24) Игра начинается ОДНИМ человеком: тактический нож, слабый фонарь, куртка. Остальные четверо нанимаются за опыт по кривой 600/2400/9600/38400 — самая дорогая покупка в игре и намеренно. Одиночка с ножом это первое впечатление, и продать второго бойца за одну вылазку значило бы выбросить весь хоррор начала: вдвоём уже не страшно. NUM_AGENTS осталась величиной времени сборки и означает теперь ВМЕСТИМОСТЬ, а не размер отряда: делать её динамической это сотня мест ради числа, которое всё равно упирается в пять. Занятость слота это Agent::present, и он отличается от alive. Правила боя читают только alive — для них пустой слот и павший одно, и это верно. Разница нужна там, где показывают человека: у павшего есть тело на полу и перечёркнутая карточка, у пустого слота нет ни того, ни другого. Проверка в --descend меряет цепочку «в профиле -> на этаже» и потолок, а главное — переживает ли одиночка первую схватку: отряд: в профиле 1 -> на этаже 1, после найма 2, цена 600, сверх потолка отказ одиночка с ножом на первом этаже: выжил, 100 -> 41, подошло 2, убил 2 Первая версия этой проверки не измеряла ничего: боец стоял минуту, к нему не подошёл никто, и «выжил» было пустым словом. Теперь отряд ставится рядом с тварью. Попутно найдено и исправлено: Profile::Save три вехи не писала ни версию формата, ни улучшения. Купленный запас патронов действовал до конца запуска и молча пропадал при чтении, а профиль без версии читался как формат 1. Круговой прогон этого не видел, потому что сверял три поля из десяти и делал это на пустом профиле, где всё сходится само. Теперь он сверяет поля списком и на набитом профиле. Формат профиля 3: добавлена строка squad. Старый профиль играл впятером и получает пятерых при миграции — отобрать их значило бы съесть отряд, которого игрок не терял. --accept PASS без изменений: без профиля отряд по-прежнему впятером. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 02:29:19 +07:00
// Листается только по ЗАНЯТЫМ слотам: пустые пролистываются
// насквозь, иначе половина нажатий показывала бы пустоту.
const int step = (Pressed(KEY_RIGHT) || Pressed(KEY_D)) ? 1
: (Pressed(KEY_LEFT) || Pressed(KEY_A)) ? -1
: 0;
if (step != 0)
for (int k = 0; k < NUM_AGENTS; ++k)
{
agentIndex = (agentIndex + step + NUM_AGENTS) % NUM_AGENTS;
if (game.squad.agents[agentIndex].present) break;
}
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
}
// ENTER — взять из сумки в руки. Обратно уезжает то, что было:
// отряд не носит воздух, у него всегда что-то в руках.
if (Pressed(KEY_ENTER) || Pressed(KEY_KP_ENTER))
{
// Взять из ОБЩЕЙ сумки в руки выбранному бойцу, а бывшее в
// руках положить на его место. Разрешено только оружие:
// бинтом отбиваться нечем.
Agent& a = game.squad.agents[agentIndex];
Inventory* inv = game.world.Get<Inventory>(a.self);
Редкости и аффиксы: находка стала решением (M25) Предмет перестал быть строкой каталога. ItemInstance несёт редкость и до шести аффиксов, и «карабин» с первого этажа больше не равен «карабину» с сорокового. Четыре редкости, десять тегов, тир и бюджет от глубины. Аффиксы не заводят новых каналов влияния: каждый тег ложится в то, что уже есть — урон оружия, смесь стихий, пробитие, четыре числа бойца, ресурсы вылазки. У предмета по-прежнему нет ручки к правилу «не бить своих», и появиться ей неоткуда. ItemInstance неявно приводится к ItemId, поэтому полторы сотни мест, которым нужен ВИД вещи, остались как были. Цена решения — присваивание в ItemId молча теряет рулоны; таких мест три (подбор, схрон, запись профиля), и все они работают с экземпляром целиком. Замеры поймали три расхождения дизайн-документа с самим собой: * формула редкости противоречила таблице рядом с ней: 2.7% находок с аффиксами на первом этаже против 22%. Выбрана формула, наклон поднят до схождения с таблицей на дне (12% обычных на сотом); * бюджет пускал восемь высших аффиксов вместо обещанных шести; * предельная вещь собиралась к семидесятому этажу вместо сотого. Числа пересчитаны, документ исправлен: 6·cost(9) = 294 <= B(100) = 295 < 7·cost(9) = 343. Предельная вещь существует ровно на дне и ровно одна. Проверка в --descend меряет не бросок, а ПРОВОДКУ до бойца — там, где дважды умирали вес и стихии: аффиксы: урон 12.0 -> 18.0, здоровье 100 -> 140, броня 0.10 -> 0.25, пробитие 0.20, KIN 1.00 -> 0.60, CRY 0.40, сумма долей 1.000 Последнее число не украшение: конверсия обязана переводить урон, а не добавлять седьмую долю сверх единицы. Попутно, на глубине 30 и только там: * «зачистка поля» в проверках не держалась — у твари есть откат воскрешения, и с нулевым таймером она оживала тем же шагом. Лечение вне схватки и расход света на стоянке молча мерили бой. На восьмом этаже тварей мало, и проверка проходила по удаче; * порог «стоянка бесплатна» стоял в единицах заряда, а тот же шаг стоит дороже с глубиной. Переведён в тайлы — и сразу показал, что отряд в строю проходит 0.8 тайла за две секунды «стоянки». Свет теперь списывается только в ADVANCING: топтание и драка бесплатны, тратит РАЗВЕДКА. Без этого таймер возвращался через чёрный ход. Формат профиля 4: токен предмета вида rifle@relic+dmg7,0:0.63. Миграции не требует — у старых токенов нет хвоста после '@'. --accept PASS без изменений: в песочнице находок нет вовсе. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:35:00 +07:00
ItemInstance& cell = game.squad.bag.slots[slotIndex];
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
if (inv && slotIndex >= 0 && slotIndex < SQUAD_BAG_SLOTS && IsWeaponItem(cell))
{
std::swap(cell, inv->weapon);
EquipWeapon(game.world, a, inv->weapon);
}
}
return true;
}
}
return true;
}
// -----------------------------------------------------------------------------
// Рисование
// -----------------------------------------------------------------------------
namespace
{
void DrawButton(const Layout& l, const Ui& ui, const Widget& b, bool selected)
{
const bool hot = Hovered(ui, b);
const Color c = !b.enabled ? COL_DEAD : ((hot || selected) ? COL_PICK : COL_FAINT);
if (b.enabled && hot) Box(l, b.rect, COL_HOVER);
if (b.enabled && (hot || selected))
Box(l, UiRect{b.rect.x, b.rect.y + b.rect.h - 1, b.rect.w, 1}, COL_BLOOD);
Text(l, b.text, b.rect.x + BTN_PAD_X, b.rect.y + BTN_PAD_Y, b.size, c);
}
// Предмет в ячейке: цветной мазок и имя. Цвет берётся из каталога, чтобы
// вещь узнавалась быстрее, чем читается слово.
Редкости и аффиксы: находка стала решением (M25) Предмет перестал быть строкой каталога. ItemInstance несёт редкость и до шести аффиксов, и «карабин» с первого этажа больше не равен «карабину» с сорокового. Четыре редкости, десять тегов, тир и бюджет от глубины. Аффиксы не заводят новых каналов влияния: каждый тег ложится в то, что уже есть — урон оружия, смесь стихий, пробитие, четыре числа бойца, ресурсы вылазки. У предмета по-прежнему нет ручки к правилу «не бить своих», и появиться ей неоткуда. ItemInstance неявно приводится к ItemId, поэтому полторы сотни мест, которым нужен ВИД вещи, остались как были. Цена решения — присваивание в ItemId молча теряет рулоны; таких мест три (подбор, схрон, запись профиля), и все они работают с экземпляром целиком. Замеры поймали три расхождения дизайн-документа с самим собой: * формула редкости противоречила таблице рядом с ней: 2.7% находок с аффиксами на первом этаже против 22%. Выбрана формула, наклон поднят до схождения с таблицей на дне (12% обычных на сотом); * бюджет пускал восемь высших аффиксов вместо обещанных шести; * предельная вещь собиралась к семидесятому этажу вместо сотого. Числа пересчитаны, документ исправлен: 6·cost(9) = 294 <= B(100) = 295 < 7·cost(9) = 343. Предельная вещь существует ровно на дне и ровно одна. Проверка в --descend меряет не бросок, а ПРОВОДКУ до бойца — там, где дважды умирали вес и стихии: аффиксы: урон 12.0 -> 18.0, здоровье 100 -> 140, броня 0.10 -> 0.25, пробитие 0.20, KIN 1.00 -> 0.60, CRY 0.40, сумма долей 1.000 Последнее число не украшение: конверсия обязана переводить урон, а не добавлять седьмую долю сверх единицы. Попутно, на глубине 30 и только там: * «зачистка поля» в проверках не держалась — у твари есть откат воскрешения, и с нулевым таймером она оживала тем же шагом. Лечение вне схватки и расход света на стоянке молча мерили бой. На восьмом этаже тварей мало, и проверка проходила по удаче; * порог «стоянка бесплатна» стоял в единицах заряда, а тот же шаг стоит дороже с глубиной. Переведён в тайлы — и сразу показал, что отряд в строю проходит 0.8 тайла за две секунды «стоянки». Свет теперь списывается только в ADVANCING: топтание и драка бесплатны, тратит РАЗВЕДКА. Без этого таймер возвращался через чёрный ход. Формат профиля 4: токен предмета вида rifle@relic+dmg7,0:0.63. Миграции не требует — у старых токенов нет хвоста после '@'. --accept PASS без изменений: в песочнице находок нет вовсе. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:35:00 +07:00
void DrawItem(const Layout& l, const ItemInstance& it, int x, int y, int h, bool bright)
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
{
Редкости и аффиксы: находка стала решением (M25) Предмет перестал быть строкой каталога. ItemInstance несёт редкость и до шести аффиксов, и «карабин» с первого этажа больше не равен «карабину» с сорокового. Четыре редкости, десять тегов, тир и бюджет от глубины. Аффиксы не заводят новых каналов влияния: каждый тег ложится в то, что уже есть — урон оружия, смесь стихий, пробитие, четыре числа бойца, ресурсы вылазки. У предмета по-прежнему нет ручки к правилу «не бить своих», и появиться ей неоткуда. ItemInstance неявно приводится к ItemId, поэтому полторы сотни мест, которым нужен ВИД вещи, остались как были. Цена решения — присваивание в ItemId молча теряет рулоны; таких мест три (подбор, схрон, запись профиля), и все они работают с экземпляром целиком. Замеры поймали три расхождения дизайн-документа с самим собой: * формула редкости противоречила таблице рядом с ней: 2.7% находок с аффиксами на первом этаже против 22%. Выбрана формула, наклон поднят до схождения с таблицей на дне (12% обычных на сотом); * бюджет пускал восемь высших аффиксов вместо обещанных шести; * предельная вещь собиралась к семидесятому этажу вместо сотого. Числа пересчитаны, документ исправлен: 6·cost(9) = 294 <= B(100) = 295 < 7·cost(9) = 343. Предельная вещь существует ровно на дне и ровно одна. Проверка в --descend меряет не бросок, а ПРОВОДКУ до бойца — там, где дважды умирали вес и стихии: аффиксы: урон 12.0 -> 18.0, здоровье 100 -> 140, броня 0.10 -> 0.25, пробитие 0.20, KIN 1.00 -> 0.60, CRY 0.40, сумма долей 1.000 Последнее число не украшение: конверсия обязана переводить урон, а не добавлять седьмую долю сверх единицы. Попутно, на глубине 30 и только там: * «зачистка поля» в проверках не держалась — у твари есть откат воскрешения, и с нулевым таймером она оживала тем же шагом. Лечение вне схватки и расход света на стоянке молча мерили бой. На восьмом этаже тварей мало, и проверка проходила по удаче; * порог «стоянка бесплатна» стоял в единицах заряда, а тот же шаг стоит дороже с глубиной. Переведён в тайлы — и сразу показал, что отряд в строю проходит 0.8 тайла за две секунды «стоянки». Свет теперь списывается только в ADVANCING: топтание и драка бесплатны, тратит РАЗВЕДКА. Без этого таймер возвращался через чёрный ход. Формат профиля 4: токен предмета вида rifle@relic+dmg7,0:0.63. Миграции не требует — у старых токенов нет хвоста после '@'. --accept PASS без изменений: в песочнице находок нет вовсе. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:35:00 +07:00
if (it.Empty())
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
{
Text(l, "-", x + 2, y + h / 2 - 4, 8, COL_FAINT);
return;
}
Редкости и аффиксы: находка стала решением (M25) Предмет перестал быть строкой каталога. ItemInstance несёт редкость и до шести аффиксов, и «карабин» с первого этажа больше не равен «карабину» с сорокового. Четыре редкости, десять тегов, тир и бюджет от глубины. Аффиксы не заводят новых каналов влияния: каждый тег ложится в то, что уже есть — урон оружия, смесь стихий, пробитие, четыре числа бойца, ресурсы вылазки. У предмета по-прежнему нет ручки к правилу «не бить своих», и появиться ей неоткуда. ItemInstance неявно приводится к ItemId, поэтому полторы сотни мест, которым нужен ВИД вещи, остались как были. Цена решения — присваивание в ItemId молча теряет рулоны; таких мест три (подбор, схрон, запись профиля), и все они работают с экземпляром целиком. Замеры поймали три расхождения дизайн-документа с самим собой: * формула редкости противоречила таблице рядом с ней: 2.7% находок с аффиксами на первом этаже против 22%. Выбрана формула, наклон поднят до схождения с таблицей на дне (12% обычных на сотом); * бюджет пускал восемь высших аффиксов вместо обещанных шести; * предельная вещь собиралась к семидесятому этажу вместо сотого. Числа пересчитаны, документ исправлен: 6·cost(9) = 294 <= B(100) = 295 < 7·cost(9) = 343. Предельная вещь существует ровно на дне и ровно одна. Проверка в --descend меряет не бросок, а ПРОВОДКУ до бойца — там, где дважды умирали вес и стихии: аффиксы: урон 12.0 -> 18.0, здоровье 100 -> 140, броня 0.10 -> 0.25, пробитие 0.20, KIN 1.00 -> 0.60, CRY 0.40, сумма долей 1.000 Последнее число не украшение: конверсия обязана переводить урон, а не добавлять седьмую долю сверх единицы. Попутно, на глубине 30 и только там: * «зачистка поля» в проверках не держалась — у твари есть откат воскрешения, и с нулевым таймером она оживала тем же шагом. Лечение вне схватки и расход света на стоянке молча мерили бой. На восьмом этаже тварей мало, и проверка проходила по удаче; * порог «стоянка бесплатна» стоял в единицах заряда, а тот же шаг стоит дороже с глубиной. Переведён в тайлы — и сразу показал, что отряд в строю проходит 0.8 тайла за две секунды «стоянки». Свет теперь списывается только в ADVANCING: топтание и драка бесплатны, тратит РАЗВЕДКА. Без этого таймер возвращался через чёрный ход. Формат профиля 4: токен предмета вида rifle@relic+dmg7,0:0.63. Миграции не требует — у старых токенов нет хвоста после '@'. --accept PASS без изменений: в песочнице находок нет вовсе. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:35:00 +07:00
const ItemDef d = ItemDefOf(it.id);
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
Box(l, UiRect{x, y + h / 2 - 4, 8, 8}, Color{d.r, d.g, d.b, 255});
Редкости и аффиксы: находка стала решением (M25) Предмет перестал быть строкой каталога. ItemInstance несёт редкость и до шести аффиксов, и «карабин» с первого этажа больше не равен «карабину» с сорокового. Четыре редкости, десять тегов, тир и бюджет от глубины. Аффиксы не заводят новых каналов влияния: каждый тег ложится в то, что уже есть — урон оружия, смесь стихий, пробитие, четыре числа бойца, ресурсы вылазки. У предмета по-прежнему нет ручки к правилу «не бить своих», и появиться ей неоткуда. ItemInstance неявно приводится к ItemId, поэтому полторы сотни мест, которым нужен ВИД вещи, остались как были. Цена решения — присваивание в ItemId молча теряет рулоны; таких мест три (подбор, схрон, запись профиля), и все они работают с экземпляром целиком. Замеры поймали три расхождения дизайн-документа с самим собой: * формула редкости противоречила таблице рядом с ней: 2.7% находок с аффиксами на первом этаже против 22%. Выбрана формула, наклон поднят до схождения с таблицей на дне (12% обычных на сотом); * бюджет пускал восемь высших аффиксов вместо обещанных шести; * предельная вещь собиралась к семидесятому этажу вместо сотого. Числа пересчитаны, документ исправлен: 6·cost(9) = 294 <= B(100) = 295 < 7·cost(9) = 343. Предельная вещь существует ровно на дне и ровно одна. Проверка в --descend меряет не бросок, а ПРОВОДКУ до бойца — там, где дважды умирали вес и стихии: аффиксы: урон 12.0 -> 18.0, здоровье 100 -> 140, броня 0.10 -> 0.25, пробитие 0.20, KIN 1.00 -> 0.60, CRY 0.40, сумма долей 1.000 Последнее число не украшение: конверсия обязана переводить урон, а не добавлять седьмую долю сверх единицы. Попутно, на глубине 30 и только там: * «зачистка поля» в проверках не держалась — у твари есть откат воскрешения, и с нулевым таймером она оживала тем же шагом. Лечение вне схватки и расход света на стоянке молча мерили бой. На восьмом этаже тварей мало, и проверка проходила по удаче; * порог «стоянка бесплатна» стоял в единицах заряда, а тот же шаг стоит дороже с глубиной. Переведён в тайлы — и сразу показал, что отряд в строю проходит 0.8 тайла за две секунды «стоянки». Свет теперь списывается только в ADVANCING: топтание и драка бесплатны, тратит РАЗВЕДКА. Без этого таймер возвращался через чёрный ход. Формат профиля 4: токен предмета вида rifle@relic+dmg7,0:0.63. Миграции не требует — у старых токенов нет хвоста после '@'. --accept PASS без изменений: в песочнице находок нет вовсе. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:35:00 +07:00
// Имя красится по РЕДКОСТИ, а мазок остаётся цветом вида. Так вещь
// по-прежнему узнаётся быстрее, чем читается, но «это реликт» видно, не
// наводя курсор: иначе редкость приходилось бы искать, а её смысл в том,
// чтобы бросаться в глаза среди шестнадцати ячеек.
Color name = bright ? COL_TEXT : COL_FAINT;
if (it.rarity != Rarity::COMMON)
{
const RarityColor rc = RarityColorOf(it.rarity);
const unsigned char k = bright ? 255 : 190;
name = Color{(unsigned char)(rc.r * k / 255), (unsigned char)(rc.g * k / 255),
(unsigned char)(rc.b * k / 255), 255};
}
Text(l, d.name, x + 12, y + h / 2 - 4, 8, name);
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
}
// Ячейка инвентаря. Рисуется строго по прямоугольнику из таблицы — тому же,
// по которому мышь проверяет попадание.
void DrawCell(const Layout& l, const Ui& ui, const Widget& c, bool selected)
{
const bool hot = Hovered(ui, c);
// Пока предмет висит на курсоре, ячейка честно говорит, примет ли она его.
const bool bad = hot && ui.dragging && !ui.dropOk;
if (bad) Box(l, c.rect, COL_REJECT);
else if (hot) Box(l, c.rect, COL_HOVER);
BoxLines(l, c.rect, bad ? COL_BLOOD : (hot ? COL_TEXT : (selected ? COL_PICK : COL_EDGE)));
int tx = c.rect.x + 5;
if (c.text)
{
Text(l, c.text, tx, c.rect.y + c.rect.h / 2 - 3, 7, COL_FAINT);
tx += SQ_LABEL_W;
}
// То, что сейчас на курсоре, из своей ячейки убрано: иначе предмет
// выглядел бы лежащим в двух местах сразу.
const bool taken = ui.dragging && ui.dragFrom == c.part && ui.dragIndex == c.index;
Редкости и аффиксы: находка стала решением (M25) Предмет перестал быть строкой каталога. ItemInstance несёт редкость и до шести аффиксов, и «карабин» с первого этажа больше не равен «карабину» с сорокового. Четыре редкости, десять тегов, тир и бюджет от глубины. Аффиксы не заводят новых каналов влияния: каждый тег ложится в то, что уже есть — урон оружия, смесь стихий, пробитие, четыре числа бойца, ресурсы вылазки. У предмета по-прежнему нет ручки к правилу «не бить своих», и появиться ей неоткуда. ItemInstance неявно приводится к ItemId, поэтому полторы сотни мест, которым нужен ВИД вещи, остались как были. Цена решения — присваивание в ItemId молча теряет рулоны; таких мест три (подбор, схрон, запись профиля), и все они работают с экземпляром целиком. Замеры поймали три расхождения дизайн-документа с самим собой: * формула редкости противоречила таблице рядом с ней: 2.7% находок с аффиксами на первом этаже против 22%. Выбрана формула, наклон поднят до схождения с таблицей на дне (12% обычных на сотом); * бюджет пускал восемь высших аффиксов вместо обещанных шести; * предельная вещь собиралась к семидесятому этажу вместо сотого. Числа пересчитаны, документ исправлен: 6·cost(9) = 294 <= B(100) = 295 < 7·cost(9) = 343. Предельная вещь существует ровно на дне и ровно одна. Проверка в --descend меряет не бросок, а ПРОВОДКУ до бойца — там, где дважды умирали вес и стихии: аффиксы: урон 12.0 -> 18.0, здоровье 100 -> 140, броня 0.10 -> 0.25, пробитие 0.20, KIN 1.00 -> 0.60, CRY 0.40, сумма долей 1.000 Последнее число не украшение: конверсия обязана переводить урон, а не добавлять седьмую долю сверх единицы. Попутно, на глубине 30 и только там: * «зачистка поля» в проверках не держалась — у твари есть откат воскрешения, и с нулевым таймером она оживала тем же шагом. Лечение вне схватки и расход света на стоянке молча мерили бой. На восьмом этаже тварей мало, и проверка проходила по удаче; * порог «стоянка бесплатна» стоял в единицах заряда, а тот же шаг стоит дороже с глубиной. Переведён в тайлы — и сразу показал, что отряд в строю проходит 0.8 тайла за две секунды «стоянки». Свет теперь списывается только в ADVANCING: топтание и драка бесплатны, тратит РАЗВЕДКА. Без этого таймер возвращался через чёрный ход. Формат профиля 4: токен предмета вида rifle@relic+dmg7,0:0.63. Миграции не требует — у старых токенов нет хвоста после '@'. --accept PASS без изменений: в песочнице находок нет вовсе. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:35:00 +07:00
DrawItem(l, taken ? ItemInstance{} : c.item, tx, c.rect.y, c.rect.h, hot || selected);
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
}
void DrawSquadScreen(const Layout& l, const Game& game, const Ui& ui, const Widgets& w)
{
Shade(l, {4, 4, 6, 240});
TextCentered(l, "SQUAD", 12, 16, COL_TEXT);
Rule(l, 32, 60);
for (int i = 0; i < w.count; ++i)
{
const Widget& c = w.item[i];
if (c.part == UiPart::AGENT)
{
const Agent& a = game.squad.agents[c.index];
const bool sel = (c.index == ui.agentIndex);
const bool hot = Hovered(ui, c);
if (hot) Box(l, c.rect, COL_HOVER);
BoxLines(l, c.rect, sel ? COL_PICK : COL_EDGE);
char buf[8];
std::snprintf(buf, sizeof(buf), "%d", c.index + 1);
Text(l, buf, c.rect.x + 6, c.rect.y + 4, 10, sel ? COL_PICK : COL_FAINT);
// Номер бойца читается по цвету слота и больше ничем — тот же цвет,
// что у силуэта в бою.
const uint32_t sc = SlotColor(c.index);
Box(l, UiRect{c.rect.x + 6, c.rect.y + 18, 12, 3},
Color{uint8_t(sc & 0xFF), uint8_t((sc >> 8) & 0xFF), uint8_t((sc >> 16) & 0xFF),
uint8_t(a.alive ? 255 : 90)});
Text(l, a.alive ? ItemName(a.held) : "DOWN", c.rect.x + 22, c.rect.y + 17, 8,
a.alive ? (sel ? COL_TEXT : COL_FAINT) : COL_BLOOD);
}
else if (IsSlot(c.part))
{
DrawCell(l, ui, c, c.part == UiPart::BAG && c.index == ui.slotIndex);
}
}
Text(l, "BAG", SQ_BAG_X, SQ_BAG_Y - 14, 10, COL_TEXT);
if (game.profile)
{
char cap[32];
std::snprintf(cap, sizeof(cap), "STASH %d/%d", game.profile->StashCount(), STASH_CAP);
Text(l, cap, SQ_STASH_X, SQ_STASH_Y - 12, 10, COL_TEXT);
// Схрон длиннее сетки: без явной подсказки прокрутка не существует.
if (game.profile->StashCount() > SQ_STASH_CELLS)
Text(l, "Q/E SCROLL", SQ_STASH_X + 92, SQ_STASH_Y - 11, 8, COL_FAINT);
}
// Числа те же, что читает симуляция, — иначе экран отряда врал бы.
const Agent& a = game.squad.agents[ui.agentIndex];
char line[64];
std::snprintf(line, sizeof(line), "HP %d/%d", int(a.hp + 0.5f), int(a.maxHp + 0.5f));
Text(l, line, SQ_STAT_X, SQ_STAT_Y, 8, COL_FAINT);
std::snprintf(line, sizeof(line), "ARMOR %d%%", int(a.armor * 100.0f + 0.5f));
Text(l, line, SQ_STAT_X, SQ_STAT_Y + SQ_STAT_STEP, 8, COL_FAINT);
// Скорость и нагрузка — ОДНОЙ строкой, потому что это одно решение:
// цифра SPEED уже включает в себя перегруз, и без веса рядом она выглядит
// необъяснимой. Отдельной строкой ниже LOAD не влезала — упиралась в
// заголовок схрона, и это было видно только на снятом кадре.
std::snprintf(line, sizeof(line), "SPEED %d%% LOAD %.1f/%.0f", int(a.speedMul * 100.0f + 0.5f),
double(a.load), double(CARRY_LIMIT));
Text(l, line, SQ_STAT_X, SQ_STAT_Y + SQ_STAT_STEP * 2, 8,
(a.load > CARRY_LIMIT) ? COL_BLOOD : COL_FAINT);
std::snprintf(line, sizeof(line), "SIGHT %d%%", int(a.sightMul * 100.0f + 0.5f));
Text(l, line, SQ_STAT_X, SQ_STAT_Y + SQ_STAT_STEP * 3, 8, COL_FAINT);
Text(l, "DRAG ITEMS WITH MOUSE RMB ON ITEM DROPS IT", SQ_STAT_X, SQ_HINT_Y, 8,
COL_FAINT);
Text(l, "A/D FIGHTER W/S SLOT ENTER TAKE ESC BACK", SQ_STAT_X,
SQ_HINT_Y + SQ_HINT_STEP, 8, COL_FAINT);
}
// Предмет под курсором: его видно ровно там, где мышь, иначе перенос читается
// как «щёлкнул и вещь телепортировалась».
void DrawDragged(const Layout& l, const Ui& ui)
{
if (!ui.dragging || ui.dragItem == ItemId::NONE) return;
const ItemDef d = ItemDefOf(ui.dragItem);
const int x = int(ui.mx) + 7;
const int y = int(ui.my) + 7;
const int w = TextW(l, d.name, 8) + 18;
Box(l, UiRect{x, y, w, 14}, COL_SHADOW);
BoxLines(l, UiRect{x, y, w, 14}, ui.dropOk ? COL_PICK : COL_BLOOD);
Box(l, UiRect{x + 3, y + 3, 8, 8}, Color{d.r, d.g, d.b, 255});
Text(l, d.name, x + 15, y + 3, 8, COL_TEXT);
}
// Свой курсор: ряды пикселей КАДРА, а не системная стрелка. Сначала контур
// на пиксель шире, потом светлая заливка — так остриё видно и на светлом фоне.
void DrawPointer(const Layout& l, const Ui& ui)
{
constexpr int H = 9; // высота стрелки в пикселях кадра
const int px = int(ui.mx);
const int py = int(ui.my);
for (int r = 0; r <= H; ++r) Box(l, UiRect{px - 1, py - 1 + r, r + 3, 1}, COL_SHADOW);
for (int r = 0; r < H; ++r) Box(l, UiRect{px, py + r, r + 1, 1}, COL_PICK);
}
} // namespace
void Ui::Draw(const Game& game, const ScreenRect& fit) const
{
if (screen == Screen::GAME) return;
const Layout l = Of(fit);
const bool pulse = (int(blink * 2.0f) & 1) == 0;
Widgets w;
BuildWidgets(*this, game, l, w);
switch (screen)
{
case Screen::GAME:
return;
case Screen::MENU:
Shade(l, COL_DIM);
TextCentered(l, "SQUAD PROTOTYPE", 52, 20, COL_TEXT);
Rule(l, 80, 78);
TextCentered(l, "IT IS DARK AND THEY ARE CLOSE", 88, 8, COL_FAINT);
for (int i = 0; i < w.count; ++i) DrawButton(l, *this, w.item[i], i == menuIndex);
TextCentered(l, "MOUSE OR W/S SELECT LMB OR ENTER CONFIRM", 236, 8, COL_FAINT);
break;
case Screen::PAUSE:
Shade(l, {6, 6, 8, 170});
TextCentered(l, "PAUSED", 92, 18, COL_TEXT);
Rule(l, 118, 44);
for (int i = 0; i < w.count; ++i) DrawButton(l, *this, w.item[i], false);
TextCentered(l, "CLICK OR ESC RESUME TAB SQUAD Q MENU", 232, 8, COL_FAINT);
break;
case Screen::DEFEAT:
{
Shade(l, {12, 2, 2, 226});
TextCentered(l, "SQUAD LOST", 96, 22, COL_BLOOD);
Rule(l, 126, 60);
char line[64];
std::snprintf(line, sizeof(line), "KILLS %d SHOTS %d", game.metrics.targetsKilled,
game.metrics.shotsFired);
TextCentered(l, line, 142, 9, COL_FAINT);
// Пульсирует подсветка, а не сама кнопка: мигающую цель мышью
// не поймать.
for (int i = 0; i < w.count; ++i) DrawButton(l, *this, w.item[i], i == 0 && pulse);
TextCentered(l, "CLICK OR ENTER TRY AGAIN ESC MENU", 232, 8, COL_FAINT);
break;
}
case Screen::UPGRADE:
{
Shade(l, COL_DIM);
Дерево улучшений вместо четырёх кнопок (M32) Четыре строки покупок (патроны, переноска, обзор, живучесть) удалены целиком. Выбора в них не было: купить в итоге можно было всё, порядок ни на что не влиял, и через две вылазки экран превращался в кнопку "продолжить". Дерево: пять ветвей по восемь узлов, у каждого пять уровней. Ветви - это пять способов закрыть разрыв из закона масштаба: стволы, клинки, стихии, снабжение, отряд. Ветвь открывается цепочкой - узел доступен, когда взят предыдущий в этой же ветви. Свободная сетка читалась бы как список; цепочка читается как путь. Ни один узел не заводит нового канала влияния: каждый кладёт своё число туда, куда уже кладут снаряжение и аффиксы. Дерево будет расти, и первый же "особенный" узел со своей веткой в коде превратил бы SyncLoadout в свалку. ЭКОНОМИКА. --tool economy поймал, что первая версия дерева стоит 9% одного прохода 1..100: опыт растёт с глубиной квадратично, а дерево с фиксированными ценами выкупается целиком за половину глубокого прохода, и выбор кончается к сороковому этажу навсегда. Введено общее удорожание: каждая вложенная точка поднимает цену всех следующих (цена = база · 1.5^уровень · (1 + 0.15 · вложено)). Полное дерево стоит 34 млн против 41 млн, заложенных в дизайне, то есть около шести с половиной полных проходов. Старые покупки из профиля читаются, но не применяются: потраченный на них опыт возвращается в банк. Молча съесть чужие двадцать часов нельзя. Проверка меряет ПРОВОДКУ по всем пяти ветвям, а не таблицу узлов: дерево: куплено уровней 45, закрытый узел закрыт урон ствола 12.0 -> 14.4, здоровье 100 -> 150, скорость 1.06 -> 1.22 парирование 0.34 -> 0.49, свет 420 -> 620 Попутно: в проекте hollow бегуны и хваты уехали с первых этажей глубже - там одиночка с ножом гиб на первом же этаже по той же причине, по которой это было в основном проекте. Формат профиля 7: строки tree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 23:24:50 +07:00
TextCentered(l, "UPGRADE TREE", 12, 14, COL_TEXT);
Rule(l, 28, 60);
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
Дерево улучшений вместо четырёх кнопок (M32) Четыре строки покупок (патроны, переноска, обзор, живучесть) удалены целиком. Выбора в них не было: купить в итоге можно было всё, порядок ни на что не влиял, и через две вылазки экран превращался в кнопку "продолжить". Дерево: пять ветвей по восемь узлов, у каждого пять уровней. Ветви - это пять способов закрыть разрыв из закона масштаба: стволы, клинки, стихии, снабжение, отряд. Ветвь открывается цепочкой - узел доступен, когда взят предыдущий в этой же ветви. Свободная сетка читалась бы как список; цепочка читается как путь. Ни один узел не заводит нового канала влияния: каждый кладёт своё число туда, куда уже кладут снаряжение и аффиксы. Дерево будет расти, и первый же "особенный" узел со своей веткой в коде превратил бы SyncLoadout в свалку. ЭКОНОМИКА. --tool economy поймал, что первая версия дерева стоит 9% одного прохода 1..100: опыт растёт с глубиной квадратично, а дерево с фиксированными ценами выкупается целиком за половину глубокого прохода, и выбор кончается к сороковому этажу навсегда. Введено общее удорожание: каждая вложенная точка поднимает цену всех следующих (цена = база · 1.5^уровень · (1 + 0.15 · вложено)). Полное дерево стоит 34 млн против 41 млн, заложенных в дизайне, то есть около шести с половиной полных проходов. Старые покупки из профиля читаются, но не применяются: потраченный на них опыт возвращается в банк. Молча съесть чужие двадцать часов нельзя. Проверка меряет ПРОВОДКУ по всем пяти ветвям, а не таблицу узлов: дерево: куплено уровней 45, закрытый узел закрыт урон ствола 12.0 -> 14.4, здоровье 100 -> 150, скорость 1.06 -> 1.22 парирование 0.34 -> 0.49, свет 420 -> 620 Попутно: в проекте hollow бегуны и хваты уехали с первых этажей глубже - там одиночка с ножом гиб на первом же этаже по той же причине, по которой это было в основном проекте. Формат профиля 7: строки tree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 23:24:50 +07:00
char line[96];
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
const float banked = game.profile ? game.profile->bankedXp : 0.0f;
std::snprintf(line, sizeof(line), "BANKED XP %d", int(banked));
Дерево улучшений вместо четырёх кнопок (M32) Четыре строки покупок (патроны, переноска, обзор, живучесть) удалены целиком. Выбора в них не было: купить в итоге можно было всё, порядок ни на что не влиял, и через две вылазки экран превращался в кнопку "продолжить". Дерево: пять ветвей по восемь узлов, у каждого пять уровней. Ветви - это пять способов закрыть разрыв из закона масштаба: стволы, клинки, стихии, снабжение, отряд. Ветвь открывается цепочкой - узел доступен, когда взят предыдущий в этой же ветви. Свободная сетка читалась бы как список; цепочка читается как путь. Ни один узел не заводит нового канала влияния: каждый кладёт своё число туда, куда уже кладут снаряжение и аффиксы. Дерево будет расти, и первый же "особенный" узел со своей веткой в коде превратил бы SyncLoadout в свалку. ЭКОНОМИКА. --tool economy поймал, что первая версия дерева стоит 9% одного прохода 1..100: опыт растёт с глубиной квадратично, а дерево с фиксированными ценами выкупается целиком за половину глубокого прохода, и выбор кончается к сороковому этажу навсегда. Введено общее удорожание: каждая вложенная точка поднимает цену всех следующих (цена = база · 1.5^уровень · (1 + 0.15 · вложено)). Полное дерево стоит 34 млн против 41 млн, заложенных в дизайне, то есть около шести с половиной полных проходов. Старые покупки из профиля читаются, но не применяются: потраченный на них опыт возвращается в банк. Молча съесть чужие двадцать часов нельзя. Проверка меряет ПРОВОДКУ по всем пяти ветвям, а не таблицу узлов: дерево: куплено уровней 45, закрытый узел закрыт урон ствола 12.0 -> 14.4, здоровье 100 -> 150, скорость 1.06 -> 1.22 парирование 0.34 -> 0.49, свет 420 -> 620 Попутно: в проекте hollow бегуны и хваты уехали с первых этажей глубже - там одиночка с ножом гиб на первом же этаже по той же причине, по которой это было в основном проекте. Формат профиля 7: строки tree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 23:24:50 +07:00
TextCentered(l, line, 34, 8, COL_FAINT);
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
Дерево улучшений вместо четырёх кнопок (M32) Четыре строки покупок (патроны, переноска, обзор, живучесть) удалены целиком. Выбора в них не было: купить в итоге можно было всё, порядок ни на что не влиял, и через две вылазки экран превращался в кнопку "продолжить". Дерево: пять ветвей по восемь узлов, у каждого пять уровней. Ветви - это пять способов закрыть разрыв из закона масштаба: стволы, клинки, стихии, снабжение, отряд. Ветвь открывается цепочкой - узел доступен, когда взят предыдущий в этой же ветви. Свободная сетка читалась бы как список; цепочка читается как путь. Ни один узел не заводит нового канала влияния: каждый кладёт своё число туда, куда уже кладут снаряжение и аффиксы. Дерево будет расти, и первый же "особенный" узел со своей веткой в коде превратил бы SyncLoadout в свалку. ЭКОНОМИКА. --tool economy поймал, что первая версия дерева стоит 9% одного прохода 1..100: опыт растёт с глубиной квадратично, а дерево с фиксированными ценами выкупается целиком за половину глубокого прохода, и выбор кончается к сороковому этажу навсегда. Введено общее удорожание: каждая вложенная точка поднимает цену всех следующих (цена = база · 1.5^уровень · (1 + 0.15 · вложено)). Полное дерево стоит 34 млн против 41 млн, заложенных в дизайне, то есть около шести с половиной полных проходов. Старые покупки из профиля читаются, но не применяются: потраченный на них опыт возвращается в банк. Молча съесть чужие двадцать часов нельзя. Проверка меряет ПРОВОДКУ по всем пяти ветвям, а не таблицу узлов: дерево: куплено уровней 45, закрытый узел закрыт урон ствола 12.0 -> 14.4, здоровье 100 -> 150, скорость 1.06 -> 1.22 парирование 0.34 -> 0.49, свет 420 -> 620 Попутно: в проекте hollow бегуны и хваты уехали с первых этажей глубже - там одиночка с ножом гиб на первом же этаже по той же причине, по которой это было в основном проекте. Формат профиля 7: строки tree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 23:24:50 +07:00
// Заголовки ветвей.
for (int b = 0; b < BRANCH_COUNT; ++b)
Отряд растёт с одного оперативника до пяти (M24) Игра начинается ОДНИМ человеком: тактический нож, слабый фонарь, куртка. Остальные четверо нанимаются за опыт по кривой 600/2400/9600/38400 — самая дорогая покупка в игре и намеренно. Одиночка с ножом это первое впечатление, и продать второго бойца за одну вылазку значило бы выбросить весь хоррор начала: вдвоём уже не страшно. NUM_AGENTS осталась величиной времени сборки и означает теперь ВМЕСТИМОСТЬ, а не размер отряда: делать её динамической это сотня мест ради числа, которое всё равно упирается в пять. Занятость слота это Agent::present, и он отличается от alive. Правила боя читают только alive — для них пустой слот и павший одно, и это верно. Разница нужна там, где показывают человека: у павшего есть тело на полу и перечёркнутая карточка, у пустого слота нет ни того, ни другого. Проверка в --descend меряет цепочку «в профиле -> на этаже» и потолок, а главное — переживает ли одиночка первую схватку: отряд: в профиле 1 -> на этаже 1, после найма 2, цена 600, сверх потолка отказ одиночка с ножом на первом этаже: выжил, 100 -> 41, подошло 2, убил 2 Первая версия этой проверки не измеряла ничего: боец стоял минуту, к нему не подошёл никто, и «выжил» было пустым словом. Теперь отряд ставится рядом с тварью. Попутно найдено и исправлено: Profile::Save три вехи не писала ни версию формата, ни улучшения. Купленный запас патронов действовал до конца запуска и молча пропадал при чтении, а профиль без версии читался как формат 1. Круговой прогон этого не видел, потому что сверял три поля из десяти и делал это на пустом профиле, где всё сходится само. Теперь он сверяет поля списком и на набитом профиле. Формат профиля 3: добавлена строка squad. Старый профиль играл впятером и получает пятерых при миграции — отобрать их значило бы съесть отряд, которого игрок не терял. --accept PASS без изменений: без профиля отряд по-прежнему впятером. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 02:29:19 +07:00
{
Дерево улучшений вместо четырёх кнопок (M32) Четыре строки покупок (патроны, переноска, обзор, живучесть) удалены целиком. Выбора в них не было: купить в итоге можно было всё, порядок ни на что не влиял, и через две вылазки экран превращался в кнопку "продолжить". Дерево: пять ветвей по восемь узлов, у каждого пять уровней. Ветви - это пять способов закрыть разрыв из закона масштаба: стволы, клинки, стихии, снабжение, отряд. Ветвь открывается цепочкой - узел доступен, когда взят предыдущий в этой же ветви. Свободная сетка читалась бы как список; цепочка читается как путь. Ни один узел не заводит нового канала влияния: каждый кладёт своё число туда, куда уже кладут снаряжение и аффиксы. Дерево будет расти, и первый же "особенный" узел со своей веткой в коде превратил бы SyncLoadout в свалку. ЭКОНОМИКА. --tool economy поймал, что первая версия дерева стоит 9% одного прохода 1..100: опыт растёт с глубиной квадратично, а дерево с фиксированными ценами выкупается целиком за половину глубокого прохода, и выбор кончается к сороковому этажу навсегда. Введено общее удорожание: каждая вложенная точка поднимает цену всех следующих (цена = база · 1.5^уровень · (1 + 0.15 · вложено)). Полное дерево стоит 34 млн против 41 млн, заложенных в дизайне, то есть около шести с половиной полных проходов. Старые покупки из профиля читаются, но не применяются: потраченный на них опыт возвращается в банк. Молча съесть чужие двадцать часов нельзя. Проверка меряет ПРОВОДКУ по всем пяти ветвям, а не таблицу узлов: дерево: куплено уровней 45, закрытый узел закрыт урон ствола 12.0 -> 14.4, здоровье 100 -> 150, скорость 1.06 -> 1.22 парирование 0.34 -> 0.49, свет 420 -> 620 Попутно: в проекте hollow бегуны и хваты уехали с первых этажей глубже - там одиночка с ножом гиб на первом же этаже по той же причине, по которой это было в основном проекте. Формат профиля 7: строки tree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 23:24:50 +07:00
const UiRect r = TreeNodeRect(b * NODES_PER_BRANCH);
Text(l, BranchName(Branch(b)), r.x + 2, TR_Y - 10, 8, COL_PICK);
Отряд растёт с одного оперативника до пяти (M24) Игра начинается ОДНИМ человеком: тактический нож, слабый фонарь, куртка. Остальные четверо нанимаются за опыт по кривой 600/2400/9600/38400 — самая дорогая покупка в игре и намеренно. Одиночка с ножом это первое впечатление, и продать второго бойца за одну вылазку значило бы выбросить весь хоррор начала: вдвоём уже не страшно. NUM_AGENTS осталась величиной времени сборки и означает теперь ВМЕСТИМОСТЬ, а не размер отряда: делать её динамической это сотня мест ради числа, которое всё равно упирается в пять. Занятость слота это Agent::present, и он отличается от alive. Правила боя читают только alive — для них пустой слот и павший одно, и это верно. Разница нужна там, где показывают человека: у павшего есть тело на полу и перечёркнутая карточка, у пустого слота нет ни того, ни другого. Проверка в --descend меряет цепочку «в профиле -> на этаже» и потолок, а главное — переживает ли одиночка первую схватку: отряд: в профиле 1 -> на этаже 1, после найма 2, цена 600, сверх потолка отказ одиночка с ножом на первом этаже: выжил, 100 -> 41, подошло 2, убил 2 Первая версия этой проверки не измеряла ничего: боец стоял минуту, к нему не подошёл никто, и «выжил» было пустым словом. Теперь отряд ставится рядом с тварью. Попутно найдено и исправлено: Profile::Save три вехи не писала ни версию формата, ни улучшения. Купленный запас патронов действовал до конца запуска и молча пропадал при чтении, а профиль без версии читался как формат 1. Круговой прогон этого не видел, потому что сверял три поля из десяти и делал это на пустом профиле, где всё сходится само. Теперь он сверяет поля списком и на набитом профиле. Формат профиля 3: добавлена строка squad. Старый профиль играл впятером и получает пятерых при миграции — отобрать их значило бы съесть отряд, которого игрок не терял. --accept PASS без изменений: без профиля отряд по-прежнему впятером. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 02:29:19 +07:00
}
Дерево улучшений вместо четырёх кнопок (M32) Четыре строки покупок (патроны, переноска, обзор, живучесть) удалены целиком. Выбора в них не было: купить в итоге можно было всё, порядок ни на что не влиял, и через две вылазки экран превращался в кнопку "продолжить". Дерево: пять ветвей по восемь узлов, у каждого пять уровней. Ветви - это пять способов закрыть разрыв из закона масштаба: стволы, клинки, стихии, снабжение, отряд. Ветвь открывается цепочкой - узел доступен, когда взят предыдущий в этой же ветви. Свободная сетка читалась бы как список; цепочка читается как путь. Ни один узел не заводит нового канала влияния: каждый кладёт своё число туда, куда уже кладут снаряжение и аффиксы. Дерево будет расти, и первый же "особенный" узел со своей веткой в коде превратил бы SyncLoadout в свалку. ЭКОНОМИКА. --tool economy поймал, что первая версия дерева стоит 9% одного прохода 1..100: опыт растёт с глубиной квадратично, а дерево с фиксированными ценами выкупается целиком за половину глубокого прохода, и выбор кончается к сороковому этажу навсегда. Введено общее удорожание: каждая вложенная точка поднимает цену всех следующих (цена = база · 1.5^уровень · (1 + 0.15 · вложено)). Полное дерево стоит 34 млн против 41 млн, заложенных в дизайне, то есть около шести с половиной полных проходов. Старые покупки из профиля читаются, но не применяются: потраченный на них опыт возвращается в банк. Молча съесть чужие двадцать часов нельзя. Проверка меряет ПРОВОДКУ по всем пяти ветвям, а не таблицу узлов: дерево: куплено уровней 45, закрытый узел закрыт урон ствола 12.0 -> 14.4, здоровье 100 -> 150, скорость 1.06 -> 1.22 парирование 0.34 -> 0.49, свет 420 -> 620 Попутно: в проекте hollow бегуны и хваты уехали с первых этажей глубже - там одиночка с ножом гиб на первом же этаже по той же причине, по которой это было в основном проекте. Формат профиля 7: строки tree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 23:24:50 +07:00
// Связи между узлами ветви: тонкая линия сверху вниз. Она и есть
// «цепочка» — узел открывается предыдущим.
for (int i = 0; i < TREE_NODES; ++i)
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
{
Дерево улучшений вместо четырёх кнопок (M32) Четыре строки покупок (патроны, переноска, обзор, живучесть) удалены целиком. Выбора в них не было: купить в итоге можно было всё, порядок ни на что не влиял, и через две вылазки экран превращался в кнопку "продолжить". Дерево: пять ветвей по восемь узлов, у каждого пять уровней. Ветви - это пять способов закрыть разрыв из закона масштаба: стволы, клинки, стихии, снабжение, отряд. Ветвь открывается цепочкой - узел доступен, когда взят предыдущий в этой же ветви. Свободная сетка читалась бы как список; цепочка читается как путь. Ни один узел не заводит нового канала влияния: каждый кладёт своё число туда, куда уже кладут снаряжение и аффиксы. Дерево будет расти, и первый же "особенный" узел со своей веткой в коде превратил бы SyncLoadout в свалку. ЭКОНОМИКА. --tool economy поймал, что первая версия дерева стоит 9% одного прохода 1..100: опыт растёт с глубиной квадратично, а дерево с фиксированными ценами выкупается целиком за половину глубокого прохода, и выбор кончается к сороковому этажу навсегда. Введено общее удорожание: каждая вложенная точка поднимает цену всех следующих (цена = база · 1.5^уровень · (1 + 0.15 · вложено)). Полное дерево стоит 34 млн против 41 млн, заложенных в дизайне, то есть около шести с половиной полных проходов. Старые покупки из профиля читаются, но не применяются: потраченный на них опыт возвращается в банк. Молча съесть чужие двадцать часов нельзя. Проверка меряет ПРОВОДКУ по всем пяти ветвям, а не таблицу узлов: дерево: куплено уровней 45, закрытый узел закрыт урон ствола 12.0 -> 14.4, здоровье 100 -> 150, скорость 1.06 -> 1.22 парирование 0.34 -> 0.49, свет 420 -> 620 Попутно: в проекте hollow бегуны и хваты уехали с первых этажей глубже - там одиночка с ножом гиб на первом же этаже по той же причине, по которой это было в основном проекте. Формат профиля 7: строки tree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 23:24:50 +07:00
const int row = i % NODES_PER_BRANCH;
if (row == 0) continue;
const UiRect a = TreeNodeRect(i - 1);
const bool open = game.profile && game.profile->NodeRank(i - 1) > 0;
Box(l, UiRect{a.x + TR_NODE_W / 2, a.y + TR_NODE_H, 1, TR_ROW_H - TR_NODE_H},
open ? Color{150, 130, 90, 255} : COL_DEAD);
}
for (int i = 0; i < w.count; ++i)
{
const Widget& c = w.item[i];
if (c.part != UiPart::UPGRADE_ITEM) continue;
const TreeNode& n = TreeNodeAt(c.index);
const int rank = game.profile ? game.profile->NodeRank(c.index) : 0;
const bool maxed = rank >= n.maxRank;
const bool afford = game.profile && game.profile->CanBuyNode(c.index);
const bool hover = (hoverPart == c.part && hoverIndex == c.index);
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
Дерево улучшений вместо четырёх кнопок (M32) Четыре строки покупок (патроны, переноска, обзор, живучесть) удалены целиком. Выбора в них не было: купить в итоге можно было всё, порядок ни на что не влиял, и через две вылазки экран превращался в кнопку "продолжить". Дерево: пять ветвей по восемь узлов, у каждого пять уровней. Ветви - это пять способов закрыть разрыв из закона масштаба: стволы, клинки, стихии, снабжение, отряд. Ветвь открывается цепочкой - узел доступен, когда взят предыдущий в этой же ветви. Свободная сетка читалась бы как список; цепочка читается как путь. Ни один узел не заводит нового канала влияния: каждый кладёт своё число туда, куда уже кладут снаряжение и аффиксы. Дерево будет расти, и первый же "особенный" узел со своей веткой в коде превратил бы SyncLoadout в свалку. ЭКОНОМИКА. --tool economy поймал, что первая версия дерева стоит 9% одного прохода 1..100: опыт растёт с глубиной квадратично, а дерево с фиксированными ценами выкупается целиком за половину глубокого прохода, и выбор кончается к сороковому этажу навсегда. Введено общее удорожание: каждая вложенная точка поднимает цену всех следующих (цена = база · 1.5^уровень · (1 + 0.15 · вложено)). Полное дерево стоит 34 млн против 41 млн, заложенных в дизайне, то есть около шести с половиной полных проходов. Старые покупки из профиля читаются, но не применяются: потраченный на них опыт возвращается в банк. Молча съесть чужие двадцать часов нельзя. Проверка меряет ПРОВОДКУ по всем пяти ветвям, а не таблицу узлов: дерево: куплено уровней 45, закрытый узел закрыт урон ствола 12.0 -> 14.4, здоровье 100 -> 150, скорость 1.06 -> 1.22 парирование 0.34 -> 0.49, свет 420 -> 620 Попутно: в проекте hollow бегуны и хваты уехали с первых этажей глубже - там одиночка с ножом гиб на первом же этаже по той же причине, по которой это было в основном проекте. Формат профиля 7: строки tree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 23:24:50 +07:00
if (hover && afford) Box(l, c.rect, COL_HOVER);
// Взятый узел заливается: сетка должна читаться одним взглядом,
// без вчитывания в цифры уровней.
if (rank > 0) Box(l, c.rect, Color{58, 46, 26, 200});
BoxLines(l, c.rect, !c.enabled ? COL_DEAD
: (afford ? Color{150, 130, 90, 255} : COL_EDGE));
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
Дерево улучшений вместо четырёх кнопок (M32) Четыре строки покупок (патроны, переноска, обзор, живучесть) удалены целиком. Выбора в них не было: купить в итоге можно было всё, порядок ни на что не влиял, и через две вылазки экран превращался в кнопку "продолжить". Дерево: пять ветвей по восемь узлов, у каждого пять уровней. Ветви - это пять способов закрыть разрыв из закона масштаба: стволы, клинки, стихии, снабжение, отряд. Ветвь открывается цепочкой - узел доступен, когда взят предыдущий в этой же ветви. Свободная сетка читалась бы как список; цепочка читается как путь. Ни один узел не заводит нового канала влияния: каждый кладёт своё число туда, куда уже кладут снаряжение и аффиксы. Дерево будет расти, и первый же "особенный" узел со своей веткой в коде превратил бы SyncLoadout в свалку. ЭКОНОМИКА. --tool economy поймал, что первая версия дерева стоит 9% одного прохода 1..100: опыт растёт с глубиной квадратично, а дерево с фиксированными ценами выкупается целиком за половину глубокого прохода, и выбор кончается к сороковому этажу навсегда. Введено общее удорожание: каждая вложенная точка поднимает цену всех следующих (цена = база · 1.5^уровень · (1 + 0.15 · вложено)). Полное дерево стоит 34 млн против 41 млн, заложенных в дизайне, то есть около шести с половиной полных проходов. Старые покупки из профиля читаются, но не применяются: потраченный на них опыт возвращается в банк. Молча съесть чужие двадцать часов нельзя. Проверка меряет ПРОВОДКУ по всем пяти ветвям, а не таблицу узлов: дерево: куплено уровней 45, закрытый узел закрыт урон ствола 12.0 -> 14.4, здоровье 100 -> 150, скорость 1.06 -> 1.22 парирование 0.34 -> 0.49, свет 420 -> 620 Попутно: в проекте hollow бегуны и хваты уехали с первых этажей глубже - там одиночка с ножом гиб на первом же этаже по той же причине, по которой это было в основном проекте. Формат профиля 7: строки tree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 23:24:50 +07:00
Text(l, n.name, c.rect.x + 3, c.rect.y + 2, 7,
c.enabled ? (rank > 0 ? COL_TEXT : COL_FAINT) : COL_DEAD);
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
// Уровни точками: пять кружков читаются быстрее, чем «3/5».
Дерево улучшений вместо четырёх кнопок (M32) Четыре строки покупок (патроны, переноска, обзор, живучесть) удалены целиком. Выбора в них не было: купить в итоге можно было всё, порядок ни на что не влиял, и через две вылазки экран превращался в кнопку "продолжить". Дерево: пять ветвей по восемь узлов, у каждого пять уровней. Ветви - это пять способов закрыть разрыв из закона масштаба: стволы, клинки, стихии, снабжение, отряд. Ветвь открывается цепочкой - узел доступен, когда взят предыдущий в этой же ветви. Свободная сетка читалась бы как список; цепочка читается как путь. Ни один узел не заводит нового канала влияния: каждый кладёт своё число туда, куда уже кладут снаряжение и аффиксы. Дерево будет расти, и первый же "особенный" узел со своей веткой в коде превратил бы SyncLoadout в свалку. ЭКОНОМИКА. --tool economy поймал, что первая версия дерева стоит 9% одного прохода 1..100: опыт растёт с глубиной квадратично, а дерево с фиксированными ценами выкупается целиком за половину глубокого прохода, и выбор кончается к сороковому этажу навсегда. Введено общее удорожание: каждая вложенная точка поднимает цену всех следующих (цена = база · 1.5^уровень · (1 + 0.15 · вложено)). Полное дерево стоит 34 млн против 41 млн, заложенных в дизайне, то есть около шести с половиной полных проходов. Старые покупки из профиля читаются, но не применяются: потраченный на них опыт возвращается в банк. Молча съесть чужие двадцать часов нельзя. Проверка меряет ПРОВОДКУ по всем пяти ветвям, а не таблицу узлов: дерево: куплено уровней 45, закрытый узел закрыт урон ствола 12.0 -> 14.4, здоровье 100 -> 150, скорость 1.06 -> 1.22 парирование 0.34 -> 0.49, свет 420 -> 620 Попутно: в проекте hollow бегуны и хваты уехали с первых этажей глубже - там одиночка с ножом гиб на первом же этаже по той же причине, по которой это было в основном проекте. Формат профиля 7: строки tree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 23:24:50 +07:00
for (int k = 0; k < n.maxRank; ++k)
Box(l, UiRect{c.rect.x + 3 + k * 5, c.rect.y + 12, 3, 3},
(k < rank) ? Color{210, 180, 110, 255} : COL_DEAD);
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
if (maxed) std::snprintf(line, sizeof(line), "MAX");
Дерево улучшений вместо четырёх кнопок (M32) Четыре строки покупок (патроны, переноска, обзор, живучесть) удалены целиком. Выбора в них не было: купить в итоге можно было всё, порядок ни на что не влиял, и через две вылазки экран превращался в кнопку "продолжить". Дерево: пять ветвей по восемь узлов, у каждого пять уровней. Ветви - это пять способов закрыть разрыв из закона масштаба: стволы, клинки, стихии, снабжение, отряд. Ветвь открывается цепочкой - узел доступен, когда взят предыдущий в этой же ветви. Свободная сетка читалась бы как список; цепочка читается как путь. Ни один узел не заводит нового канала влияния: каждый кладёт своё число туда, куда уже кладут снаряжение и аффиксы. Дерево будет расти, и первый же "особенный" узел со своей веткой в коде превратил бы SyncLoadout в свалку. ЭКОНОМИКА. --tool economy поймал, что первая версия дерева стоит 9% одного прохода 1..100: опыт растёт с глубиной квадратично, а дерево с фиксированными ценами выкупается целиком за половину глубокого прохода, и выбор кончается к сороковому этажу навсегда. Введено общее удорожание: каждая вложенная точка поднимает цену всех следующих (цена = база · 1.5^уровень · (1 + 0.15 · вложено)). Полное дерево стоит 34 млн против 41 млн, заложенных в дизайне, то есть около шести с половиной полных проходов. Старые покупки из профиля читаются, но не применяются: потраченный на них опыт возвращается в банк. Молча съесть чужие двадцать часов нельзя. Проверка меряет ПРОВОДКУ по всем пяти ветвям, а не таблицу узлов: дерево: куплено уровней 45, закрытый узел закрыт урон ствола 12.0 -> 14.4, здоровье 100 -> 150, скорость 1.06 -> 1.22 парирование 0.34 -> 0.49, свет 420 -> 620 Попутно: в проекте hollow бегуны и хваты уехали с первых этажей глубже - там одиночка с ножом гиб на первом же этаже по той же причине, по которой это было в основном проекте. Формат профиля 7: строки tree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 23:24:50 +07:00
else std::snprintf(line, sizeof(line), "%d",
int(TreeNodeCost(c.index, rank,
game.profile ? game.profile->TreeRanks() : 0)));
Text(l, line, c.rect.x + TR_NODE_W - 3 - TextW(l, line, 7), c.rect.y + 11, 7,
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
maxed ? COL_FAINT : (afford ? COL_PICK : COL_DEAD));
}
Дерево улучшений вместо четырёх кнопок (M32) Четыре строки покупок (патроны, переноска, обзор, живучесть) удалены целиком. Выбора в них не было: купить в итоге можно было всё, порядок ни на что не влиял, и через две вылазки экран превращался в кнопку "продолжить". Дерево: пять ветвей по восемь узлов, у каждого пять уровней. Ветви - это пять способов закрыть разрыв из закона масштаба: стволы, клинки, стихии, снабжение, отряд. Ветвь открывается цепочкой - узел доступен, когда взят предыдущий в этой же ветви. Свободная сетка читалась бы как список; цепочка читается как путь. Ни один узел не заводит нового канала влияния: каждый кладёт своё число туда, куда уже кладут снаряжение и аффиксы. Дерево будет расти, и первый же "особенный" узел со своей веткой в коде превратил бы SyncLoadout в свалку. ЭКОНОМИКА. --tool economy поймал, что первая версия дерева стоит 9% одного прохода 1..100: опыт растёт с глубиной квадратично, а дерево с фиксированными ценами выкупается целиком за половину глубокого прохода, и выбор кончается к сороковому этажу навсегда. Введено общее удорожание: каждая вложенная точка поднимает цену всех следующих (цена = база · 1.5^уровень · (1 + 0.15 · вложено)). Полное дерево стоит 34 млн против 41 млн, заложенных в дизайне, то есть около шести с половиной полных проходов. Старые покупки из профиля читаются, но не применяются: потраченный на них опыт возвращается в банк. Молча съесть чужие двадцать часов нельзя. Проверка меряет ПРОВОДКУ по всем пяти ветвям, а не таблицу узлов: дерево: куплено уровней 45, закрытый узел закрыт урон ствола 12.0 -> 14.4, здоровье 100 -> 150, скорость 1.06 -> 1.22 парирование 0.34 -> 0.49, свет 420 -> 620 Попутно: в проекте hollow бегуны и хваты уехали с первых этажей глубже - там одиночка с ножом гиб на первом же этаже по той же причине, по которой это было в основном проекте. Формат профиля 7: строки tree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 23:24:50 +07:00
// Подсказка по узлу под курсором: что он даёт и сколько уже дал.
if (hoverPart == UiPart::UPGRADE_ITEM && hoverIndex >= 0 && hoverIndex < TREE_NODES)
{
const TreeNode& n = TreeNodeAt(hoverIndex);
const int rank = game.profile ? game.profile->NodeRank(hoverIndex) : 0;
const bool percent = n.perRank < 1.0f;
if (percent)
std::snprintf(line, sizeof(line), "%s: %+d%% PER RANK, NOW %+d%%",
NodeEffectName(n.effect), int(n.perRank * 100.0f + 0.5f),
int(n.perRank * float(rank) * 100.0f + 0.5f));
else
std::snprintf(line, sizeof(line), "%s: %+d PER RANK, NOW %+d",
NodeEffectName(n.effect), int(n.perRank + 0.5f),
int(n.perRank * float(rank) + 0.5f));
TextCentered(l, line, 252, 8, COL_TEXT);
}
TextCentered(l, "CLICK TO SPEND ESC BACK", 262, 7, COL_FAINT);
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
break;
}
Материалы и мастерская: вещь можно доработать (M27) Аффиксы сделали находку решением, но решением одноразовым: выпало как выпало. Материалы дают второй способ получить нужное — не «повезло», а «доработал». Пять материалов, пять операций, экран WORKSHOP. Главное правило экономики — материал добывается ТОЛЬКО В СВОЕЙ ПОЛОСЕ: био-ядра с плотских этажей, крио с мёрзлых. Это единственная причина возвращаться на средние глубины, когда рекорд ушёл вниз; без полос весь контент, кроме последнего акта, умирает в день прохождения. Материалы — валюта, а не предметы: не занимают ячеек, не весят, в схроне не лежат. Но теряются как добыча: зачисляются только при эвакуации. Три уточнения операций, каждое против конкретного обхода: перековка меняет только значения (иначе это новый бросок предмета в обход редкости), прививка не ставит уже стоящий тег (иначе «+урон» шесть раз), возвышается случайный аффикс (иначе все предметы сойдутся к одному). --tool craft поймал, что материалы бесплатны. С твари падало по её danger, и вылазка на восьмидесятом этаже приносила тысячу ядер при цене доводки вещи в шестьсот: 0.6 вылазки на предмет вместо восьми. Правило заменено на «одно ядро с мелочи, два с крупной» — стало 6.2. Сам инструмент при этом тоже считал неверно: брал доход из бюджета опасности этажа, хотя на этаж влезает не больше MAX_LEVEL_SPAWNS тварей. Бюджет уходит не в количество тварей, а в их силу, и доход упирается в потолок расстановки с сорокового этажа. Глубина даёт не количество материалов, а качество предметов — и вместе с ним цену доводки. Залом замерен на тысяче попыток: рассыпалось 11.6% при заявленных 12%, плата снимается всегда, слот теряется всегда. «У меня трижды не рассыпалось» не доказательство ни в какую сторону. Пункт меню WORKSHOP проверен --ui-check — той самой проверкой, которая появилась вчера из-за пункта UPGRADES, молча закрывавшего игру. Формат профиля 5: строка mat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 07:07:46 +07:00
case Screen::WORKSHOP:
{
Shade(l, COL_DIM);
TextCentered(l, "WORKSHOP", 12, 16, COL_TEXT);
Rule(l, 32, 56);
char line[96];
// Материалы строкой сверху. Они валюта, и место им там же, где у
// улучшений стоит запас опыта: игрок читает «на что мне хватает»
// до того, как выбирает вещь.
if (game.profile)
{
const Profile& pr = *game.profile;
std::snprintf(line, sizeof(line), "%d SCRAP %d BIO %d CRYO %d SHARD %d SLAG",
pr.material[0], pr.material[1], pr.material[2], pr.material[3],
pr.material[4]);
TextCentered(l, line, 40, 8, COL_FAINT);
}
Крафт стал выбором: закалка, отпечаток, направленные операции (M33) Прежние пять операций были СЛЕПЫМИ: перековка бросала всё заново, прививка ставила случайный тег, отсечение снимало последний аффикс, возвышение поднимало случайный. Игрок не принимал ни одного решения - жал кнопку и смотрел, что выпало. Это не крафт, это второй бросок предмета за материалы. Теперь половина операций направленные: игрок показывает пальцем, какой аффикс трогать, и платит за это вдвое - вот это уже решение ("перебросить всё дёшево или снять один дорого"). Два новых глагола: ЗАКАЛКА двигает рулон выбранного аффикса к максимуму его тира на треть остатка. Дёшево, понятно, повторяемо - то самое "доработать почти хорошую вещь". Тир не трогает: за ступень платят возвышением. ОТПЕЧАТОК переносит лучший аффикс с ДРУГОЙ вещи, сжигая её. Именно он превращает хлам из схрона в ресурс, а не в мусор, который лежит. Правило "один тег один раз" им не обходится, бюджет проверяется. В мастерской: клик по вещи кладёт её на верстак, повторный клик по той же назначает донором (помечается кровью - он сгорит, и видеть это надо ДО нажатия). Строки аффиксов стали кнопками: по ним выбирается цель. Проверка меряет НАПРАВЛЕННОСТЬ, а не факт срабатывания: направленность: отсечение по цели, возвышение по цели, закалка двигает рулон, отпечаток перенёс и сжёг донора Отсечение пробуется на СРЕДНЕМ аффиксе из трёх: операция, которая "работает", снимая последний, прошла бы и слепую версию. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 23:37:07 +07:00
Text(l, "STASH (CLICK TWICE = DONOR)", WS_SLOT_X, 60, 8, COL_FAINT);
Материалы и мастерская: вещь можно доработать (M27) Аффиксы сделали находку решением, но решением одноразовым: выпало как выпало. Материалы дают второй способ получить нужное — не «повезло», а «доработал». Пять материалов, пять операций, экран WORKSHOP. Главное правило экономики — материал добывается ТОЛЬКО В СВОЕЙ ПОЛОСЕ: био-ядра с плотских этажей, крио с мёрзлых. Это единственная причина возвращаться на средние глубины, когда рекорд ушёл вниз; без полос весь контент, кроме последнего акта, умирает в день прохождения. Материалы — валюта, а не предметы: не занимают ячеек, не весят, в схроне не лежат. Но теряются как добыча: зачисляются только при эвакуации. Три уточнения операций, каждое против конкретного обхода: перековка меняет только значения (иначе это новый бросок предмета в обход редкости), прививка не ставит уже стоящий тег (иначе «+урон» шесть раз), возвышается случайный аффикс (иначе все предметы сойдутся к одному). --tool craft поймал, что материалы бесплатны. С твари падало по её danger, и вылазка на восьмидесятом этаже приносила тысячу ядер при цене доводки вещи в шестьсот: 0.6 вылазки на предмет вместо восьми. Правило заменено на «одно ядро с мелочи, два с крупной» — стало 6.2. Сам инструмент при этом тоже считал неверно: брал доход из бюджета опасности этажа, хотя на этаж влезает не больше MAX_LEVEL_SPAWNS тварей. Бюджет уходит не в количество тварей, а в их силу, и доход упирается в потолок расстановки с сорокового этажа. Глубина даёт не количество материалов, а качество предметов — и вместе с ним цену доводки. Залом замерен на тысяче попыток: рассыпалось 11.6% при заявленных 12%, плата снимается всегда, слот теряется всегда. «У меня трижды не рассыпалось» не доказательство ни в какую сторону. Пункт меню WORKSHOP проверен --ui-check — той самой проверкой, которая появилась вчера из-за пункта UPGRADES, молча закрывавшего игру. Формат профиля 5: строка mat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 07:07:46 +07:00
Text(l, "BENCH", WS_OP_X, 60, 8, COL_FAINT);
// --- ячейки схрона ---
for (int i = 0; i < w.count; ++i)
{
const Widget& c = w.item[i];
if (c.part != UiPart::CRAFT_SLOT) continue;
DrawCell(l, *this, c, c.index == craftIndex);
Крафт стал выбором: закалка, отпечаток, направленные операции (M33) Прежние пять операций были СЛЕПЫМИ: перековка бросала всё заново, прививка ставила случайный тег, отсечение снимало последний аффикс, возвышение поднимало случайный. Игрок не принимал ни одного решения - жал кнопку и смотрел, что выпало. Это не крафт, это второй бросок предмета за материалы. Теперь половина операций направленные: игрок показывает пальцем, какой аффикс трогать, и платит за это вдвое - вот это уже решение ("перебросить всё дёшево или снять один дорого"). Два новых глагола: ЗАКАЛКА двигает рулон выбранного аффикса к максимуму его тира на треть остатка. Дёшево, понятно, повторяемо - то самое "доработать почти хорошую вещь". Тир не трогает: за ступень платят возвышением. ОТПЕЧАТОК переносит лучший аффикс с ДРУГОЙ вещи, сжигая её. Именно он превращает хлам из схрона в ресурс, а не в мусор, который лежит. Правило "один тег один раз" им не обходится, бюджет проверяется. В мастерской: клик по вещи кладёт её на верстак, повторный клик по той же назначает донором (помечается кровью - он сгорит, и видеть это надо ДО нажатия). Строки аффиксов стали кнопками: по ним выбирается цель. Проверка меряет НАПРАВЛЕННОСТЬ, а не факт срабатывания: направленность: отсечение по цели, возвышение по цели, закалка двигает рулон, отпечаток перенёс и сжёг донора Отсечение пробуется на СРЕДНЕМ аффиксе из трёх: операция, которая "работает", снимая последний, прошла бы и слепую версию. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 23:37:07 +07:00
// Донор помечается кровью: он сгорит, и увидеть это надо ДО
// нажатия, а не после.
if (c.index == craftDonor)
Box(l, UiRect{c.rect.x + c.rect.w - 5, c.rect.y + 2, 3, 3}, COL_BLOOD);
Материалы и мастерская: вещь можно доработать (M27) Аффиксы сделали находку решением, но решением одноразовым: выпало как выпало. Материалы дают второй способ получить нужное — не «повезло», а «доработал». Пять материалов, пять операций, экран WORKSHOP. Главное правило экономики — материал добывается ТОЛЬКО В СВОЕЙ ПОЛОСЕ: био-ядра с плотских этажей, крио с мёрзлых. Это единственная причина возвращаться на средние глубины, когда рекорд ушёл вниз; без полос весь контент, кроме последнего акта, умирает в день прохождения. Материалы — валюта, а не предметы: не занимают ячеек, не весят, в схроне не лежат. Но теряются как добыча: зачисляются только при эвакуации. Три уточнения операций, каждое против конкретного обхода: перековка меняет только значения (иначе это новый бросок предмета в обход редкости), прививка не ставит уже стоящий тег (иначе «+урон» шесть раз), возвышается случайный аффикс (иначе все предметы сойдутся к одному). --tool craft поймал, что материалы бесплатны. С твари падало по её danger, и вылазка на восьмидесятом этаже приносила тысячу ядер при цене доводки вещи в шестьсот: 0.6 вылазки на предмет вместо восьми. Правило заменено на «одно ядро с мелочи, два с крупной» — стало 6.2. Сам инструмент при этом тоже считал неверно: брал доход из бюджета опасности этажа, хотя на этаж влезает не больше MAX_LEVEL_SPAWNS тварей. Бюджет уходит не в количество тварей, а в их силу, и доход упирается в потолок расстановки с сорокового этажа. Глубина даёт не количество материалов, а качество предметов — и вместе с ним цену доводки. Залом замерен на тысяче попыток: рассыпалось 11.6% при заявленных 12%, плата снимается всегда, слот теряется всегда. «У меня трижды не рассыпалось» не доказательство ни в какую сторону. Пункт меню WORKSHOP проверен --ui-check — той самой проверкой, которая появилась вчера из-за пункта UPGRADES, молча закрывавшего игру. Формат профиля 5: строка mat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 07:07:46 +07:00
}
// --- что лежит на верстаке ---
const ItemInstance* bench = nullptr;
if (game.profile && craftIndex >= 0 && craftIndex < game.profile->StashCount())
bench = &game.profile->stash[size_t(craftIndex)];
if (!bench)
{
Text(l, "STASH IS EMPTY", WS_OP_X, 74, 9, COL_FAINT);
}
else
{
const ItemDef d = ItemDefOf(bench->id);
const RarityColor rc = RarityColorOf(bench->rarity);
std::snprintf(line, sizeof(line), "%s %s", RarityName(bench->rarity), d.name);
Text(l, line, WS_OP_X, 72, 10, Color{rc.r, rc.g, rc.b, 255});
// Аффиксы столбиком слева от операций: игрок должен видеть, что
// именно он собирается менять, не наводя курсор.
for (int a = 0; a < bench->Count(); ++a)
{
char af[64];
AffixLabel(bench->affix[a], af, int(sizeof(af)));
Крафт стал выбором: закалка, отпечаток, направленные операции (M33) Прежние пять операций были СЛЕПЫМИ: перековка бросала всё заново, прививка ставила случайный тег, отсечение снимало последний аффикс, возвышение поднимало случайный. Игрок не принимал ни одного решения - жал кнопку и смотрел, что выпало. Это не крафт, это второй бросок предмета за материалы. Теперь половина операций направленные: игрок показывает пальцем, какой аффикс трогать, и платит за это вдвое - вот это уже решение ("перебросить всё дёшево или снять один дорого"). Два новых глагола: ЗАКАЛКА двигает рулон выбранного аффикса к максимуму его тира на треть остатка. Дёшево, понятно, повторяемо - то самое "доработать почти хорошую вещь". Тир не трогает: за ступень платят возвышением. ОТПЕЧАТОК переносит лучший аффикс с ДРУГОЙ вещи, сжигая её. Именно он превращает хлам из схрона в ресурс, а не в мусор, который лежит. Правило "один тег один раз" им не обходится, бюджет проверяется. В мастерской: клик по вещи кладёт её на верстак, повторный клик по той же назначает донором (помечается кровью - он сгорит, и видеть это надо ДО нажатия). Строки аффиксов стали кнопками: по ним выбирается цель. Проверка меряет НАПРАВЛЕННОСТЬ, а не факт срабатывания: направленность: отсечение по цели, возвышение по цели, закалка двигает рулон, отпечаток перенёс и сжёг донора Отсечение пробуется на СРЕДНЕМ аффиксе из трёх: операция, которая "работает", снимая последний, прошла бы и слепую версию. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 23:37:07 +07:00
// Выбранная строка подсвечена: направленные операции
// работают именно по ней, и промах тут стоит материалов.
const bool picked = (a == craftAffix);
const bool hot = (hoverPart == UiPart::CRAFT_AFFIX && hoverIndex == a);
if (picked) Box(l, UiRect{WS_SLOT_X - 2, 198 + a * 9, 212, 9}, COL_HOVER);
Text(l, picked ? ">" : " ", WS_SLOT_X - 6, 200 + a * 9, 8, COL_PICK);
Text(l, af, WS_SLOT_X, 200 + a * 9, 8,
(picked || hot) ? COL_TEXT : COL_FAINT);
Материалы и мастерская: вещь можно доработать (M27) Аффиксы сделали находку решением, но решением одноразовым: выпало как выпало. Материалы дают второй способ получить нужное — не «повезло», а «доработал». Пять материалов, пять операций, экран WORKSHOP. Главное правило экономики — материал добывается ТОЛЬКО В СВОЕЙ ПОЛОСЕ: био-ядра с плотских этажей, крио с мёрзлых. Это единственная причина возвращаться на средние глубины, когда рекорд ушёл вниз; без полос весь контент, кроме последнего акта, умирает в день прохождения. Материалы — валюта, а не предметы: не занимают ячеек, не весят, в схроне не лежат. Но теряются как добыча: зачисляются только при эвакуации. Три уточнения операций, каждое против конкретного обхода: перековка меняет только значения (иначе это новый бросок предмета в обход редкости), прививка не ставит уже стоящий тег (иначе «+урон» шесть раз), возвышается случайный аффикс (иначе все предметы сойдутся к одному). --tool craft поймал, что материалы бесплатны. С твари падало по её danger, и вылазка на восьмидесятом этаже приносила тысячу ядер при цене доводки вещи в шестьсот: 0.6 вылазки на предмет вместо восьми. Правило заменено на «одно ядро с мелочи, два с крупной» — стало 6.2. Сам инструмент при этом тоже считал неверно: брал доход из бюджета опасности этажа, хотя на этаж влезает не больше MAX_LEVEL_SPAWNS тварей. Бюджет уходит не в количество тварей, а в их силу, и доход упирается в потолок расстановки с сорокового этажа. Глубина даёт не количество материалов, а качество предметов — и вместе с ним цену доводки. Залом замерен на тысяче попыток: рассыпалось 11.6% при заявленных 12%, плата снимается всегда, слот теряется всегда. «У меня трижды не рассыпалось» не доказательство ни в какую сторону. Пункт меню WORKSHOP проверен --ui-check — той самой проверкой, которая появилась вчера из-за пункта UPGRADES, молча закрывавшего игру. Формат профиля 5: строка mat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 07:07:46 +07:00
}
if (bench->Count() == 0)
Text(l, "NO AFFIXES", WS_SLOT_X, 200, 8, COL_DEAD);
}
// --- операции ---
for (int o = 0; o < int(CraftOp::COUNT); ++o)
{
const CraftOp op = CraftOp(o);
const UiRect r = CraftOpRect(o);
const bool hover = (hoverPart == UiPart::CRAFT_ITEM && hoverIndex == o);
bool afford = false;
CraftCost cost;
if (bench && game.profile)
{
cost = CostOf(op, *bench);
afford = true;
for (int m = 0; m < MATERIAL_COUNT; ++m)
if (game.profile->material[m] < cost.amount[m]) afford = false;
}
if (hover && afford) Box(l, r, COL_HOVER);
BoxLines(l, r, afford ? COL_EDGE : COL_DEAD);
// Залом красится кровью всегда: единственная операция, которая
// может уничтожить вещь, обязана выглядеть иначе остальных.
const Color nameCol = (op == CraftOp::FRACTURE)
? COL_BLOOD
: (afford ? COL_TEXT : COL_FAINT);
Text(l, CraftOpName(op), r.x + 6, r.y + 3, 9, nameCol);
Крафт стал выбором: закалка, отпечаток, направленные операции (M33) Прежние пять операций были СЛЕПЫМИ: перековка бросала всё заново, прививка ставила случайный тег, отсечение снимало последний аффикс, возвышение поднимало случайный. Игрок не принимал ни одного решения - жал кнопку и смотрел, что выпало. Это не крафт, это второй бросок предмета за материалы. Теперь половина операций направленные: игрок показывает пальцем, какой аффикс трогать, и платит за это вдвое - вот это уже решение ("перебросить всё дёшево или снять один дорого"). Два новых глагола: ЗАКАЛКА двигает рулон выбранного аффикса к максимуму его тира на треть остатка. Дёшево, понятно, повторяемо - то самое "доработать почти хорошую вещь". Тир не трогает: за ступень платят возвышением. ОТПЕЧАТОК переносит лучший аффикс с ДРУГОЙ вещи, сжигая её. Именно он превращает хлам из схрона в ресурс, а не в мусор, который лежит. Правило "один тег один раз" им не обходится, бюджет проверяется. В мастерской: клик по вещи кладёт её на верстак, повторный клик по той же назначает донором (помечается кровью - он сгорит, и видеть это надо ДО нажатия). Строки аффиксов стали кнопками: по ним выбирается цель. Проверка меряет НАПРАВЛЕННОСТЬ, а не факт срабатывания: направленность: отсечение по цели, возвышение по цели, закалка двигает рулон, отпечаток перенёс и сжёг донора Отсечение пробуется на СРЕДНЕМ аффиксе из трёх: операция, которая "работает", снимая последний, прошла бы и слепую версию. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 23:37:07 +07:00
Text(l, CraftOpEffect(op), r.x + 6, r.y + 12, 7, COL_FAINT);
Материалы и мастерская: вещь можно доработать (M27) Аффиксы сделали находку решением, но решением одноразовым: выпало как выпало. Материалы дают второй способ получить нужное — не «повезло», а «доработал». Пять материалов, пять операций, экран WORKSHOP. Главное правило экономики — материал добывается ТОЛЬКО В СВОЕЙ ПОЛОСЕ: био-ядра с плотских этажей, крио с мёрзлых. Это единственная причина возвращаться на средние глубины, когда рекорд ушёл вниз; без полос весь контент, кроме последнего акта, умирает в день прохождения. Материалы — валюта, а не предметы: не занимают ячеек, не весят, в схроне не лежат. Но теряются как добыча: зачисляются только при эвакуации. Три уточнения операций, каждое против конкретного обхода: перековка меняет только значения (иначе это новый бросок предмета в обход редкости), прививка не ставит уже стоящий тег (иначе «+урон» шесть раз), возвышается случайный аффикс (иначе все предметы сойдутся к одному). --tool craft поймал, что материалы бесплатны. С твари падало по её danger, и вылазка на восьмидесятом этаже приносила тысячу ядер при цене доводки вещи в шестьсот: 0.6 вылазки на предмет вместо восьми. Правило заменено на «одно ядро с мелочи, два с крупной» — стало 6.2. Сам инструмент при этом тоже считал неверно: брал доход из бюджета опасности этажа, хотя на этаж влезает не больше MAX_LEVEL_SPAWNS тварей. Бюджет уходит не в количество тварей, а в их силу, и доход упирается в потолок расстановки с сорокового этажа. Глубина даёт не количество материалов, а качество предметов — и вместе с ним цену доводки. Залом замерен на тысяче попыток: рассыпалось 11.6% при заявленных 12%, плата снимается всегда, слот теряется всегда. «У меня трижды не рассыпалось» не доказательство ни в какую сторону. Пункт меню WORKSHOP проверен --ui-check — той самой проверкой, которая появилась вчера из-за пункта UPGRADES, молча закрывавшего игру. Формат профиля 5: строка mat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 07:07:46 +07:00
// Цена справа: только те материалы, которые нужны.
char price[64] = {0};
int used = 0;
for (int m = 0; m < MATERIAL_COUNT; ++m)
{
if (cost.amount[m] <= 0) continue;
char part[24];
std::snprintf(part, sizeof(part), "%s%d %s", used ? " " : "", cost.amount[m],
MaterialToken(MaterialKind(m)));
std::snprintf(price + used, sizeof(price) - size_t(used), "%s", part);
used = int(std::strlen(price));
}
if (used > 0)
Text(l, price, r.x + r.w - 6 - TextW(l, price, 7), r.y + 5, 7,
afford ? COL_PICK : COL_DEAD);
}
// --- чем кончилась последняя операция ---
// Молчать нельзя ни в одном из четырёх случаев: «рассыпалось» — это
// потеря вещи, «не хватило» — несостоявшийся клик, а успех без
// ответа выглядит как неработающая кнопка.
if (craftMessage > 0)
{
const CraftResult r = CraftResult(craftMessage - 1);
const char* what = "";
Color col = COL_TEXT;
switch (r)
{
case CraftResult::OK:
what = "DONE";
col = Color{150, 220, 170, 255};
break;
case CraftResult::NO_MATERIALS:
what = "NOT ENOUGH MATERIALS";
col = COL_FAINT;
break;
case CraftResult::NOT_APPLICABLE:
what = "NOTHING TO CHANGE";
col = COL_FAINT;
break;
default:
what = "THE ITEM CRUMBLED TO DUST";
col = COL_BLOOD;
break;
}
std::snprintf(line, sizeof(line), "%s: %s",
CraftOpName(CraftOp(craftOpDone)), what);
Крафт стал выбором: закалка, отпечаток, направленные операции (M33) Прежние пять операций были СЛЕПЫМИ: перековка бросала всё заново, прививка ставила случайный тег, отсечение снимало последний аффикс, возвышение поднимало случайный. Игрок не принимал ни одного решения - жал кнопку и смотрел, что выпало. Это не крафт, это второй бросок предмета за материалы. Теперь половина операций направленные: игрок показывает пальцем, какой аффикс трогать, и платит за это вдвое - вот это уже решение ("перебросить всё дёшево или снять один дорого"). Два новых глагола: ЗАКАЛКА двигает рулон выбранного аффикса к максимуму его тира на треть остатка. Дёшево, понятно, повторяемо - то самое "доработать почти хорошую вещь". Тир не трогает: за ступень платят возвышением. ОТПЕЧАТОК переносит лучший аффикс с ДРУГОЙ вещи, сжигая её. Именно он превращает хлам из схрона в ресурс, а не в мусор, который лежит. Правило "один тег один раз" им не обходится, бюджет проверяется. В мастерской: клик по вещи кладёт её на верстак, повторный клик по той же назначает донором (помечается кровью - он сгорит, и видеть это надо ДО нажатия). Строки аффиксов стали кнопками: по ним выбирается цель. Проверка меряет НАПРАВЛЕННОСТЬ, а не факт срабатывания: направленность: отсечение по цели, возвышение по цели, закалка двигает рулон, отпечаток перенёс и сжёг донора Отсечение пробуется на СРЕДНЕМ аффиксе из трёх: операция, которая "работает", снимая последний, прошла бы и слепую версию. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 23:37:07 +07:00
TextCentered(l, line, 254, 8, col);
Материалы и мастерская: вещь можно доработать (M27) Аффиксы сделали находку решением, но решением одноразовым: выпало как выпало. Материалы дают второй способ получить нужное — не «повезло», а «доработал». Пять материалов, пять операций, экран WORKSHOP. Главное правило экономики — материал добывается ТОЛЬКО В СВОЕЙ ПОЛОСЕ: био-ядра с плотских этажей, крио с мёрзлых. Это единственная причина возвращаться на средние глубины, когда рекорд ушёл вниз; без полос весь контент, кроме последнего акта, умирает в день прохождения. Материалы — валюта, а не предметы: не занимают ячеек, не весят, в схроне не лежат. Но теряются как добыча: зачисляются только при эвакуации. Три уточнения операций, каждое против конкретного обхода: перековка меняет только значения (иначе это новый бросок предмета в обход редкости), прививка не ставит уже стоящий тег (иначе «+урон» шесть раз), возвышается случайный аффикс (иначе все предметы сойдутся к одному). --tool craft поймал, что материалы бесплатны. С твари падало по её danger, и вылазка на восьмидесятом этаже приносила тысячу ядер при цене доводки вещи в шестьсот: 0.6 вылазки на предмет вместо восьми. Правило заменено на «одно ядро с мелочи, два с крупной» — стало 6.2. Сам инструмент при этом тоже считал неверно: брал доход из бюджета опасности этажа, хотя на этаж влезает не больше MAX_LEVEL_SPAWNS тварей. Бюджет уходит не в количество тварей, а в их силу, и доход упирается в потолок расстановки с сорокового этажа. Глубина даёт не количество материалов, а качество предметов — и вместе с ним цену доводки. Залом замерен на тысяче попыток: рассыпалось 11.6% при заявленных 12%, плата снимается всегда, слот теряется всегда. «У меня трижды не рассыпалось» не доказательство ни в какую сторону. Пункт меню WORKSHOP проверен --ui-check — той самой проверкой, которая появилась вчера из-за пункта UPGRADES, молча закрывавшего игру. Формат профиля 5: строка mat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 07:07:46 +07:00
}
Крафт стал выбором: закалка, отпечаток, направленные операции (M33) Прежние пять операций были СЛЕПЫМИ: перековка бросала всё заново, прививка ставила случайный тег, отсечение снимало последний аффикс, возвышение поднимало случайный. Игрок не принимал ни одного решения - жал кнопку и смотрел, что выпало. Это не крафт, это второй бросок предмета за материалы. Теперь половина операций направленные: игрок показывает пальцем, какой аффикс трогать, и платит за это вдвое - вот это уже решение ("перебросить всё дёшево или снять один дорого"). Два новых глагола: ЗАКАЛКА двигает рулон выбранного аффикса к максимуму его тира на треть остатка. Дёшево, понятно, повторяемо - то самое "доработать почти хорошую вещь". Тир не трогает: за ступень платят возвышением. ОТПЕЧАТОК переносит лучший аффикс с ДРУГОЙ вещи, сжигая её. Именно он превращает хлам из схрона в ресурс, а не в мусор, который лежит. Правило "один тег один раз" им не обходится, бюджет проверяется. В мастерской: клик по вещи кладёт её на верстак, повторный клик по той же назначает донором (помечается кровью - он сгорит, и видеть это надо ДО нажатия). Строки аффиксов стали кнопками: по ним выбирается цель. Проверка меряет НАПРАВЛЕННОСТЬ, а не факт срабатывания: направленность: отсечение по цели, возвышение по цели, закалка двигает рулон, отпечаток перенёс и сжёг донора Отсечение пробуется на СРЕДНЕМ аффиксе из трёх: операция, которая "работает", снимая последний, прошла бы и слепую версию. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 23:37:07 +07:00
TextCentered(l, "CLICK ITEM THEN OPERATION Q/E SCROLL ESC BACK", 264, 7,
Материалы и мастерская: вещь можно доработать (M27) Аффиксы сделали находку решением, но решением одноразовым: выпало как выпало. Материалы дают второй способ получить нужное — не «повезло», а «доработал». Пять материалов, пять операций, экран WORKSHOP. Главное правило экономики — материал добывается ТОЛЬКО В СВОЕЙ ПОЛОСЕ: био-ядра с плотских этажей, крио с мёрзлых. Это единственная причина возвращаться на средние глубины, когда рекорд ушёл вниз; без полос весь контент, кроме последнего акта, умирает в день прохождения. Материалы — валюта, а не предметы: не занимают ячеек, не весят, в схроне не лежат. Но теряются как добыча: зачисляются только при эвакуации. Три уточнения операций, каждое против конкретного обхода: перековка меняет только значения (иначе это новый бросок предмета в обход редкости), прививка не ставит уже стоящий тег (иначе «+урон» шесть раз), возвышается случайный аффикс (иначе все предметы сойдутся к одному). --tool craft поймал, что материалы бесплатны. С твари падало по её danger, и вылазка на восьмидесятом этаже приносила тысячу ядер при цене доводки вещи в шестьсот: 0.6 вылазки на предмет вместо восьми. Правило заменено на «одно ядро с мелочи, два с крупной» — стало 6.2. Сам инструмент при этом тоже считал неверно: брал доход из бюджета опасности этажа, хотя на этаж влезает не больше MAX_LEVEL_SPAWNS тварей. Бюджет уходит не в количество тварей, а в их силу, и доход упирается в потолок расстановки с сорокового этажа. Глубина даёт не количество материалов, а качество предметов — и вместе с ним цену доводки. Залом замерен на тысяче попыток: рассыпалось 11.6% при заявленных 12%, плата снимается всегда, слот теряется всегда. «У меня трижды не рассыпалось» не доказательство ни в какую сторону. Пункт меню WORKSHOP проверен --ui-check — той самой проверкой, которая появилась вчера из-за пункта UPGRADES, молча закрывавшего игру. Формат профиля 5: строка mat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 07:07:46 +07:00
COL_FAINT);
break;
}
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
case Screen::DEPTH:
{
Shade(l, COL_DIM);
TextCentered(l, "DESCEND FROM", 46, 16, COL_TEXT);
Rule(l, 70, 62);
char line[80];
const int best = game.profile ? game.profile->maxDepth : 1;
std::snprintf(line, sizeof(line), "DEEPEST REACHED D%d", best);
TextCentered(l, line, 76, 8, COL_FAINT);
for (int i = 0; i < w.count; ++i) DrawButton(l, *this, w.item[i], i == depthIndex);
// Главное правило экстракшена, и его надо повторять: глубже —
// богаче, но потерянное не возвращается.
TextCentered(l, "DEEPER MEANS RICHER AND FINAL", 226, 8, COL_FAINT);
TextCentered(l, "MOUSE OR W/S SELECT ESC BACK", 240, 8, COL_FAINT);
break;
}
case Screen::SUMMARY:
{
const RunReport& r = game.report;
const bool won = r.outcome == RunOutcome::EXTRACTED;
Shade(l, won ? Color{4, 10, 8, 232} : Color{12, 2, 2, 232});
TextCentered(l, won ? "EXTRACTED" : "SQUAD LOST", 60, 22,
won ? COL_TEXT : COL_BLOOD);
Rule(l, 90, 70);
char line[80];
std::snprintf(line, sizeof(line), "REACHED D%d", r.deepest);
TextCentered(l, line, 104, 12, COL_PICK);
Отряд растёт с одного оперативника до пяти (M24) Игра начинается ОДНИМ человеком: тактический нож, слабый фонарь, куртка. Остальные четверо нанимаются за опыт по кривой 600/2400/9600/38400 — самая дорогая покупка в игре и намеренно. Одиночка с ножом это первое впечатление, и продать второго бойца за одну вылазку значило бы выбросить весь хоррор начала: вдвоём уже не страшно. NUM_AGENTS осталась величиной времени сборки и означает теперь ВМЕСТИМОСТЬ, а не размер отряда: делать её динамической это сотня мест ради числа, которое всё равно упирается в пять. Занятость слота это Agent::present, и он отличается от alive. Правила боя читают только alive — для них пустой слот и павший одно, и это верно. Разница нужна там, где показывают человека: у павшего есть тело на полу и перечёркнутая карточка, у пустого слота нет ни того, ни другого. Проверка в --descend меряет цепочку «в профиле -> на этаже» и потолок, а главное — переживает ли одиночка первую схватку: отряд: в профиле 1 -> на этаже 1, после найма 2, цена 600, сверх потолка отказ одиночка с ножом на первом этаже: выжил, 100 -> 41, подошло 2, убил 2 Первая версия этой проверки не измеряла ничего: боец стоял минуту, к нему не подошёл никто, и «выжил» было пустым словом. Теперь отряд ставится рядом с тварью. Попутно найдено и исправлено: Profile::Save три вехи не писала ни версию формата, ни улучшения. Купленный запас патронов действовал до конца запуска и молча пропадал при чтении, а профиль без версии читался как формат 1. Круговой прогон этого не видел, потому что сверял три поля из десяти и делал это на пустом профиле, где всё сходится само. Теперь он сверяет поля списком и на набитом профиле. Формат профиля 3: добавлена строка squad. Старый профиль играл впятером и получает пятерых при миграции — отобрать их значило бы съесть отряд, которого игрок не терял. --accept PASS без изменений: без профиля отряд по-прежнему впятером. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 02:29:19 +07:00
// Из скольких ушли, а не из скольких бывает: «2 / 5» у игрока с
// отрядом из двух читалось бы как потеря троих.
std::snprintf(line, sizeof(line), "SURVIVORS %d / %d", r.survivors,
std::max(r.survivors, game.squad.roster));
Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20) Десять вех одним состоянием: разделять их пофайлово нечем, потому что src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений, все проверки проходят. ДВИЖОК (engine/, цель tile2d) Каталог свой, и решение «это движок» принимается тем, куда положен файл. Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра и карты уехали в engine/config.h, потолок населения этажа стал своим (MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала центром карты вместо позиции отряда. engine/projection.* единственное место, где движок знает про изометрию; видов сразу два — абстракция с одной реализацией ничем не проверена и обычно оказывается неправильной engine/project.* манифест проекта: пути, вид, кривая глубины engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла engine/tilemap_draw движок научился показывать свою карту РЕДАКТОР (editor/, цель tile2d_editor) Открывает любой проект и ни одной игры по имени не знает. --tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка этажа, ASCII-карта, проверка набора. Правку делают файлами, последствия читают командой (docs/19-agent.md) терминал снизу: те же инструменты, git, сборка и запросы к claude/codex, открытый проект уходит потомкам через TILE2D_PROJECT ПРИМЕР (samples/walk, цель tile2d_walk) Второй потребитель движка. Игра и редактор самостоятельность движка проверить не могут: редактор мира не рисует, а игра — то место, куда общий код и уезжает незаметно. ИГРА (src/) Экстракшен: вес и два порога переноски, конечный боезапас, расходники применяются сами, общая сумка отряда, трата опыта на улучшения. Каждое из правил проверяется прогоном, а не глазами, и проверки поймали: вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче); боец с клинком не тратил найденные патроны при запасном стволе в сумке; массив виджетов экрана переполнился и молча съел хвост схрона; проверка улучшений сама ломала забег, вызывая NewRun посреди спуска. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 01:07:19 +07:00
TextCentered(l, line, 126, 9, COL_FAINT);
// Две цифры, ради которых экран и существует: что осталось у тебя
// и что осталось внизу.
std::snprintf(line, sizeof(line), "BROUGHT BACK %d", r.itemsBrought);
TextCentered(l, line, 140, 9, won ? COL_TEXT : COL_DEAD);
std::snprintf(line, sizeof(line), "LOST %d", r.itemsLost);
TextCentered(l, line, 152, 9, COL_BLOOD);
std::snprintf(line, sizeof(line), "XP %d", int(r.xp));
TextCentered(l, line, 166, 9, COL_FAINT);
if (r.newRecord)
{
std::snprintf(line, sizeof(line), "NEW CHECKPOINT D%d", r.checkpoint);
TextCentered(l, line, 184, 10, pulse ? COL_PICK : COL_TEXT);
}
for (int i = 0; i < w.count; ++i) DrawButton(l, *this, w.item[i], pulse);
break;
}
case Screen::SQUAD:
DrawSquadScreen(l, game, *this, w);
break;
}
// Курсор и то, что на нём висит, — поверх всего экрана.
if (!mouseIn) return;
// Подсказка — до курсора, чтобы курсор остался сверху, и НЕ во время
// перетаскивания: там под курсором уже висит вещь, и две подписи разом
// читаются хуже, чем одна.
if (screen == Screen::SQUAD && !dragging && hoverPart != UiPart::NONE)
{
const Widget* hov = w.Find(hoverPart, hoverIndex);
if (hov)
{
if (hoverPart == UiPart::STAT)
DrawTip(l, StatTip(hoverIndex, game.squad.agents[agentIndex]), int(mx), int(my));
else if (hov->item != ItemId::NONE)
DrawTip(l, ItemTip(hov->item), int(mx), int(my));
}
}
DrawDragged(l, *this);
DrawPointer(l, *this);
}