Files
MVP1/Docs/Modules/Army.md
T
AlexanderandClaude Opus 5.5 e8b17a7efa Скорости маршей по типам войск и слоты похода (регламент 31.10)
Мир (Bogatyri.World.Simulation):
- MarchSpeedRules: скорость по типам — пехота 8, лучники 9, кавалерия 12, осадные 4 клетки
  за реальный час (ТЕСТ); марш идёт в реальном времени
- смешанное войско — по самому медленному типу (настройка mixedUsesSlowest: средняя по числу воинов);
  правило для одной армии в регламенте не задано — вопрос команде
- WorldSimulation.TravelTimeMs, AssignUnits: время прихода пересчитывается по составу похода;
  обратный путь — по составу выживших
- MarchRules.GetTravelTimeMs — для похода без состава (как пехота)

Армия (Bogatyri.Army):
- слоты похода: IArmy.MarchSlots (база 2, ТЕСТ), ActiveMarches; TakeForMarch отказывает при занятых слотах;
  походы из старых сохранений сверх лимита дойдут, новых не будет

Таблица MarchConfig (Assets/Resources/MarchConfig.asset): скорости, режим смешанного войска, база слотов.
Карта мира: путь в окне атаки — по составу при выбранном числе воинов, «Все слоты похода заняты»,
верхняя строка «отрядов: 1 из 2».

Тесты: скорости по типам, смешанное войско, обратный путь, слоты; старые тесты мира переведены
на часовые марши. Все 49 тестов ModuleTests проходят вне Unity. Код Валдемара не менялся.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 16:05:55 +01:00

106 lines
12 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.
# Армия
Войска и их характеристики: сколько воинов каждого типа в городе, сколько из них в гарнизоне, сколько в походах, обучение, потери, уход в поход и возвращение. **Единственное место, где хранятся войска.**
- **Сборка:** `Bogatyri.Army` (без Unity).
- **Папка:** `Assets/Scripts/Modules/Army`.
- **Зависит от:** ядра.
## Контракт `IArmy`
| Член | Что делает |
| --- | --- |
| `TypeCount` | Типов войск — 4 (пехота, лучники, кавалерия, осадные; порядок как в `GameData.Troops`). |
| `GetTroops(type)` | Воинов этого типа в городе. |
| `SoldiersAtHome` | Всего воинов в городе (вместе с гарнизоном). |
| `TroopsInMarches` | Воинов в походах. |
| `GarrisonTotal`, `GetGarrison(type)` | Сколько воинов в гарнизоне — всего и по типам. |
| `MarchSlots`, `ActiveMarches` | Слоты похода (регламент 31.10): сколько походов может идти одновременно (база 2 из `MarchConfig`, «Военная логистика» — до 7, позже) и сколько идёт сейчас. `TakeForMarch` отказывает, если все слоты заняты. Походы из старых сохранений сверх лимита дойдут, новых не будет. |
| `AvailableForMarch` | Свободные воины: в городе минус гарнизон. Только их можно отправить в поход. |
| `SetGarrison(count)` | Назначить гарнизон: набирается по составу армии в городе (30 пехоты и 10 лучников, гарнизон 8 → 6 пехоты и 2 лучника). Не больше, чем воинов в городе; возвращает, сколько назначено. |
| `AddTroops(type, count)` | Добавить воинов (обучение в Казармах). |
| `LoseFraction(fraction)` | Потери в бою: от каждого типа погибает доля (с округлением вниз); возвращает, сколько погибло. |
| `TakeForMarch(marchId, count)` | Забрать свободных воинов (без гарнизона) в поход пропорционально составу и запомнить состав. `false` — ничего не взято: не хватает воинов или поход с таким номером уже есть. |
| `ReturnFromMarch(marchId, survivors)` | Вернуть выживших по тем же типам (пропорционально). `false` — поход неизвестен, никто не добавлен. |
| `GetMarchUnits(marchId)` | Состав похода по типам — для боя с типами войск. |
| `ReturnUnits(marchId, survivorUnits)` | Вернуть выживших точно по типам, как посчитал бой. Больше, чем ушло, не вернётся. `false` — поход неизвестен. |
Получить: `GameServices.Get<IArmy>()`. Регистрирует `App/GameModules`. `GameProgress` (обучение, кампания, арена, сила армии) и панели работают с войсками только через этот контракт.
## Правило «один солдат в одном месте»
Воин либо в городе, либо в походе. Поход забирает воинов из города и запоминает состав, возвращение раздаёт выживших по тем же типам. Тест `ArmyService_MarchNeverCreatesOrLosesSoldiers` проверяет, что «дома + в походах» всегда равно общему числу.
**Возвращение — только по известному походу и только один раз** (ревью 27.10.2026, п. 2.1):
- неизвестный поход (повторное событие, испорченное сохранение) не добавляет никого — раньше такие выжившие зачислялись пехотой;
- после возвращения запись похода удаляется, второе возвращение того же похода возвращает `false`;
- вернуться может не больше, чем ушло;
- два похода с одним номером невозможны: `TakeForMarch` отклоняет повтор, загрузка и перенос старых данных пропускают дубликаты;
- перенос старых сохранений — отдельно, в `ImportLegacy`.
**Гарнизон** — часть воинов города. Они по-прежнему в городе (`SoldiersAtHome`), но в поход не уходят:
- 30 воинов, 10 в гарнизоне → 20 свободны, всего по-прежнему 30 (тест `ArmyService_Garrison_OneSoldierInOnePlace`);
- гарнизон по каждому типу никогда не больше воинов этого типа: после потерь (`LoseFraction`) он обрезается;
- новые воины из Казарм становятся свободными, гарнизон не меняется;
- назначается в окне «Войска» в городе, карточка «Гарнизон города»: кнопки 0, -10, -1, +1, +10, «Все».
Пока гарнизон только держит воинов дома: на город игрока никто не нападает. Защита города, еда гарнизона и дебаффы при голоде — по плану ниже.
## События
- **Публикует:** `TroopsChangedEvent` (воинов в городе, из них в гарнизоне, и в походах) — при обучении, потерях, походе, возвращении, загрузке, сбросе. По нему `App/GameModules` перерисовывает панели HUD.
- **Слушает:** ничего.
## Сохранение
Секция `"army"`. Модуль без Unity, поэтому формат — простые строки:
```
troops=<пехота>,<лучники>,<кавалерия>,<осадные>
garrison=<пехота>,<лучники>,<кавалерия>,<осадные> (часть troops)
march=<id похода>:<пехота>,<лучники>,<кавалерия>,<осадные>
```
Старое сохранение без строки `garrison` читается: гарнизона нет.
Сброс (`ResetState`): войск, гарнизона и походов нет.
**Перенос из старых сохранений:**
- Раньше войска лежали в `progress.json`: `troops` хранил `GameProgress`, `sashaMarchUnits` — мост `ArmyBridge`.
- Если в `save.json` нет секции `"army"`, после загрузки `App/GameModules` переносит их в модуль (`ImportLegacy`).
- Из `progress.json` они удаляются только после записи `save.json`. Флаг `armyInModule` отмечает, что перенос сделан.
## Файлы
| Файл | Что это |
| --- | --- |
| `ArmyService.cs` | Хранилище войск, реализует `IArmy` и `ISaveParticipant`. |
| `ArmyMath.cs` | `TakeProportional` — кого взять в поход; `SplitSurvivors` — как вернуть выживших. |
## Решения и план
- **Этап 5 (план разработки):** солдат стоит 1 человека, 15 еды, 5 металла, 5 золота; содержание 0,2 еды в час; погибшие уменьшают население. Делается вместе с модулем «Население».
- **Решение команды от 19.10.2026:**
- войска набираются из населения с сохранением гражданского минимума;
- гарнизон поселения: часть солдат назначается защищать город;
- общая армия = гарнизон + доступные отряды + войска в походах, на точках и в гарнизоне Круга, **один солдат не может быть в двух местах**;
- гарнизон ест еду и участвует в защите;
- при голоде дебаффы действуют и на гарнизон, и на походы.
- **Сделано 27.10.2026:** гарнизон города (`SetGarrison`, `AvailableForMarch`).
- **Дальше по гарнизону:** участие в защите города (когда на него смогут напасть — PvP), еда гарнизона (сейчас все воины едят одинаково, считает модуль Населения), дебаффы при голоде. Выбор состава по типам — если понадобится.
- Описания типов войск (`GameData.Troops`: цена, сила, время обучения) пока в слое приложения; цены перейдут в `EconomyConfig`.
## Журнал изменений
| Дата | Кто | Что |
| --- | --- | --- |
| 31.10.2026 | Claude | Слоты похода: `MarchSlots`, `ActiveMarches`; `ArmyService(baseMarchSlots)`, база 2 из таблицы `MarchConfig`; `TakeForMarch` отказывает при занятых слотах. Тест `ArmyService_MarchSlots`. |
| 28.10.2026 | Claude | По ревью 27.10.2026 (п. 2.1–2.2): `ReturnFromMarch` и `ReturnUnits` возвращают `bool` и зачисляют только по известному походу и только один раз; `TakeForMarch` отклоняет повторный номер похода; дубликаты походов в сохранении пропускаются. Тесты: неизвестный поход, повторное возвращение, повторный номер. |
| 27.10.2026 | Claude | Гарнизон города: `GarrisonTotal`, `GetGarrison`, `AvailableForMarch`, `SetGarrison`; в поход уходят только свободные; строка `garrison=` в сохранении; поле `Garrison` в `TroopsChangedEvent`; карточка «Гарнизон города» в окне «Войска». Тест «один солдат в одном месте» с гарнизоном. |
| 27.10.2026 | Claude | `GetMarchUnits` и `ReturnUnits`: бой с типами войск получает состав похода, выжившие возвращаются точно по типам. |
| 23.10.2026 | Claude | Войска переехали в модуль: `ArmyService` — единственное хранилище войск (секция `"army"`), `IArmy` расширен (типы, обучение, потери, войска в походах), событие `TroopsChangedEvent`. Перенос из `progress.json` с удалением старых данных только после записи `save.json`. `ArmyBridge` удалён — контракты и перенос сохранений проверены тестами. |
| 19.10.2026 | Claude | `ArmyBridge.TroopsInMarches` для панели «Войска». После удаления маленькой карты войска в походах учитываются только здесь (`sashaMarchUnits`). |
| 19.10.2026 | Claude | Модуль создан: `ArmyMath` (правила деления войск, без Unity, тесты на отсутствие двойного учёта). `ArmyBridge` сохранён и теперь реализует `IArmy` через `ArmyMath`; формат `sashaMarchUnits` не изменён. |