# Предметы и инвентарь — `$.items`, `$.inv`

Предмет описывается один раз, а лежит в контейнерах: рюкзак, схрон, тайник
торговца. Порт из audm-neko (`item_def.gd`, `items.gd`), где предмет был
`.tres`-ресурсом с размером в клетках, массой и слотом ношения.

```js
$.ready(() => {
    $.items.define({ id: 'medkit', title: 'Аптечка', kind: 'med', size: [1, 2],
                     mass: 0.6, value: 300, heal: 40, stack: 3 });
    $.items.define({ id: 'ak', title: 'АК', kind: 'weapon', size: [6, 2],
                     mass: 3.8, value: 9000, wear: 'back' });
    $.items.define({ id: 'vest', title: 'Жилет', kind: 'armor', size: [2, 2],
                     mass: 4.0, wear: 'armor', armor_class: 3, durability: 100 });
    $.items.define({ id: 'pack', title: 'Рюкзак', size: [3, 3], mass: 1.5,
                     wear: 'pack', carry_bonus: 12 });

    const bag = $.inv.create({ cols: 8, rows: 6, capacity: 25 });
    bag.add('medkit', 2);
    bag.add('ak', 1);
    bag.wear('vest');
    bag.wear('pack');
    bag.mass();          // вес, кг
    bag.carryLimit();    // предел + бонус рюкзака
});
```

---

## 1. Описание предмета

| Поле | Смысл |
|---|---|
| `id`, `title` | ключ и подпись |
| `kind` | `weapon`, `part`, `ammo`, `med`, `other`, `food`, `armor`, `loot` |
| `size` | `[ширина, высота]` в клетках (автомат 6×2, аптечка 1×2, бинт 1×1) |
| `mass`, `value` | вес в кг и цена |
| `stack` | сколько штук в стопке (патроны — 60, остальное поштучно) |
| `wear` | слот ношения: `armor`, `helmet`, `rig`, `pack`, `back` (пусто — не надевается) |
| `armor_class`, `durability` | защита и прочность |
| `heal`, `stops_bleeding` | медицина |
| `food`, `water` | доля шкалы сытости/жажды (0..1) |
| `ammo`, `weapon`, `carry_bonus` | патроны, ствол, бонус переноса |
| `icon`, `lore` | картинка и текст документа |

## 2. Контейнер

| Вызов | Возвращает |
|---|---|
| `$.inv.create({ cols, rows, capacity, baseMass? })` | инвентарь |
| `bag.add(id, count?)` | сколько штук уложено (может быть меньше) |
| `bag.remove(id, count?)` | сколько снято (сначала снимается надетое) |
| `bag.count(id)` / `bag.has(id, n?)` | штук всего (с надетым) / хватает ли |
| `bag.find(id)` / `bag.fits(id, n)` | место, не занимая / хватит ли места |
| `bag.list()` / `bag.summary()` | содержимое с координатами / сводка по id |
| `bag.mass()` / `bag.value()` | вес, кг / общая ценность |
| `bag.wear(id)` / `bag.unwear(slot)` | надеть / снять |
| `bag.wornList()` / `bag.armorClass(slot?)` | что надето / класс защиты |
| `bag.repack()` / `bag.sortByValue()` | уложить заново / дорогое наверх |
| `bag.save()` / `bag.load(data)` | снимок и восстановление |

## 3. Раскладка и стопки

Предмет занимает `size` клеток и лежит в контейнере целиком. Поиск места идёт
**сверху вниз, слева направо**, поэтому раскладка детерминированная — инвентарь
воспроизводим в тестах и сейвах. Стопки: при `stack > 1` предмет сначала
доливается в существующие стопки, и только потом кладётся на новое место
(патроны лягут как 60 + 40, а не двумя стопками по 50).

## 4. Вес и ношение

* `mass()` считает и лежащее, и надетое, плюс `setBaseMass` — собственную массу
  контейнера (сам рюкзак, схрон);
* `capacity()` — **предел, который задала игра**: бонус рюкзака в него не
  входит, поэтому `setCapacity(8)` даёт ровно 8;
* `carryBonus()` — сумма бонусов надетого; `carryLimit()` — предел плюс бонус;
* `overloaded()` — сравнение массы с `carryLimit()`; при нулевом пределе
  перегруза нет никогда.

`wear()` возвращает **имя надетого предмета** (или `null`, если надеть нельзя).
Прежний предмет слота возвращается в контейнер; повторное надевание того же
предмета — не ошибка и не создаёт второй экземпляр. `unwear()` не снимет
предмет, если в контейнере нет места — иначе вещь пропала бы.

## 5. Загрузка из JSON

```js
$.items.load([{ id: 'a' }, { id: 'b' }]);        // пачка
$.items.loadFile('items.json');                  // { items: [...] } тоже
```

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

* **предметы не вращаются в сетке**: размер занимает клетки как задан; поворот
  на 90° не поддержан (в оригинале его тоже не было);
* **вложенные контейнеры**: рюкзак внутри рюкзака — просто предмет, отдельного
  раскрытия нет;
* **эффекты не применяются**: `heal`, `food`, `water`, `ammo` — данные; кто и
  когда их применяет, решает игра (`$.inv` их не «расходует»);
* **прочность брони не тает**: `durability` хранится, но урон по ней считает
  боевая система;
* **денег одна сумма**: `bag.money` — кошелёк контейнера, отдельного счёта
  торговца нет.

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

```bash
# ядро: укладка, стопки, вес, ношение, сейв (без движка)
build/_deps/quickjs-build/qjs tests/js/items_test.mjs
```
