squad-proto/engine/progression.h
z.kirill 7d6266aaf3 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-13 21:07:19 +03:00

107 lines
6.5 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#pragma once
// -----------------------------------------------------------------------------
// Что означает глубина N.
//
// Игра — это бесконечный спуск: чем глубже, тем страшнее окружение, злее твари,
// богаче находки. Весь этот наклон живёт ЗДЕСЬ, в одной функции, и больше
// нигде. Причина простая: кривая сложности — это то, что правят чаще всего и
// правят на ощупь. Размазанная по генератору, каталогу и рендеру, она через
// месяц перестаёт поддаваться правке вообще, потому что «сделать десятый этаж
// чуть злее» превращается в раскопки по трём файлам.
//
// Чего здесь НЕТ намеренно:
// * множителей урона и здоровья тварей. «Тот же бредущий, но со втрое
// большим HP» — это не страшнее, это дольше. Глубина меняет, КТО выходит
// и сколько их, а не сколько раз надо выстрелить в одного и того же;
// * потолка. Функция определена для любого N: спуск бесконечен, и этаж 200
// обязан собраться, даже если каталог кончился на 12-м.
//
// Всё, что отсюда выходит, — данные. Ни рендер, ни симуляция про «глубину» не
// знают, они читают готовые числа.
// -----------------------------------------------------------------------------
#include <cstdint>
#include "engine/theme.h"
struct FloorSpec
{
int depth = 1;
// Отделка этажа. Комнаты со своей темой её сохраняют — тема этажа
// достаётся коридорам и тем комнатам, которые от неё не отказались.
uint8_t theme = 0;
// Сколько «опасности» разрешено выпустить на этаж (цены — в каталоге).
// Главный рычаг сложности: он растёт всегда, а состав того, на что его
// тратят, меняется вместе с ним.
float dangerBudget = 4.0f;
// Множитель шанса срабатывания точек спавна. Бюджет отвечает за то,
// НАСКОЛЬКО злой этаж, этот множитель — за то, насколько он плотный.
float spawnChanceScale = 1.0f;
// Лут: сколько предметов лежит на этаже и какого качества.
int lootDrops = 2;
int lootTier = 0;
// Насколько сжат обзор. Render-only: свет и туман войны на симуляцию не
// влияют (docs/02-architecture.md), поэтому темнеть с глубиной безопасно.
// Именно это и делает окружение страшнее — не новые спрайты, а то, что
// видно всё меньше.
float sightScale = 1.0f;
// Опыт за этаж — множитель к цене убитого. Растёт медленнее опасности:
// спускаться должно быть выгодно, но не настолько, чтобы пропуск этажей
// стал единственной разумной игрой.
float xpScale = 1.0f;
};
// -----------------------------------------------------------------------------
// Коэффициенты кривой. Раньше они стояли числами прямо в DescribeFloor, то есть
// «чуть злее с десятого этажа» требовало пересборки движка. Теперь это данные
// проекта (project.txt), и ФОРМА кривой при этом остаётся в коде: проект гнёт
// её, но не может подменить, скажем, линейный рост экспонентой.
//
// Так и задумано. Проект, которому нужна другая форма, — это повод завести ещё
// одно поле здесь, а не язык выражений в текстовом файле: кривую правят на
// ощупь и часто, и она обязана оставаться читаемой целиком за один взгляд.
// -----------------------------------------------------------------------------
struct FloorCurve
{
// Опасность: base + linear*(d-1) + quad*(d-1)^2.
float dangerBase = 3.0f;
float dangerLinear = 2.2f;
float dangerQuad = 0.10f;
// Плотность точек: 1 + perFloor*(d-1), но не выше cap.
float densityPerFloor = 0.05f;
float densityCap = 1.5f;
// Лут: base + d/floorsPerDrop предметов, тир — ступень на floorsPerTier.
int lootBase = 2;
int floorsPerDrop = 4;
int floorsPerTier = 3;
// Обзор: 1 - perFloor*(d-1), но не ниже floorAt.
float sightPerFloor = 0.035f;
float sightFloorAt = 0.60f;
// Опыт: 1 + perFloor*(d-1).
float xpPerFloor = 0.15f;
// С какого этажа начинается каждая тема отделки. Последняя полоса открытая:
// дальше тема не меняется, потому что менять её больше не на что.
int themeFrom[THEME_COUNT] = {1, 4, 8, 13};
};
// Активная кривая. Ставится один раз при загрузке проекта, как и g_proj.
inline FloorCurve g_curve;
// Кривая. Определена для любого depth >= 1.
FloorSpec DescribeFloor(int depth);
// Подпись этажа для HUD: "D3 RUST". Здесь, а не в HUD, потому что имя темы
// этажа — часть кривой, и знать, какая тема на каком этаже, HUD не обязан.
// Пишет в buf не больше cap символов, включая ноль.
void FloorLabel(const FloorSpec& spec, char* buf, int cap);