squad-proto/docs/17-extraction.md
z.kirill 4382af2322 Документация: границы движка, работа через модель, дизайн игры до лейтгейма
18-engine.md    границы Tile2D, проекции 2D, формат проекта, второй проект
  19-agent.md     инструменты --tool, скиллы, терминал в редакторе
  20-gdd.md       акты, петли, рост отряда, закон масштаба, бюджет 200 часов
  21-damage.md    семь стихий, статусы, десять реакций, формулы, триггеры
  22-loot.md      редкости, аффиксы, артефакты, зачарования, материалы
  23-endgame.md   Бездна, мутации, лидерборды, 262 ачивки

Плюс правки 01/02/07/15/16/17 под переехавшие пути и новые правила забега.

Дизайнерская часть держится на одном законе: угроза растёт квадратично, сила
отряда — линейно с затуханием, разрыв закрывается ЗНАНИЕМ, а не числами. Из
него выведены и экономика опыта (41 млн против 44 млн дохода), и то, почему
реакции считаются от глубины, а не от урона оружия, и почему в Бездне
множитель HP разрешён, а во всём авторском контенте запрещён.

.claude/commands: скиллы /tile2d, /room, /floor — правила этого движка,
которые нельзя вывести из кода. Главное записано первым: не отчитываться об
успехе без прохода --tool validate.

Числа в этих документах — вход для инструментов, а не украшение. Расхождение
между таблицей и выводом инструмента означает, что неправ документ.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 21:07:45 +03:00

261 lines
18 KiB
Markdown
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.

# 17 — Экстракшен: спуск как ставка
## Что изменилось в игре
До этой вехи глубина была прогрессом: чем дальше, тем лучше. Теперь она
**ставка**. Отряд уходит вниз с тем, что нажил, и каждый этаж — заново заданный
вопрос: ещё ниже или назад, пока есть что выносить.
Три правила, из которых следует всё остальное:
1. **Унести можно только то, что на выживших.** Павший забирает своё с собой.
2. **Полный вайп не возвращает ничего.** Снаряжение сбрасывается к стартовому.
3. **Достигнутая глубина остаётся навсегда.** Она открывает чекпоинты — и это
единственное, чего смерть не отнимает.
Третье правило — ответ на «а если этажей десять тысяч». Проходить их заново
каждую вылазку не нужно; терять при этом можно только снаряжение.
## Два выхода с этажа
| | Где | Клавиша | Что делает |
|---|---|---|---|
| **спуск** | самая дальняя достижимая точка | `F` | следующий этаж, отряд как есть |
| **эвакуация** | там, где отряд вошёл на этаж | `E` | забег окончен, добыча в схроне |
Эвакуация совпадает с точкой входа не из экономии: это лестница наверх, и в этом
вся суть напряжения. Спуск лежит на дальнем конце этажа — решив уйти, отряд идёт
обратно через всё, что успел растревожить, раненый и с полными руками.
Кольца на полу разного цвета: зелёное — вниз, голубое — наверх. Оба видны даже
в полной темноте. Спутать их означает уйти не туда, и цена ошибки здесь — весь
забег, поэтому подсказки в HUD подкрашены теми же цветами.
Ни то ни другое не срабатывает от захода на клетку. Спуск и эвакуация — решения
игрока: этаж не зачищен, лут не собран, уходить рано — это нормальный выбор, и
отряд может стоять на выходе сколько угодно.
## Профиль
[src/sim/profile.h](../src/sim/profile.h) — всё, что переживает забег:
```
maxdepth 13
runs 6
extractions 3
agent 0 cleaver plate charm bandage stimpack - - - -
stash saber plate vest smg bow charm lantern spurs
```
- **снаряжение пяти бойцов** лежит здесь, а не собирается заново при старте.
То, в чём отряд ушёл вниз, — это то, в чём он вернулся с прошлой вылазки;
- **схрон** — общий запас сверх надетого, потолок `STASH_CAP`. Без потолка
экстракшен превращается в склад, и решение «что взять вниз» исчезает;
- **максимальная глубина** открывает чекпоинты.
Профиль — состояние ИГРОКА, а не мира: он не в `ecs::World` и не сбрасывается ни
`Reset`, ни `Descend`. Записывается он только в конце забега — авария посреди
вылазки означает потерю, и это часть контракта, а не недоделка.
Формат текстовый по той же причине, что у комнат: файл лежит рядом с игрой, его
правят руками при отладке, и он обязан читаться глазами.
## Чекпоинты
Каждые `CHECKPOINT_STEP` (5) этажей. Достигнутая глубина открывает все чекпоинты
не глубже неё: дошёл до 13-го — доступны D1, D6, D11.
![выбор глубины](ui-depth.png)
Рекорд глубины засчитывается **любым исходом**. Дошёл — значит дошёл; требовать
за чекпоинт ещё и удачную эвакуацию значило бы наказывать дважды за одну смерть.
Пять — это «достаточно редко, чтобы дойти до следующего было событием, и
достаточно часто, чтобы потеря забега не откатывала в самое начало».
## Сумка одна на отряд
Раньше каждый боец таскал свои шесть ячеек, и это порождало проблему, которую
нечем было решить: пачка патронов, поднятая бойцом с клинком, доставалась только
ему и лежала мёртвым грузом до конца забега. Перекладывать между бойцами игра не
умела.
Теперь сумка одна — шестнадцать ячеек на пятерых. Личным остаётся **надетое**:
руки, броня, карман. Их не разделить, они и есть «кто этот боец».
**Вес делится на живых.** Потерять бойца — значит взвалить его долю на
остальных: обратно наверх отряд пойдёт медленнее, чем шёл сюда. Это не побочный
эффект, а то, ради чего сумка общая.
Правило «унести можно только то, что на выживших» этим не ослаблено, оно стало
точнее: надетое павшего теряется по-прежнему, а общая ноша на нём не висела —
её тащил отряд. Вайп по-прежнему не выносит ничего: тогда выживших нет вовсе, и
сумку некому донести.
## Схрон и сборы
![экран отряда со схроном](ui-squad-stash.png)
Экран отряда (`I`) стал местом сборов: снаряжение бойца слева, общая сумка
справа, схрон двумя рядами внизу. Предметы перетаскиваются мышью, `Q`/`E`
листают схрон, правая кнопка по вещи выбрасывает её под ноги.
**Наведи — и увидишь, что это.** Подсказка под курсором показывает вес и все
эффекты предмета, а на строках характеристик объясняет, откуда берётся число.
До неё вещь была именем и цветным квадратиком: чем VEST отличается от JACKET,
знал только каталог в коде. С сумкой на шестнадцать мелких ячеек это перестало
быть терпимым — в ячейку не влезает даже имя.
Схрон полон — обмен не состоится целиком, и вещь останется в слоте. Молча терять
её нельзя: «некуда положить» обязано быть видно.
## Вес: что унести
Шесть ячеек в сумке отвечали на вопрос «сколько вещей», но не на вопрос «чего
они стоят»: шесть бинтов и шесть плит обходились одинаково. Теперь у каждой
вещи есть вес, а у бойца — два порога.
| | |
|---|---|
| до `CARRY_LIMIT` (7.5) | отряд идёт как шёл |
| дальше | тем медленнее, чем больше нагрёб, до 55% на пределе |
| выше `CARRY_MAX` (14) | не поднимет: вещь остаётся лежать |
Порогов ДВА намеренно. Один жёсткий предел означал бы «сумка кончилась» — то
же самое, что счёт ячеек, только считать труднее. Настоящий выбор возникает
там, где лишнее взять МОЖНО, но за это платят: а идти с этим — обратно через
весь этаж, который уже растревожен.
**Надетое не считается.** За броню уже заплачено множителем скорости (плита —
0.82), и считать её вес вторично значило бы наказывать за один выбор дважды.
Хуже того: плита съедала бы почти весь запас, и боец в тяжёлой броне не мог бы
поднять вообще ничего — вместо решения вышел бы запрет. А вот плита, НАЙДЕННАЯ
и лежащая в сумке, весит полной мерой: тащить её наверх — это и есть выбор.
Выбросить лишнее можно правой кнопкой по ячейке на экране отряда: вещь ложится
под ноги и подбирается обратно. Из рук бросать нельзя — безоружный боец не
бежит быстрее, он просто перестаёт быть бойцом.
## То, что кончается
Наверх гонит не таймер. Часы заставляли бы торопиться механически, а это
хоррор: давить должно **исчерпание**, а задача — уйти как можно глубже.
**Патроны конечны.** Запас (`startAmmo`, 120 на бойца) выдаётся на вылазку и
едет вниз вместе с отрядом. Перезарядка тянет из него; кончился — стрелок
молчит, и перезарядка даже не начинается: боец с пустым стволом не изображает
работу. Восполнить можно только находкой — пачкой на 40 патронов.
**Расходники применяются сами.** Ручных действий в игре нет (спек 5), поэтому
бинт и пачка — не кнопка, а поведение: боец перевязывается, когда ранен ниже
`healAtFrac` и рядом никого нет, и набивает подсумок, когда стрелять нечем.
Лечиться в схватке нельзя — перевязка посреди боя обесценила бы урон, который
тварь только что нанесла.
До этой вехи `heal` у бинта был мёртвым числом: расходники не применялись
нигде, и бинт был просто грузом. С весом (выше) он стал грузом буквально.
Патроны нужны тому, у кого **вообще** есть ствол, а не только тому, кто держит
его сейчас: иначе боец с клинком в руках и запасным карабином в сумке таскал бы
найденную пачку до конца забега. Это нашла проверка, а не глаз.
## Во что вкладывается опыт
![экран улучшений](ui-upgrades.png)
Опыт копился с вехи M12 и до сих пор был честной заглушкой: число, которое
ни на что не влияет. Теперь он тратится — на четыре улучшения отряда.
| | что даёт | почему это |
|---|---|---|
| AMMO RESERVE | +20 патронов на вылазку | |
| CARRY LIMIT | +1.5 до перегруза | |
| SIGHT RANGE | +6% обзора | |
| VIGOR | +12 здоровья | |
Все четыре — **ресурсные**, и ни одного силового. Игра про исчерпание, и
вкладываться в ней надо в то, что кончается: отряд, который бьёт вдвое сильнее,
перестаёт бояться, а страх тут единственная механика, которую нельзя
восстановить балансом.
Улучшения ОТРЯДНЫЕ, а не на бойца. Не из экономии: личным в этой игре остаётся
снаряжение, и второе личное измерение превратило бы сборы в бухгалтерию на
пятерых.
Опыт зачисляется только за **успешную эвакуацию** (`FinishRun`): вайп не
приносит ничего. Это та же ставка, что и с добычей.
Приёмки это не касается вообще: улучшения живут в профиле, а `--accept` его не
подключает — там нет ни снаряжения, ни улучшений, и критерии меряются на том
же, на чём мерялись всегда.
## Итог забега
![итог вылазки](ui-summary.png)
Две цифры, ради которых экран и существует: **сколько вынес** и **сколько
осталось внизу**. Плюс глубина, выжившие и открытый чекпоинт.
Забег закрывается ДО показа экрана — в момент вайпа или нажатия `E`. Симуляция
при этом ничего не решает: она только отмечает `atExit`/`atExtract`, а конец
забега объявляет UI. Смена мира посреди шага, между движением и стрельбой,
оставила бы половину систем со ссылками на снесённые сущности.
## Как играть
```bash
squad_proto.exe --rooms
```
`NEW RAID` → выбрать чекпоинт → вылазка. `I` перед выходом — разобрать схрон.
| Ключ | Что делает |
|---|---|
| `--profile <файл>` | другой профиль (полезно для проверок) |
| `--no-profile` | не читать и не писать профиль вообще |
Без `--rooms` игра работает как раньше: одна карта, никакого забега. Приёмка и
`--headless` профиль не подключают — критерии меряются на прежнем снаряжении.
## Проверки
```bash
squad_proto.exe --descend 7 --no-profile
```
Прогон проверяет всю цепочку: спуск по этажам, перенос сумок, эвакуацию,
**круговой прогон профиля через запись** — и правило переноски.
Переноска проверяется на ВЫДУМАННОМ инвентаре, а не по грузу в прогоне: в сумки
кладутся бинты, чтобы проверить перенос между этажами, и по ним вес выглядит
втрое меньше настоящего. Первые две попытки выставить пороги делались как раз
по этой цифре — и обе дали числа, которые не ограничивали ничего.
```
расходники: патроны набиты, лечение сработало (33 -> 58), из сумки ушли обе
перезарядка: запас 40 -> 16 (магазин 24)
переноска: налегке 6.9 (скорость 100%), с грузом 12.9 (скорость 63%), плит влезло 1
```
Расход патронов проверяется тут же и отдельно: спуск ни с кем не воюет, столбец
«патронов» в таблице стоит на месте — и без прямой проверки бесконечный
боезапас выглядел бы ровно так же.
```
7 RUST 13 19 3 149 живых 5, в сумках 30
эвакуация: вынесено 40, потеряно 0, выживших 5, чекпоинт D6
профиль: запись и чтение сходятся
```
Круговой прогон здесь не для полноты: запись профиля — единственный путь,
которым игра может стереть всё нажитое, и ошибка в нём выглядит как «схрон
куда-то делся», а не как падение.
## Чего ещё нет
- **Схватки за добычу.** В настоящем экстракшене выход можно не отдать —
здесь на этаже нет никого, кто охотится именно за вынесенным.
- **Страховки и торговли.** Схрон только копится; девать его некуда.
- **Траты опыта** — она по-прежнему ждёт своей вехи
([16-descent.md](16-descent.md)).