Гайд Укрощение случайности: Анализ нового no spread.

Разработчик
Статус
Оффлайн
Регистрация
1 Сен 2018
Сообщения
1,597
Реакции[?]
881
Поинты[?]
116K
Вступление
Привет всем! Сегодня я хочу поделиться с вами информацией о том, почему снова появился no-spread и как он работает. Это действительно то, что изменит правила игры для всех

Изменения которые привели к его появлению
В ночь 9 ноября успешная компания Valve выпустила новое обновление для Counter-Strike 2. В этом обновлении они решили исправить проблемы с системой sub-tick. Одно из решений, к которому они пришли, было синхронизировать клиентское и серверный сид случайностей.
Как мы знаем, предыдущий no-spread появился из-за того, что сервер доверял клиентскому сиду. Решение этой проблемы было в генерации сида на сервере, что привело к десинхронизации до 9 ноября (в редких случаях она все еще существует)
Изменения на клиенте
В этой части я применю реверс-инжиниринг Soufiw в знак уважения к исходному источнику.
После этого обновления, сид синхронизируется между клиентом и сервером с помощью хэширования углов обзора и текущего тика отрисовки.

C++:
uint32_t GetRandomSeed( const QAngle& angles, int player_render_tick )

{

#ifdef SERVER_DLL

    // sv_usercmd_custom_random_seed = false by default now.

    // m_nPredictionRandomSeed = cmd->random_seed;

    // m_nPredictionRandomSeedServer = sv_usercmd_custom_random_seed ? GetRngSeed() : cmd->random_seed;

    if ( m_nPredictionRandomSeedServer != m_nPredictionRandomSeed )

        return m_nPredictionRandomSeedServer;

#endif

    struct HashBuffer

    {

        float pitch;

        float yaw;

        int tick;

    };

    HashBuffer buffer;

    float pitch = AngleNormalize(angles.pitch);

    pitch += pitch;

    buffer.pitch = RoundFloat(pitch) * 0.5f;

    float yaw = AngleNormalize(angles.yaw);

    yaw += yaw;

    buffer.yaw = RoundFloat(yaw) * 0.5f;

    buffer.tick = player_render_tick;

    CSHA1 sha1;

    sha1.Reset(  );

    sha1.Update( &buffer, sizeof(buffer) );

    sha1.Final();

    return *reinterpret_cast<uint32_t*>(&sha1.m_digest[0]);

}
В итоге, создание no-spread кажется действительно простым: всего лишь нужно получить текущий сид и компенсировать его, как в 2015 году! Но, к сожалению, это не работает так. При изменении угла обзора, сид также меняется, что логично. Чтобы обойти это ограничение, я пришел к более простому и оригинальному решению.

Использование хаоса как основу для порядка
Если вы сейчас стоите, то вам лучше присесть, потому что это самое простое решение, которое можно придумать.

Предположим, у нас есть угол обзора и текущий тик отрисовки. Для компенсации разброса мы можем перебирать случайные углы, вычислять для них сид и вычислять финальный угол с учетом разброса.

В итоге, наш принцип действия будет выглядеть так:
  1. Генерируем случайный угол.
  2. Вычисляем сид для этого случайного угла и текущего тика.
  3. Используем полученный сид для моделирования выстрела с разбросом из текущего угла.
  4. Получаем финальный угол выстрела с учетом разброса.
  5. Если финальный угол достаточно близок к исходному (в пределах заданного порога), сохраняем найденный сид.
  6. Если нет, генерируем новый случайный угол и повторяем шаги с 3 по 6.
  7. Как только находим подходящий угол, используем его вместо реального.
  8. Для нового угла обзора повторяем поиск подходящего угла сначала.

Таким образом, мы перебираем случайные варианты, пока не найдем сид, которое компенсирует разброс для текущего угла.
Улучшения реализации: В нашем реализации мы брали угол обзора и моделировали разброс с сидом от 0 до 255. Это позволило оптимизировать подбор случайного угла.
Как же посчитать разброс?
Давайте сразу перейдем к коду, который отвечает за моделирование разброса. Я не буду углубляться в подробные объяснения, чтобы быстрее показать реализацию.

C++:
bool bForceMaxInaccuracy = weapon_debug_max_inaccuracy.GetBool();

bool bForceInaccuracyDirection = weapon_debug_inaccuracy_only_up.GetBool();

bool bWeaponAccuracyShotgunSpreadPatterns = weapon_accuracy_shotgun_spread_patterns.GetBool();


Math::RandomSeed(nRandomSeed + 1);


float flRadiusCurveDensity = Math::RandomFloat(0.0f, 1.0f);


if (nItemIndex == 64 && nMode == nSecondaryMode) {

  // R8 Revolver secondary fire

  flRadiusCurveDensity = 1.0f - flRadiusCurveDensity * flRadiusCurveDensity;

}

else if (nItemIndex == 28 && flRecoilIndex < 3) {

  // Negev Wild Beast

  for (int nIndex = 3; nIndex > flRecoilIndex; --nIndex) {

    flRadiusCurveDensity *= flRadiusCurveDensity;

  }

  flRadiusCurveDensity = 1.0f - flRadiusCurveDensity;

}


if (bForceMaxInaccuracy) {

  flRadiusCurveDensity = 1.0f;

}


float flTheta0 = Math::RandomFloat(0.0f, 2.0f * M_PI);

if (bForceInaccuracyDirection) {

  flTheta0 = M_PI * 0.5f;

}


if (bWeaponAccuracyShotgunSpreadPatterns)

{

    // Missing.

}


float flRadius0 = flRadiusCurveDensity * flInaccuracy;

float flX0 = flRadius0 * cosf(flTheta0);

float flY0 = flRadius0 * sinf(flTheta0);


float flSpreadCurveDensity = Math::RandomFloat(0.0f, 1.0f);


if (nItemIndex == 64 && nMode == nSecondaryMode) {

  // R8 Revolver secondary fire

  flSpreadCurveDensity = 1.0f - flSpreadCurveDensity * flSpreadCurveDensity;

}

else if (nItemIndex == 28 && flRecoilIndex < 3) {

  // Negev Wild Beast

  for (int nIndex = 3; nIndex > flRecoilIndex; --nIndex) {

    flSpreadCurveDensity *= flSpreadCurveDensity;

  }

  flSpreadCurveDensity = 1.0f - flSpreadCurveDensity;

}


if (bForceMaxInaccuracy) {

  flSpreadCurveDensity = 1.0f;

}


float flTheta1 = Math::RandomFloat(0.0f, 2.0f * M_PI);

if (bForceInaccuracyDirection) {

  flTheta1 = M_PI * 0.5f;

}


float flRadius1 = flSpreadCurveDensity * flSpread;

float flX1 = flRadius1 * cosf(flTheta1);

float flY1 = flRadius1 * sinf(flTheta1);


double flYResult = (double)(flY1 * flRadius0) +

               (double)(flY0 * flRadius1);


double flXResult = (double)(flX1 * flRadius0) +

               (double)(flX0 * flRadius1);
Итог
Сложив всю информацию, вы могли прийти к тому же результату, что и я. Возврат компенсации отдачи повлечёт серьёзные изменения в культуре HvH и читеров в целом. Сейчас я хочу продемонстрировать, что это действительно работает.
В этом видео, записанном моим другом porches, мы тестируем работу компенсации отдачи. Для создания лабораторных условий отдачи установлено на 0.25 с помощью команды weapon_accuracy_forcespread.
Давайте посмотрим, как это работает на практике:

Всем пока, еще встретимся!

Credits:
SapDragon - Theoretical research
Porches - Practical implementation
 
Разработчик
Статус
Оффлайн
Регистрация
1 Сен 2018
Сообщения
1,597
Реакции[?]
881
Поинты[?]
116K
Начинающий
Статус
Оффлайн
Регистрация
31 Окт 2023
Сообщения
69
Реакции[?]
28
Поинты[?]
27K
Да это ж чисто PoC того, что но сприд снова существует, и очень просто делается
это больше похоже на улучшенный просприд триггербот, который брутфорсит сид через углы. ноусприд это когда твой выстрел регает в чела....
 
Разработчик
Статус
Оффлайн
Регистрация
1 Сен 2018
Сообщения
1,597
Реакции[?]
881
Поинты[?]
116K
это больше похоже на улучшенный просприд триггербот, который брутфорсит сид через углы. ноусприд это когда твой выстрел регает в чела....
просьба хоть ознакомиться с постом и видео прежде чем это писать
 
Начинающий
Статус
Оффлайн
Регистрация
31 Окт 2023
Сообщения
69
Реакции[?]
28
Поинты[?]
27K
просьба хоть ознакомиться с постом и видео прежде чем это писать
Анализ нового no spread.
Анализ нового no spread.
Анализ нового no spread.
Анализ нового no spread.
Анализ нового no spread.
Анализ нового no spread.
Анализ нового no spread.
Анализ нового no spread.
Анализ нового no spread.
Анализ нового no spread.
 
Разработчик
Статус
Оффлайн
Регистрация
1 Сен 2018
Сообщения
1,597
Реакции[?]
881
Поинты[?]
116K
Анализ нового no spread.
Анализ нового no spread.
Анализ нового no spread.
Анализ нового no spread.
Анализ нового no spread.
Анализ нового no spread.
Анализ нового no spread.
Анализ нового no spread.
Анализ нового no spread.
Анализ нового no spread.
4 слова прочитал, а дальше??
 
Легенда форума
Статус
Оффлайн
Регистрация
10 Дек 2018
Сообщения
4,385
Реакции[?]
2,286
Поинты[?]
191K
Привет! У меня два вопроса:
  1. Как ты узнаёшь, какой сид выберет сервер, когда просчитываешь возможные сиды для каждого отдельного угла. Получается, что тебе известен алгоритм, по которому он просчитывается?
  2. Судя по тому, что при просчётах ты берёшь разные углы, после успешного совпадения на конкретном угле & тике тебе нужно вовремя установить правильный угол. Поэтому выходит ли так, что модель игрока будет фликать?
Если что — извиняюсь, я далёк от читкодинга, поэтому могу писать бред, но мне интересно, как это работает.
 
Разработчик
Статус
Оффлайн
Регистрация
1 Сен 2018
Сообщения
1,597
Реакции[?]
881
Поинты[?]
116K
Привет! У меня два вопроса:
  1. Как ты узнаёшь, какой сид выберет сервер, когда просчитываешь возможные сиды для каждого отдельного угла. Получается, что тебе известен алгоритм, по которому он просчитывается?
  2. Судя по тому, что при просчётах ты берёшь разные углы, после успешного совпадения на конкретном угле & тике тебе нужно вовремя установить правильный угол. Поэтому выходит ли так, что модель игрока будет фликать?
Если что — извиняюсь, я далёк от читкодинга, поэтому могу писать бред, но мне интересно, как это работает.
1. Ну да, он же и лежит в посте.
2. Нет
 
Keine panik!
Эксперт
Статус
Оффлайн
Регистрация
29 Апр 2020
Сообщения
812
Реакции[?]
417
Поинты[?]
49K
какой сид выберет сервер, когда просчитываешь возможные сиды для каждого отдельного угла. Получается, что тебе известен алгоритм, по которому он просчитывается
Алгоритм генерации сида теперь общий между клиентом и сервером.
Обе стороны знают углы и номер тика, из них они вычисляют хэш, который и станет общим сидом (упрощенно).
Клиент знает эти данные потому что он сам их генерирует, а сервер знает потому что они пришли от клиента.
 
Murasaki
Разработчик
Статус
Оффлайн
Регистрация
18 Мар 2020
Сообщения
445
Реакции[?]
875
Поинты[?]
205K
как хорошо, что лично сам сапдрагон расписал про носприд, благослови тебя тим энтериал
 
эксперт в майнкрафт апи
Read Only
Статус
Оффлайн
Регистрация
25 Янв 2023
Сообщения
676
Реакции[?]
284
Поинты[?]
22K
Вступление
Привет всем! Сегодня я хочу поделиться с вами информацией о том, почему снова появился no-spread и как он работает. Это действительно то, что изменит правила игры для всех

Изменения которые привели к его появлению
В ночь 9 ноября успешная компания Valve выпустила новое обновление для Counter-Strike 2. В этом обновлении они решили исправить проблемы с системой sub-tick. Одно из решений, к которому они пришли, было синхронизировать клиентское и серверный сид случайностей.

Изменения на клиенте
В этой части я применю реверс-инжиниринг Soufiw в знак уважения к исходному источнику.
После этого обновления, сид синхронизируется между клиентом и сервером с помощью хэширования углов обзора и текущего тика отрисовки.

C++:
uint32_t GetRandomSeed( const QAngle& angles, int player_render_tick )

{

#ifdef SERVER_DLL

    // sv_usercmd_custom_random_seed = false by default now.

    // m_nPredictionRandomSeed = cmd->random_seed;

    // m_nPredictionRandomSeedServer = sv_usercmd_custom_random_seed ? GetRngSeed() : cmd->random_seed;

    if ( m_nPredictionRandomSeedServer != m_nPredictionRandomSeed )

        return m_nPredictionRandomSeedServer;

#endif

    struct HashBuffer

    {

        float pitch;

        float yaw;

        int tick;

    };

    HashBuffer buffer;

    float pitch = AngleNormalize(angles.pitch);

    pitch += pitch;

    buffer.pitch = RoundFloat(pitch) * 0.5f;

    float yaw = AngleNormalize(angles.yaw);

    yaw += yaw;

    buffer.yaw = RoundFloat(yaw) * 0.5f;

    buffer.tick = player_render_tick;

    CSHA1 sha1;

    sha1.Reset(  );

    sha1.Update( &buffer, sizeof(buffer) );

    sha1.Final();

    return *reinterpret_cast<uint32_t*>(&sha1.m_digest[0]);

}
В итоге, создание no-spread кажется действительно простым: всего лишь нужно получить текущий сид и компенсировать его, как в 2015 году! Но, к сожалению, это не работает так. При изменении угла обзора, сид также меняется, что логично. Чтобы обойти это ограничение, я пришел к более простому и оригинальному решению.

Использование хаоса как основу для порядка
Если вы сейчас стоите, то вам лучше присесть, потому что это самое простое решение, которое можно придумать.

Предположим, у нас есть угол обзора и текущий тик отрисовки. Для компенсации разброса мы можем перебирать случайные углы, вычислять для них сид и вычислять финальный угол с учетом разброса.

В итоге, наш принцип действия будет выглядеть так:
  1. Генерируем случайный угол.
  2. Вычисляем сид для этого случайного угла и текущего тика.
  3. Используем полученный сид для моделирования выстрела с разбросом из текущего угла.
  4. Получаем финальный угол выстрела с учетом разброса.
  5. Если финальный угол достаточно близок к исходному (в пределах заданного порога), сохраняем найденный сид.
  6. Если нет, генерируем новый случайный угол и повторяем шаги с 3 по 6.
  7. Как только находим подходящий угол, используем его вместо реального.
  8. Для нового угла обзора повторяем поиск подходящего угла сначала.

Таким образом, мы перебираем случайные варианты, пока не найдем сид, которое компенсирует разброс для текущего угла.

Как же посчитать разброс?
Давайте сразу перейдем к коду, который отвечает за моделирование разброса. Я не буду углубляться в подробные объяснения, чтобы быстрее показать реализацию.

C++:
bool bForceMaxInaccuracy = weapon_debug_max_inaccuracy.GetBool();

bool bForceInaccuracyDirection = weapon_debug_inaccuracy_only_up.GetBool();

bool bWeaponAccuracyShotgunSpreadPatterns = weapon_accuracy_shotgun_spread_patterns.GetBool();


Math::RandomSeed(nRandomSeed + 1);


float flRadiusCurveDensity = Math::RandomFloat(0.0f, 1.0f);


if (nItemIndex == 64 && nMode == nSecondaryMode) {

  // R8 Revolver secondary fire

  flRadiusCurveDensity = 1.0f - flRadiusCurveDensity * flRadiusCurveDensity;

}

else if (nItemIndex == 28 && flRecoilIndex < 3) {

  // Negev Wild Beast

  for (int nIndex = 3; nIndex > flRecoilIndex; --nIndex) {

    flRadiusCurveDensity *= flRadiusCurveDensity;

  }

  flRadiusCurveDensity = 1.0f - flRadiusCurveDensity;

}


if (bForceMaxInaccuracy) {

  flRadiusCurveDensity = 1.0f;

}


float flTheta0 = Math::RandomFloat(0.0f, 2.0f * M_PI);

if (bForceInaccuracyDirection) {

  flTheta0 = M_PI * 0.5f;

}


if (bWeaponAccuracyShotgunSpreadPatterns)

{

    // Missing.

}


float flRadius0 = flRadiusCurveDensity * flInaccuracy;

float flX0 = flRadius0 * cosf(flTheta0);

float flY0 = flRadius0 * sinf(flTheta0);


float flSpreadCurveDensity = Math::RandomFloat(0.0f, 1.0f);


if (nItemIndex == 64 && nMode == nSecondaryMode) {

  // R8 Revolver secondary fire

  flSpreadCurveDensity = 1.0f - flSpreadCurveDensity * flSpreadCurveDensity;

}

else if (nItemIndex == 28 && flRecoilIndex < 3) {

  // Negev Wild Beast

  for (int nIndex = 3; nIndex > flRecoilIndex; --nIndex) {

    flSpreadCurveDensity *= flSpreadCurveDensity;

  }

  flSpreadCurveDensity = 1.0f - flSpreadCurveDensity;

}


if (bForceMaxInaccuracy) {

  flSpreadCurveDensity = 1.0f;

}


float flTheta1 = Math::RandomFloat(0.0f, 2.0f * M_PI);

if (bForceInaccuracyDirection) {

  flTheta1 = M_PI * 0.5f;

}


float flRadius1 = flSpreadCurveDensity * flSpread;

float flX1 = flRadius1 * cosf(flTheta1);

float flY1 = flRadius1 * sinf(flTheta1);


double flYResult = (double)(flY1 * flRadius0) +

               (double)(flY0 * flRadius1);


double flXResult = (double)(flX1 * flRadius0) +

               (double)(flX0 * flRadius1);
Итог
Сложив всю информацию, вы могли прийти к тому же результату, что и я. Возврат компенсации отдачи повлечёт серьёзные изменения в культуре HvH и читеров в целом. Сейчас я хочу продемонстрировать, что это действительно работает.


Давайте посмотрим, как это работает на практике:

Всем пока, еще встретимся!

Credits:
SapDragon - Theoretical research
Porches - Practical implementation
попадал в похожую ситуацию где с клиента на сервер передавался rdtsc от чего на сервере устанавливался common-seed на выстрел.
при этом
Код:
if (current_seed <= prev_seed) {
    // detect
}
при этом от seed высчитывались ~6+ значений, т.е сиды должны были давать последовательность 6+ даблов с минимальной погрешностью от 0.
результатом стало предварительное высчитывание огромного баффера сидов(100к раз(ну уж за игровую сессию 100к раз выстрелить ещё надо умудриться)).
делалось все на GPU шейдером из-за очевидной низкой производительности CPU.

ну а насчет новой эры...я так не думаю.вернут старый рассинхрон сидов клиент-сервера и лишитесь вы ноусприда.(почему это вообще допустилось - я хуй его знает..хотя
1700314252974.png
)
 
Последнее редактирование:
5 ночей на aim_ag_texture2
Эксперт
Статус
Оффлайн
Регистрация
6 Апр 2017
Сообщения
826
Реакции[?]
401
Поинты[?]
11K
это больше похоже на улучшенный просприд триггербот, который брутфорсит сид через углы. ноусприд это когда твой выстрел регает в чела....
это так но по сути похуй если всегда есть сид при котором сприд минимальный
вернут старый рассинхрон сидов клиент-сервера и лишитесь вы ноусприда.(почему это вообще допустилось - я хуй его знает..хотя
они скорее ченить другое придумают нежели вернут рассинхрон сидов, один хуй детектить носпреад не так уж и трудно........
 
эксперт в майнкрафт апи
Read Only
Статус
Оффлайн
Регистрация
25 Янв 2023
Сообщения
676
Реакции[?]
284
Поинты[?]
22K
Keine panik!
Эксперт
Статус
Оффлайн
Регистрация
29 Апр 2020
Сообщения
812
Реакции[?]
417
Поинты[?]
49K
что за половина числа пи откуда ты это взял?
Это из кода самой игры, при weapon_debug_inaccuracy_only_up направление сприда всегда будет идти вверх, PI = 180 градусов, половина будет +90 градусов, т.е. вверх.
 
Тьомчик
Участник
Статус
Оффлайн
Регистрация
30 Июн 2020
Сообщения
751
Реакции[?]
153
Поинты[?]
61K
Это из кода самой игры, при weapon_debug_inaccuracy_only_up направление сприда всегда будет идти вверх, PI = 180 градусов, половина будет +90 градусов, т.е. вверх.
спасибо, буду знать топ
 
Начинающий
Статус
Оффлайн
Регистрация
12 Июн 2023
Сообщения
35
Реакции[?]
10
Поинты[?]
11K
Ждём добавления носприд триггера в абсолютно каждую пасту
 
Участник
Статус
Оффлайн
Регистрация
28 Дек 2018
Сообщения
520
Реакции[?]
196
Поинты[?]
36K
Сверху Снизу