Редкости и аффиксы: находка стала решением (M25)
Предмет перестал быть строкой каталога. ItemInstance несёт редкость и до
шести аффиксов, и «карабин» с первого этажа больше не равен «карабину» с
сорокового. Четыре редкости, десять тегов, тир и бюджет от глубины.
Аффиксы не заводят новых каналов влияния: каждый тег ложится в то, что уже
есть — урон оружия, смесь стихий, пробитие, четыре числа бойца, ресурсы
вылазки. У предмета по-прежнему нет ручки к правилу «не бить своих», и
появиться ей неоткуда.
ItemInstance неявно приводится к ItemId, поэтому полторы сотни мест,
которым нужен ВИД вещи, остались как были. Цена решения — присваивание в
ItemId молча теряет рулоны; таких мест три (подбор, схрон, запись
профиля), и все они работают с экземпляром целиком.
Замеры поймали три расхождения дизайн-документа с самим собой:
* формула редкости противоречила таблице рядом с ней: 2.7% находок с
аффиксами на первом этаже против 22%. Выбрана формула, наклон поднят
до схождения с таблицей на дне (12% обычных на сотом);
* бюджет пускал восемь высших аффиксов вместо обещанных шести;
* предельная вещь собиралась к семидесятому этажу вместо сотого.
Числа пересчитаны, документ исправлен: 6·cost(9) = 294 <= B(100) = 295 <
7·cost(9) = 343. Предельная вещь существует ровно на дне и ровно одна.
Проверка в --descend меряет не бросок, а ПРОВОДКУ до бойца — там, где
дважды умирали вес и стихии:
аффиксы: урон 12.0 -> 18.0, здоровье 100 -> 140, броня 0.10 -> 0.25,
пробитие 0.20, KIN 1.00 -> 0.60, CRY 0.40, сумма долей 1.000
Последнее число не украшение: конверсия обязана переводить урон, а не
добавлять седьмую долю сверх единицы.
Попутно, на глубине 30 и только там:
* «зачистка поля» в проверках не держалась — у твари есть откат
воскрешения, и с нулевым таймером она оживала тем же шагом. Лечение
вне схватки и расход света на стоянке молча мерили бой. На восьмом
этаже тварей мало, и проверка проходила по удаче;
* порог «стоянка бесплатна» стоял в единицах заряда, а тот же шаг стоит
дороже с глубиной. Переведён в тайлы — и сразу показал, что отряд в
строю проходит 0.8 тайла за две секунды «стоянки». Свет теперь
списывается только в ADVANCING: топтание и драка бесплатны, тратит
РАЗВЕДКА. Без этого таймер возвращался через чёрный ход.
Формат профиля 4: токен предмета вида rifle@relic+dmg7,0:0.63. Миграции
не требует — у старых токенов нет хвоста после '@'.
--accept PASS без изменений: в песочнице находок нет вовсе.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:35:00 +07:00
|
|
|
|
#pragma once
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
|
// Редкости и аффиксы: то, что превращает находку в РЕШЕНИЕ.
|
|
|
|
|
|
//
|
|
|
|
|
|
// До этой вехи предмет был строкой каталога: «карабин» на первом этаже и
|
|
|
|
|
|
// «карабин» на сотом — одна и та же вещь. Идти за ней в темноту было незачем,
|
|
|
|
|
|
// а весь спуск держался на количестве находок, а не на их качестве.
|
|
|
|
|
|
//
|
|
|
|
|
|
// Аффикс = тег + тир + рулон (docs/22-loot.md §3). Тег говорит, ЧТО он даёт;
|
|
|
|
|
|
// тир — насколько сильным он бывает на этой глубине; рулон — конкретное число.
|
|
|
|
|
|
//
|
|
|
|
|
|
// ТРИ ПРАВИЛА, КОТОРЫЕ ЗДЕСЬ ЗАЩИЩАЮТСЯ АРИФМЕТИКОЙ, А НЕ СПИСКАМИ ЗАПРЕТОВ:
|
|
|
|
|
|
//
|
|
|
|
|
|
// 1. БЮДЖЕТ. У предмета есть бюджет по уровню, аффикс стоит по тиру. Шесть
|
|
|
|
|
|
// высших аффиксов не влезают никуда, кроме предмета сотого уровня, — и
|
|
|
|
|
|
// это единственный барьер, который не приходится подпирать частными
|
|
|
|
|
|
// исключениями. Список исключений разрастается и однажды противоречит
|
|
|
|
|
|
// сам себе.
|
|
|
|
|
|
// 2. ОДИН ТЕГ ОДИН РАЗ. Иначе «+урон» шесть раз становится единственной
|
|
|
|
|
|
// правильной сборкой, а весь остальной список — мусором.
|
|
|
|
|
|
// 3. КИНЕТИКА НЕ ИСЧЕЗАЕТ. Конверсий не больше 0.85 в сумме: оружие без
|
|
|
|
|
|
// физического урона перестало бы работать по целям без сопротивлений
|
|
|
|
|
|
// вовсе (sim/element.h).
|
|
|
|
|
|
//
|
|
|
|
|
|
// Аффиксы НЕ заводят новых каналов влияния. Каждый тег кладётся в то, что уже
|
|
|
|
|
|
// есть: урон оружия, сопротивления, стихии, статы бойца, ресурсы вылазки. Это
|
|
|
|
|
|
// не экономия, а то же правило, что и у снаряжения: у предмета нет ручки к
|
|
|
|
|
|
// правилу «не бить своих», и появиться она не может (sim/items.h).
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
|
#include <cstdint>
|
|
|
|
|
|
|
|
|
|
|
|
#include "sim/element.h"
|
|
|
|
|
|
#include "sim/items.h"
|
Триггеры: четыре условных эффекта на аффиксах (M28)
ON_HIT поджигает цель, ON_KILL даёт рывок скорости стаками, ON_HURT
обжигает того, кто ударил, ON_DOWN даёт отряду ярость за павшего.
Отдельным тегом аффикса, то есть по тем же правилам, что и всё
остальное: платит по тиру, влезает в бюджет, катается и прививается.
СДЕЛАНО НЕ ТАК, КАК ПРЕДЛАГАЛ ДИЗАЙН. В docs/21-damage.md §4 стояло:
журнал боевых событий уже есть, вешайте триггеры на него. Звучит
правильно, и сделать так нельзя: sim/events.h это канал «симуляция ->
эффекты», односторонний по устройству, и в headless-прогонах его нет
вовсе. Начни правила боя его читать — игра станет вести себя по-разному
с окном и без, то есть --accept начнёт мерить не то, что меряет.
Триггеры срабатывают в месте урона. Опасение «два канала разойдутся»
снято иначе: второго канала никто и не завёл, есть четыре точки рядом с
тем уроном, который триггер обслуживает.
Четыре из девяти, и это не «пока»: каждый эффект обязан класть своё
число в уже существующий канал влияния. Крита в игре нет, площадного
урона нет, выдачи предметов на входе нет — заводить их ради строк
таблицы значит завести пять мёртвых веток. Причины выписаны в документе.
Три ограничителя проверяются числами в --descend:
триггеры: аффикс доехал да, ON_HIT поджёг стаков 1, откат 0.9 с
(за него наложено 0), ON_KILL стаков 1, скорость 1.06
рекурсия: за один шаг стаков прибавлялось максимум 1
Первая строка про проводку: аффикс с триггером легко сделать так, что он
лежит в предмете, показывается в подсказке и не делает ничего.
Проверка сперва брала targets[0] и проходила на descent по везению: на
hollow первым в пуле стоял ящик, а обстановка не горит и убийством не
считается. Теперь цель ищется по виду и ставится в свободную клетку с
прямой видимостью — иначе замеряется планировка этажа, а не триггеры.
Шаг симуляции 3.7 мкс из 16666. --accept PASS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 07:32:12 +07:00
|
|
|
|
#include "sim/trigger.h"
|
Редкости и аффиксы: находка стала решением (M25)
Предмет перестал быть строкой каталога. ItemInstance несёт редкость и до
шести аффиксов, и «карабин» с первого этажа больше не равен «карабину» с
сорокового. Четыре редкости, десять тегов, тир и бюджет от глубины.
Аффиксы не заводят новых каналов влияния: каждый тег ложится в то, что уже
есть — урон оружия, смесь стихий, пробитие, четыре числа бойца, ресурсы
вылазки. У предмета по-прежнему нет ручки к правилу «не бить своих», и
появиться ей неоткуда.
ItemInstance неявно приводится к ItemId, поэтому полторы сотни мест,
которым нужен ВИД вещи, остались как были. Цена решения — присваивание в
ItemId молча теряет рулоны; таких мест три (подбор, схрон, запись
профиля), и все они работают с экземпляром целиком.
Замеры поймали три расхождения дизайн-документа с самим собой:
* формула редкости противоречила таблице рядом с ней: 2.7% находок с
аффиксами на первом этаже против 22%. Выбрана формула, наклон поднят
до схождения с таблицей на дне (12% обычных на сотом);
* бюджет пускал восемь высших аффиксов вместо обещанных шести;
* предельная вещь собиралась к семидесятому этажу вместо сотого.
Числа пересчитаны, документ исправлен: 6·cost(9) = 294 <= B(100) = 295 <
7·cost(9) = 343. Предельная вещь существует ровно на дне и ровно одна.
Проверка в --descend меряет не бросок, а ПРОВОДКУ до бойца — там, где
дважды умирали вес и стихии:
аффиксы: урон 12.0 -> 18.0, здоровье 100 -> 140, броня 0.10 -> 0.25,
пробитие 0.20, KIN 1.00 -> 0.60, CRY 0.40, сумма долей 1.000
Последнее число не украшение: конверсия обязана переводить урон, а не
добавлять седьмую долю сверх единицы.
Попутно, на глубине 30 и только там:
* «зачистка поля» в проверках не держалась — у твари есть откат
воскрешения, и с нулевым таймером она оживала тем же шагом. Лечение
вне схватки и расход света на стоянке молча мерили бой. На восьмом
этаже тварей мало, и проверка проходила по удаче;
* порог «стоянка бесплатна» стоял в единицах заряда, а тот же шаг стоит
дороже с глубиной. Переведён в тайлы — и сразу показал, что отряд в
строю проходит 0.8 тайла за две секунды «стоянки». Свет теперь
списывается только в ADVANCING: топтание и драка бесплатны, тратит
РАЗВЕДКА. Без этого таймер возвращался через чёрный ход.
Формат профиля 4: токен предмета вида rifle@relic+dmg7,0:0.63. Миграции
не требует — у старых токенов нет хвоста после '@'.
--accept PASS без изменений: в песочнице находок нет вовсе.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:35:00 +07:00
|
|
|
|
|
|
|
|
|
|
// Сами типы (Rarity, AffixTag, Affix, ItemInstance) объявлены в sim/items.h:
|
|
|
|
|
|
// экземпляр предмета — это то, что боец несёт, и место ему там. Здесь — вся
|
|
|
|
|
|
// арифметика вокруг них.
|
|
|
|
|
|
|
|
|
|
|
|
inline const char* RarityName(Rarity r)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (r)
|
|
|
|
|
|
{
|
|
|
|
|
|
case Rarity::MARKED: return "MARKED";
|
|
|
|
|
|
case Rarity::PROTOTYPE: return "PROTOTYPE";
|
|
|
|
|
|
case Rarity::RELIC: return "RELIC";
|
|
|
|
|
|
default: return "COMMON";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Цвет рамки и подписи. Байты RGB: в sim/ типов рендера нет.
|
|
|
|
|
|
struct RarityColor { uint8_t r, g, b; };
|
|
|
|
|
|
|
|
|
|
|
|
inline RarityColor RarityColorOf(Rarity r)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (r)
|
|
|
|
|
|
{
|
|
|
|
|
|
case Rarity::MARKED: return {120, 190, 235};
|
|
|
|
|
|
case Rarity::PROTOTYPE: return {170, 140, 240};
|
|
|
|
|
|
case Rarity::RELIC: return {235, 165, 70};
|
|
|
|
|
|
default: return {190, 190, 190};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Доступный тир на глубине и стоимость аффикса в бюджете предмета.
|
|
|
|
|
|
constexpr int MAX_AFFIX_TIER = 9;
|
|
|
|
|
|
|
|
|
|
|
|
inline int MaxAffixTier(int depth)
|
|
|
|
|
|
{
|
|
|
|
|
|
const int t = 1 + (depth > 0 ? depth : 1) / 12;
|
|
|
|
|
|
return (t < MAX_AFFIX_TIER) ? t : MAX_AFFIX_TIER;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Цена тира и бюджет предмета. Числа НЕ те, что стояли в docs/22-loot.md §3:
|
|
|
|
|
|
// там было cost = 6 + 4·T при бюджете 40 + 3·ilvl, и утверждалось, что на
|
|
|
|
|
|
// предмете сотого уровня это даёт «шесть аффиксов девятого тира и ни одним
|
|
|
|
|
|
// больше». На деле давало восемь (340 / 42 = 8.1), а предмет-максимум
|
|
|
|
|
|
// собирался уже к семидесятому этажу.
|
|
|
|
|
|
//
|
|
|
|
|
|
// Пересчитано так, чтобы обещание выполнялось арифметически:
|
|
|
|
|
|
//
|
|
|
|
|
|
// 6 · cost(9) = 294 <= B(100) = 295 < 7 · cost(9) = 343
|
|
|
|
|
|
//
|
|
|
|
|
|
// То есть предельная вещь существует ровно на дне и ровно одна. Проверяется
|
|
|
|
|
|
// инструментом `--tool affix`, а не глазами.
|
|
|
|
|
|
inline float AffixCost(int tier) { return 4.0f + 5.0f * float(tier); }
|
|
|
|
|
|
inline float ItemBudget(int ilvl) { return 45.0f + 2.5f * float(ilvl); }
|
|
|
|
|
|
|
|
|
|
|
|
// Диапазон рулона тега на первом тире. Дальше растёт как T^1.35 — так высокий
|
|
|
|
|
|
// тир заметно лучше низкого, но не превращает предмет в другую игру.
|
|
|
|
|
|
struct AffixRange { float lo, hi; };
|
|
|
|
|
|
|
|
|
|
|
|
inline AffixRange AffixBase(AffixTag t)
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (t)
|
|
|
|
|
|
{
|
|
|
|
|
|
case AffixTag::DAMAGE: return {0.05f, 0.09f};
|
|
|
|
|
|
case AffixTag::PEN: return {0.03f, 0.06f};
|
|
|
|
|
|
case AffixTag::CONVERT: return {0.08f, 0.14f};
|
|
|
|
|
|
case AffixTag::ARMOR: return {0.02f, 0.04f};
|
|
|
|
|
|
case AffixTag::HEALTH: return {4.0f, 8.0f};
|
|
|
|
|
|
case AffixTag::SPEED: return {0.02f, 0.035f};
|
|
|
|
|
|
case AffixTag::SIGHT: return {0.03f, 0.05f};
|
|
|
|
|
|
case AffixTag::AMMO: return {8.0f, 14.0f};
|
|
|
|
|
|
case AffixTag::CARRY: return {0.30f, 0.55f};
|
|
|
|
|
|
case AffixTag::LIGHT: return {18.0f, 30.0f};
|
Триггеры: четыре условных эффекта на аффиксах (M28)
ON_HIT поджигает цель, ON_KILL даёт рывок скорости стаками, ON_HURT
обжигает того, кто ударил, ON_DOWN даёт отряду ярость за павшего.
Отдельным тегом аффикса, то есть по тем же правилам, что и всё
остальное: платит по тиру, влезает в бюджет, катается и прививается.
СДЕЛАНО НЕ ТАК, КАК ПРЕДЛАГАЛ ДИЗАЙН. В docs/21-damage.md §4 стояло:
журнал боевых событий уже есть, вешайте триггеры на него. Звучит
правильно, и сделать так нельзя: sim/events.h это канал «симуляция ->
эффекты», односторонний по устройству, и в headless-прогонах его нет
вовсе. Начни правила боя его читать — игра станет вести себя по-разному
с окном и без, то есть --accept начнёт мерить не то, что меряет.
Триггеры срабатывают в месте урона. Опасение «два канала разойдутся»
снято иначе: второго канала никто и не завёл, есть четыре точки рядом с
тем уроном, который триггер обслуживает.
Четыре из девяти, и это не «пока»: каждый эффект обязан класть своё
число в уже существующий канал влияния. Крита в игре нет, площадного
урона нет, выдачи предметов на входе нет — заводить их ради строк
таблицы значит завести пять мёртвых веток. Причины выписаны в документе.
Три ограничителя проверяются числами в --descend:
триггеры: аффикс доехал да, ON_HIT поджёг стаков 1, откат 0.9 с
(за него наложено 0), ON_KILL стаков 1, скорость 1.06
рекурсия: за один шаг стаков прибавлялось максимум 1
Первая строка про проводку: аффикс с триггером легко сделать так, что он
лежит в предмете, показывается в подсказке и не делает ничего.
Проверка сперва брала targets[0] и проходила на descent по везению: на
hollow первым в пуле стоял ящик, а обстановка не горит и убийством не
считается. Теперь цель ищется по виду и ставится в свободную клетку с
прямой видимостью — иначе замеряется планировка этажа, а не триггеры.
Шаг симуляции 3.7 мкс из 16666. --accept PASS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 07:32:12 +07:00
|
|
|
|
// Триггер — это ШАНС сработать. Дорогой аффикс с маленькими числами:
|
|
|
|
|
|
// «изредка поджигает» меняет бой сильнее, чем «+7% урона».
|
|
|
|
|
|
case AffixTag::TRIGGER: return {0.06f, 0.10f};
|
Редкости и аффиксы: находка стала решением (M25)
Предмет перестал быть строкой каталога. ItemInstance несёт редкость и до
шести аффиксов, и «карабин» с первого этажа больше не равен «карабину» с
сорокового. Четыре редкости, десять тегов, тир и бюджет от глубины.
Аффиксы не заводят новых каналов влияния: каждый тег ложится в то, что уже
есть — урон оружия, смесь стихий, пробитие, четыре числа бойца, ресурсы
вылазки. У предмета по-прежнему нет ручки к правилу «не бить своих», и
появиться ей неоткуда.
ItemInstance неявно приводится к ItemId, поэтому полторы сотни мест,
которым нужен ВИД вещи, остались как были. Цена решения — присваивание в
ItemId молча теряет рулоны; таких мест три (подбор, схрон, запись
профиля), и все они работают с экземпляром целиком.
Замеры поймали три расхождения дизайн-документа с самим собой:
* формула редкости противоречила таблице рядом с ней: 2.7% находок с
аффиксами на первом этаже против 22%. Выбрана формула, наклон поднят
до схождения с таблицей на дне (12% обычных на сотом);
* бюджет пускал восемь высших аффиксов вместо обещанных шести;
* предельная вещь собиралась к семидесятому этажу вместо сотого.
Числа пересчитаны, документ исправлен: 6·cost(9) = 294 <= B(100) = 295 <
7·cost(9) = 343. Предельная вещь существует ровно на дне и ровно одна.
Проверка в --descend меряет не бросок, а ПРОВОДКУ до бойца — там, где
дважды умирали вес и стихии:
аффиксы: урон 12.0 -> 18.0, здоровье 100 -> 140, броня 0.10 -> 0.25,
пробитие 0.20, KIN 1.00 -> 0.60, CRY 0.40, сумма долей 1.000
Последнее число не украшение: конверсия обязана переводить урон, а не
добавлять седьмую долю сверх единицы.
Попутно, на глубине 30 и только там:
* «зачистка поля» в проверках не держалась — у твари есть откат
воскрешения, и с нулевым таймером она оживала тем же шагом. Лечение
вне схватки и расход света на стоянке молча мерили бой. На восьмом
этаже тварей мало, и проверка проходила по удаче;
* порог «стоянка бесплатна» стоял в единицах заряда, а тот же шаг стоит
дороже с глубиной. Переведён в тайлы — и сразу показал, что отряд в
строю проходит 0.8 тайла за две секунды «стоянки». Свет теперь
списывается только в ADVANCING: топтание и драка бесплатны, тратит
РАЗВЕДКА. Без этого таймер возвращался через чёрный ход.
Формат профиля 4: токен предмета вида rifle@relic+dmg7,0:0.63. Миграции
не требует — у старых токенов нет хвоста после '@'.
--accept PASS без изменений: в песочнице находок нет вовсе.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:35:00 +07:00
|
|
|
|
default: return {0.0f, 0.0f};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const char* AffixName(AffixTag t);
|
|
|
|
|
|
|
|
|
|
|
|
// Строка для подсказки: «DAMAGE +14%». Пишет в буфер, потому что подсказки
|
|
|
|
|
|
// собираются на стеке и аллокаций в UI за кадр быть не должно.
|
|
|
|
|
|
void AffixLabel(const Affix& a, char* out, int cap);
|
|
|
|
|
|
|
|
|
|
|
|
// Какие теги вообще ложатся на этот вид предмета. Каталог, а не «на что
|
|
|
|
|
|
// похоже»: конверсия на куртке — это урон, которого у куртки нет.
|
|
|
|
|
|
bool AffixFitsKind(AffixTag t, ItemKind k);
|
|
|
|
|
|
|
|
|
|
|
|
// --- накопленные модификаторы -------------------------------------------------
|
|
|
|
|
|
// Сумма аффиксов надетого. Считается в SyncLoadout один раз за шаг, дальше все
|
|
|
|
|
|
// читают готовые числа: лазать по аффиксам из правил боя нельзя, там про
|
|
|
|
|
|
// предметы не знают вообще.
|
|
|
|
|
|
struct AffixMods
|
|
|
|
|
|
{
|
|
|
|
|
|
float damageMul = 1.0f; // множитель урона оружия
|
|
|
|
|
|
float pen = 0.0f; // пробитие сопротивлений
|
|
|
|
|
|
ElementMix convert; // во что переведена часть урона
|
|
|
|
|
|
float armor = 0.0f;
|
|
|
|
|
|
float hp = 0.0f;
|
|
|
|
|
|
float speedMul = 1.0f;
|
|
|
|
|
|
float sightMul = 1.0f;
|
|
|
|
|
|
float ammo = 0.0f;
|
|
|
|
|
|
float carry = 0.0f;
|
|
|
|
|
|
float light = 0.0f;
|
|
|
|
|
|
|
Триггеры: четыре условных эффекта на аффиксах (M28)
ON_HIT поджигает цель, ON_KILL даёт рывок скорости стаками, ON_HURT
обжигает того, кто ударил, ON_DOWN даёт отряду ярость за павшего.
Отдельным тегом аффикса, то есть по тем же правилам, что и всё
остальное: платит по тиру, влезает в бюджет, катается и прививается.
СДЕЛАНО НЕ ТАК, КАК ПРЕДЛАГАЛ ДИЗАЙН. В docs/21-damage.md §4 стояло:
журнал боевых событий уже есть, вешайте триггеры на него. Звучит
правильно, и сделать так нельзя: sim/events.h это канал «симуляция ->
эффекты», односторонний по устройству, и в headless-прогонах его нет
вовсе. Начни правила боя его читать — игра станет вести себя по-разному
с окном и без, то есть --accept начнёт мерить не то, что меряет.
Триггеры срабатывают в месте урона. Опасение «два канала разойдутся»
снято иначе: второго канала никто и не завёл, есть четыре точки рядом с
тем уроном, который триггер обслуживает.
Четыре из девяти, и это не «пока»: каждый эффект обязан класть своё
число в уже существующий канал влияния. Крита в игре нет, площадного
урона нет, выдачи предметов на входе нет — заводить их ради строк
таблицы значит завести пять мёртвых веток. Причины выписаны в документе.
Три ограничителя проверяются числами в --descend:
триггеры: аффикс доехал да, ON_HIT поджёг стаков 1, откат 0.9 с
(за него наложено 0), ON_KILL стаков 1, скорость 1.06
рекурсия: за один шаг стаков прибавлялось максимум 1
Первая строка про проводку: аффикс с триггером легко сделать так, что он
лежит в предмете, показывается в подсказке и не делает ничего.
Проверка сперва брала targets[0] и проходила на descent по везению: на
hollow первым в пуле стоял ящик, а обстановка не горит и убийством не
считается. Теперь цель ищется по виду и ставится в свободную клетку с
прямой видимостью — иначе замеряется планировка этажа, а не триггеры.
Шаг симуляции 3.7 мкс из 16666. --accept PASS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 07:32:12 +07:00
|
|
|
|
// Сила по видам триггеров. Ноль — такого триггера на бойце нет
|
|
|
|
|
|
// (sim/trigger.h). Массивом, а не флагами: два предмета с одним видом
|
|
|
|
|
|
// складываются, и это законно — сильнее, но не чаще, откат общий.
|
|
|
|
|
|
float trigger[TRIGGER_COUNT] = {};
|
|
|
|
|
|
|
Редкости и аффиксы: находка стала решением (M25)
Предмет перестал быть строкой каталога. ItemInstance несёт редкость и до
шести аффиксов, и «карабин» с первого этажа больше не равен «карабину» с
сорокового. Четыре редкости, десять тегов, тир и бюджет от глубины.
Аффиксы не заводят новых каналов влияния: каждый тег ложится в то, что уже
есть — урон оружия, смесь стихий, пробитие, четыре числа бойца, ресурсы
вылазки. У предмета по-прежнему нет ручки к правилу «не бить своих», и
появиться ей неоткуда.
ItemInstance неявно приводится к ItemId, поэтому полторы сотни мест,
которым нужен ВИД вещи, остались как были. Цена решения — присваивание в
ItemId молча теряет рулоны; таких мест три (подбор, схрон, запись
профиля), и все они работают с экземпляром целиком.
Замеры поймали три расхождения дизайн-документа с самим собой:
* формула редкости противоречила таблице рядом с ней: 2.7% находок с
аффиксами на первом этаже против 22%. Выбрана формула, наклон поднят
до схождения с таблицей на дне (12% обычных на сотом);
* бюджет пускал восемь высших аффиксов вместо обещанных шести;
* предельная вещь собиралась к семидесятому этажу вместо сотого.
Числа пересчитаны, документ исправлен: 6·cost(9) = 294 <= B(100) = 295 <
7·cost(9) = 343. Предельная вещь существует ровно на дне и ровно одна.
Проверка в --descend меряет не бросок, а ПРОВОДКУ до бойца — там, где
дважды умирали вес и стихии:
аффиксы: урон 12.0 -> 18.0, здоровье 100 -> 140, броня 0.10 -> 0.25,
пробитие 0.20, KIN 1.00 -> 0.60, CRY 0.40, сумма долей 1.000
Последнее число не украшение: конверсия обязана переводить урон, а не
добавлять седьмую долю сверх единицы.
Попутно, на глубине 30 и только там:
* «зачистка поля» в проверках не держалась — у твари есть откат
воскрешения, и с нулевым таймером она оживала тем же шагом. Лечение
вне схватки и расход света на стоянке молча мерили бой. На восьмом
этаже тварей мало, и проверка проходила по удаче;
* порог «стоянка бесплатна» стоял в единицах заряда, а тот же шаг стоит
дороже с глубиной. Переведён в тайлы — и сразу показал, что отряд в
строю проходит 0.8 тайла за две секунды «стоянки». Свет теперь
списывается только в ADVANCING: топтание и драка бесплатны, тратит
РАЗВЕДКА. Без этого таймер возвращался через чёрный ход.
Формат профиля 4: токен предмета вида rifle@relic+dmg7,0:0.63. Миграции
не требует — у старых токенов нет хвоста после '@'.
--accept PASS без изменений: в песочнице находок нет вовсе.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 06:35:00 +07:00
|
|
|
|
void Add(const Affix& a);
|
|
|
|
|
|
void Add(const ItemInstance& it);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Смешать собственную стихию оружия с конверсией аффиксов.
|
|
|
|
|
|
//
|
|
|
|
|
|
// Конверсия ЗАМЕНЯЕТ часть смеси, а не добавляется поверх: у оружия смесь одна
|
|
|
|
|
|
// и сумма долей всегда единица (sim/element.h). Иначе «дробовик с жаром плюс
|
|
|
|
|
|
// 20% холода» давал бы 120% урона, и стихии стали бы каналом наращивания
|
|
|
|
|
|
// чисел — ровно тем, чем они не должны быть.
|
|
|
|
|
|
ElementMix MergeConversion(const ElementMix& base, const ElementMix& convert);
|
|
|
|
|
|
|
|
|
|
|
|
// --- бросок предмета ----------------------------------------------------------
|
|
|
|
|
|
// Сид передаётся снаружи: генерация уровня детерминирована по сиду, и находки
|
|
|
|
|
|
// обязаны быть частью этой же детерминированности. Иначе «тот же уровень» — это
|
|
|
|
|
|
// уже не тот же уровень, и сравнить два прогона нельзя.
|
|
|
|
|
|
ItemInstance RollItem(ItemId base, int depth, uint32_t& rng);
|
|
|
|
|
|
|
|
|
|
|
|
// Редкость по глубине. Отдельно от RollItem, потому что её проверяют
|
|
|
|
|
|
// инструментом на десятках тысяч бросков (`--tool drops`).
|
|
|
|
|
|
Rarity RollRarity(int depth, uint32_t& rng);
|
|
|
|
|
|
|
|
|
|
|
|
// Сколько аффиксов у этой редкости (нижняя и верхняя граница).
|
|
|
|
|
|
int AffixCountFor(Rarity r, uint32_t& rng);
|