# Бой: здоровье по зонам, урон и кровь — `$.combat`

Порт из audm-neko (`limb_health.gd`, `damage_model.gd`). Здоровье — не одно
число, а **пять зон**, как в таркове, но без душноты: чёрную конечность
поднимает обычная аптечка, а не только хирургия — иначе игрок без хирургии
встаёт в тупик.

| Зона | Максимум | Ноль означает |
|---|---|---|
| `head` | 35 | смерть |
| `chest` | 85 | смерть |
| `arms` | 60 | чёрная рука: тряска прицела ×1.6, перезарядка ×1.4 |
| `stomach` | 70 | чёрный живот: тикает уроном, пока идёт кровь |
| `legs` | 65 | чёрные ноги: скорость ×0.45, прыжок ×0.5 |

Парные конечности — **один пул**: левая и правая рука считаются вместе.
Хитбоксы у них разные, но игроку не надо помнить, какую именно зацепило.

```js
$.ready(() => {
    $.combat.attach('#hero');                       // полоска узла = зоны
    $.combat.attach('#enemy', { onDeath: (node) => loot(node) });

    // Выстрел: оружие 35, попадание в голову.
    $.combat.hit('#enemy', 35, 'head');

    // Кровь тикает каждый кадр (или зовётся вручную).
    $.update((dt) => $.combat.tick(dt));
});
```

---

## 1. Урон

Итоговый урон попадания: **(оружие + добавка за часть) × 1.35** — числа
оригинала. Добавки: голова +30, грудь +10, руки/ноги/живот +6.

| Вызов | Что делает |
|---|---|
| `$.combat.damage(35, 'head')` | урон оружия по слоту хитбокса |
| `$.combat.damageFor(35, 'head')` | то же по имени зоны |
| `$.combat.zoneForSlot(slot)` | слот рига → зона (`arm_forearm_front` → `arms`) |
| `$.combat.hit(узел, 35, 'head', opts?)` | выстрел по бойцу с привязкой к узлу |

Неизвестный слот считается **грудью**: лучше попасть, чем промахнуться в
пустоту. `opts`: `zone` — своя зона, `raw` — урон без расчёта, `armor` — броня,
`tick` — сразу отсчитать крови.

## 2. Модель здоровья

```js
const hp = $.combat.health('#hero');   // модель привязанного бойца
hp.get('chest');          // HP зоны
hp.hit('arm', 30);        // урон: { died, blacked, amount, zone, blocked }
hp.blacked('legs');       // конечность чёрная?
hp.bleeding();            // ['arms', 'stomach', …]
hp.tick(dt);              // кровь
hp.heal('arms', 20);      // аптечка (поднимает и чёрное)
hp.stopBleeding();        // жгут
hp.aimPenalty();          // множители дебаффов
hp.report();              // «Руки 0/60 (чёрная), Ноги 40/65 (кровь)»
hp.save() / hp.load(d);   // сейв
```

`$.combat.health()` без аргумента создаёт **новую** модель — удобно для своих
бойцов и тестов.

## 3. Кровь

Сильное попадание (**от 25 урона**) в руку, живот или ногу заводит
кровотечение: зона теряет 1 HP в секунду, пока кровь не остановят жгутом или
аптечкой. Голова и грудь не кровят — там сразу смерть или ничего.

Доли времени не теряются: модель копит остаток и считает кровь шагами по 0.1 с,
поэтому 60 кадров по 1/60 дают ровно 1 HP потери.

## 4. Броня

Класс брони 1..6 держит от 15% до 70% урона, и защита падает вместе с
прочностью: `applyArmor(amount, class, durability, maxDurability)` возвращает
`{ damage, blocked, spent }`. Целая броня шестого класса держит 70%, убитая —
ничего.

```js
$.combat.hit('#hero', 35, 'chest', {
    armor: { class: 3, durability: 80, maxDurability: 100, spend: (n) => wearOff(n) },
});
```

## 5. События

Через шину `$.signal`: `combat:zone` (урон по зоне), `combat:blacked` (зона
почернела), `combat:bleed` (началось кровотечение), `combat:died` (смерть).
У привязанного узла обновляются `cur_hp`/`max_hp` — сумма по ВСЕМ зонам: если
считать только нелетальные, попадание в грудь не двигало бы полоску вовсе
(этот баг нашёлся при проверке в движке). `hp.total()` остаётся «суммой без
смертельных» — для совместимости с прежним смыслом, а `hp.sum()` — полная.

## 6. Ограничения

* **попадание считает игра**: модель не знает ни формы хитбоксов, ни
  траектории — она принимает уже готовый урон и слот;
* **отрыва конечностей нет**: `LIMB_SLOTS`/`RIP_DELAY` оригинала не портированы
  (это визуальная система поверх модели);
* **нет боли, давления и пульса**: `blood_pressure.gd` и `fighter.gd` не
  портированы — из них взята именно модель конечностей;
* **полоска — сумма по всем зонам**: смерть определяется только головой и
  грудью, поэтому полоска может быть «почти полной» у бойца с пробитой грудиной;
* **модель на узел, а не на бойца**: физика, ИИ и оружие остаются за игрой.

## 7. Проверка

```bash
# зоны, смерть, кровь, чёрные конечности, броня и урон (без движка)
build/_deps/quickjs-build/qjs tests/js/combat_test.mjs
```
