Детерминизм и воспроизводимость симуляций

Основы физического движка

Ammo.js является JavaScript-портом движка Bullet Physics, реализованного на C++ с помощью Emscripten. Он предоставляет реалистичную физическую симуляцию, включая столкновения, динамику твёрдых тел, констрейнты и мягкие тела. Для полноценного контроля над симуляцией важно понимать понятия детерминизма и воспроизводимости, так как физические расчёты часто подвержены численным погрешностям.

Детерминизм означает, что при одинаковых начальных условиях, одинаковых шагах интеграции и идентичных параметрах симуляции система выдаёт точно одинаковые результаты. В контексте Ammo.js это критично для сетевых игр, повторов и тестирования.

Численные интеграторы и шаг времени

Ammo.js использует интегратор явного типа, основанный на методах Эйлера и Verlet, для расчёта движения твёрдых тел. Основные параметры, влияющие на воспроизводимость:

  • timeStep — величина шага времени в функции stepSimulation.
  • maxSubSteps — максимальное количество внутренних шагов для сглаживания больших временных дельт.
  • fixedTimeStep — фиксированное время между внутренними шагами, определяющее частоту обновлений физики.
physicsWorld.stepSimulation(deltaTime, maxSubSteps, fixedTimeStep);

При использовании нефиксированного шага времени (deltaTime варьируется каждый кадр) результаты симуляции могут отличаться при повторных запусках, даже при одинаковых начальных условиях. Для гарантированного детерминизма рекомендуется использовать фиксированный шаг времени, например, 1/60 секунды.

Инициализация объектов и повторяемость

Каждое физическое тело в Ammo.js создаётся с помощью конструктора btRigidBody, которому передаются:

  • mass — масса объекта.
  • collisionShape — форма столкновения (btBoxShape, btSphereShape и др.).
  • position и rotation — начальная трансформация.
  • linearVelocity и angularVelocity — начальные скорости.

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

const startPos = new Ammo.btVector3(0, 10, 0);
const startRot = new Ammo.btQuaternion(0, 0, 0, 1);

Любая случайная генерация без фиксированного seed приведёт к различным симуляциям при повторном запуске.

Погрешности вычислений

Ammo.js работает с числами с плавающей запятой двойной точности, но в JavaScript они представляются как Number (64-битные IEEE 754). Это накладывает ограничения:

  • Мелкие отклонения на каждом шаге интеграции могут накапливаться.
  • Столкновения, особенно с констрейнтами или мягкими телами, подвержены нестабильности при больших скоростях или малых массах.

Для минимизации эффекта рекомендуется:

  • Использовать фиксированные шаги времени.
  • Ограничивать диапазон масс и скоростей.
  • Настраивать константы точности столкновений (collisionMargin, contactBreakingThreshold).

Констрейнты и детерминизм

Констрейнты (ограничения движения, например, btHingeConstraint или btPoint2PointConstraint) могут быть источником непредсказуемости. Их точность зависит от:

  • Параметра erp (error reduction parameter) — коэффициент исправления ошибки.
  • Параметра cfm (constraint force mixing) — коэффициент сглаживания силы констрейнта.
  • Частоты обновления шагов интегратора.

Неправильная комбинация этих параметров и переменного deltaTime приводит к разным результатам между запусками.

const hinge = new Ammo.btHingeConstraint(bodyA, bodyB, pivotA, pivotB, axisA, axisB, true);
hinge.setParam(Ammo.CONSTRAINT_ERP, 0.2, 0);
hinge.setParam(Ammo.CONSTRAINT_CFM, 0.0, 0);

Стабильность симуляции

Для улучшения воспроизводимости и предотвращения «скачков» тел:

  1. Использовать небольшие значения массы для легких объектов и адекватно масштабировать силы.
  2. Устанавливать sleepThresholds для тел, чтобы они стабилизировались при покое.
  3. Избегать слишком малых или слишком больших шагов интеграции, что вызывает нестабильные контакты.
rigidBody.setSleepingThresholds(0.1, 0.1);

Принципы сетевой синхронизации

В многопользовательских приложениях детерминизм Ammo.js позволяет:

  • Отправлять только начальные состояния и шаги управления игроками вместо полной симуляции.
  • Использовать lock-step для синхронизации всех клиентов.
  • Минимизировать сетевой трафик, избегая рассинхронизации объектов.

При этом необходимо фиксировать шаг интеграции и инициализацию всех объектов на всех клиентах.

Выводы по детерминизму

В Ammo.js воспроизводимость симуляций зависит от:

  • Использования фиксированного шага времени.
  • Полной инициализации всех объектов и параметров.
  • Контроля численных ошибок через настройки констрейнтов и столкновений.
  • Фиксированных значений случайных чисел при необходимости.

Следование этим принципам обеспечивает стабильное и предсказуемое поведение физической симуляции на любом устройстве и при любом повторном запуске.