squad-proto/engine/progression.h
z.kirill ef103050b1 Правки по игре: начало, ставка, наёмники (M29)
Семь изменений по итогам игры вживую.

НАЧАЛО. Первый этаж встречал одинокого оперативника с ножом тремя тварями,
из которых одна быстрая: бюджет 3.0 и бегуны с первого этажа. Стало 2.2
(ровно два бредущих), бегуны с четвёртого. Света прибавлено: обзор
начинается с 1.30 вместо 1.0, у этажа появилась своя яркость пройденного
(0.42 наверху против 0.13 на дне). Наверху ещё работает освещение, и
хоррор первого этажа в том, что игрок один, а не в том, что не видно
стен. Темнота это инструмент глубины, и тратить её на первый этаж значит
не иметь запаса дальше.

СТАВКА. Опыт платится за убийства и сразу: выносить надо ВЕЩИ. Вайп
больше не отнимает всё - доезжает около половины добычи, и чем вещь реже,
тем охотнее теряется. Прежнее "вайп не возвращает ничего" делало
единственным разумным поведением уход наверх при первой царапине.

ВЫХОД. Аномалия возврата переехала из точки входа к спуску: развилка
"вниз или наверх" теперь там же, где этаж кончился. Обратный путь через
растревоженный этаж ничего не решал - это был ещё раз тот же этаж.

ЧЕКПОИНТЫ через 50 этажей вместо пяти: забег это долгий спуск, а не
перебежка между сохранениями.

НАЁМНИКИ. Кнопка "нанять за опыт" убрана, оперативники находятся в
капсулах на этажах: в среднем раз в сотню этажей и не чаще раза в
двадцать. У каждого свои три прибавки из одного бюджета - разница в
характере, а не в ранге. Покупка была скучной ровно в том смысле, в каком
скучна любая кнопка "стать сильнее": ничего не рассказывает и никуда не
зовёт.

ЗАМЕРЫ. Вайп на сотне повторов: доезжает 52% (обычное 69%, реликты 25%).
Первая версия проверки считала одну сумку и выдала "обычного доехало
112%" - спасение забирает ещё и надетое. Опыт: банк 558 -> 578 за
убийство без эвакуации. Капсула поднимает отряд до двух и даёт наёмнику
его числа.

Формат профиля 6: строки merc и rescued.

Не сделано и обсуждается: ростер сверх пятерых, задания для свободных,
пассивные бонусы от сидящих на базе.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 04:09:26 +03:00

128 lines
8.1 KiB
C++
Raw Permalink 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 ambient = 0.42f;
// Опыт за этаж — множитель к цене убитого. Растёт медленнее опасности:
// спускаться должно быть выгодно, но не настолько, чтобы пропуск этажей
// стал единственной разумной игрой.
float xpScale = 1.0f;
};
// -----------------------------------------------------------------------------
// Коэффициенты кривой. Раньше они стояли числами прямо в DescribeFloor, то есть
// «чуть злее с десятого этажа» требовало пересборки движка. Теперь это данные
// проекта (project.txt), и ФОРМА кривой при этом остаётся в коде: проект гнёт
// её, но не может подменить, скажем, линейный рост экспонентой.
//
// Так и задумано. Проект, которому нужна другая форма, — это повод завести ещё
// одно поле здесь, а не язык выражений в текстовом файле: кривую правят на
// ощупь и часто, и она обязана оставаться читаемой целиком за один взгляд.
// -----------------------------------------------------------------------------
struct FloorCurve
{
// Опасность: base + linear*(d-1) + quad*(d-1)^2.
//
// База 1.6, а не 3.0: первый этаж — это «полупустая лаборатория, две вялых
// твари» (docs/20-gdd.md §1), а не три штуки на одинокого оперативника с
// ножом. Три и были: бюджета 3.0 хватало ровно на трёх бредущих, и стартовый
// забег превращался в отбивание от толпы.
float dangerBase = 2.2f;
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;
// Обзор: sightStart - perFloor*(d-1), но не ниже floorAt.
//
// Начинается ВЫШЕ единицы: наверху ещё работает освещение, и хоррор там не
// из темноты, а из того, что игрок один. Темнота — инструмент глубины, и
// тратить её на первый этаж значит не иметь запаса дальше.
float sightStart = 1.30f;
float sightPerFloor = 0.035f;
float sightFloorAt = 0.60f;
// Освещение самого этажа: насколько ярко видно УЖЕ ПРОЙДЕННОЕ. Наверху свет
// ещё горит (0.42), внизу остаётся только память о том, где ты был (0.13).
// Это и есть «атмосфера становится страшнее» — не спрайтами, а тем, что
// сзади темнеет.
float ambientStart = 0.42f;
float ambientPerFloor = 0.022f;
float ambientFloorAt = 0.13f;
// Опыт: 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);