diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aab59d3..1f4654c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,6 +38,12 @@ jobs: - name: Run unit tests run: yarn test --maxWorkers=2 --coverage + - name: Run fusionml C++ tests + run: | + sudo apt-get update + sudo apt-get install -y g++ zlib1g-dev + yarn test:fusion + build-library: runs-on: ubuntu-latest steps: @@ -87,7 +93,16 @@ jobs: - name: Finalize Android SDK if: env.turbo_cache_hit != 1 run: | - /bin/bash -c "yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses > /dev/null" + test -n "$ANDROID_HOME" + echo "sdk.dir=$ANDROID_HOME" > example/android/local.properties + yes | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --licenses > /dev/null + # RN 0.83 pins NDK 27.1; install it here so Gradle does not download a bad zip + for attempt in 1 2 3; do + yes | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --install "ndk;27.1.12297006" && break + echo "NDK install attempt ${attempt} failed; retrying..." + sleep $((attempt * 8)) + done + test -d "$ANDROID_HOME/ndk/27.1.12297006" - name: Cache Gradle if: env.turbo_cache_hit != 1 @@ -107,9 +122,10 @@ jobs: yarn turbo run build:android --cache-dir="${{ env.TURBO_CACHE_DIR }}" build-ios: - runs-on: macos-latest + # macos-latest is Xcode 26-only; RN 0.83 still builds with Xcode 16.x + runs-on: macos-15 env: - XCODE_VERSION: 16.2 + XCODE_VERSION: '16.4' TURBO_CACHE_DIR: .turbo/ios steps: - name: Checkout @@ -146,7 +162,7 @@ jobs: cd example bundle install bundle exec pod repo update --verbose - bundle exec pod install --project-directory=ios + bundle exec pod update --project-directory=ios - name: Build example for iOS run: | diff --git a/.nvmrc b/.nvmrc index 5f53e87..70f6554 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v20.19.0 +v20.19.4 diff --git a/Ahrs.podspec b/Ahrs.podspec index e72c286..4d81cad 100644 --- a/Ahrs.podspec +++ b/Ahrs.podspec @@ -28,7 +28,8 @@ Pod::Spec.new do |s| "fusionml/src/uNavINS.{h,cpp}", "fusionml/src/AltitudeCalculator.{h,cpp}", "fusionml/src/FlightPhaseDetector.{h,cpp}", - "fusionml/src/JsonRecorder.{h,cpp}" + "fusionml/src/JsonRecorder.{h,cpp}", + "fusionml/src/XYZgeomag.hpp" s.private_header_files = "ios/**/*.h", "fusionml/src/uNavINS.h", diff --git a/android/CMakeLists.txt b/android/CMakeLists.txt index 5afefb1..a46f9c9 100755 --- a/android/CMakeLists.txt +++ b/android/CMakeLists.txt @@ -35,7 +35,6 @@ add_library(ahrs SHARED ../fusionml/src/AltitudeCalculator.cpp ../fusionml/src/FlightPhaseDetector.cpp ../fusionml/src/JsonRecorder.cpp - "${RN_DIR}/ReactCommon/jsi/jsi/jsi.cpp" ) # Specify include directories diff --git a/android/jni/AhrsModuleJNI.cpp b/android/jni/AhrsModuleJNI.cpp index 52e2358..0100a83 100755 --- a/android/jni/AhrsModuleJNI.cpp +++ b/android/jni/AhrsModuleJNI.cpp @@ -1,47 +1,75 @@ #include -#include -#include -#include -#include #include -#include #include +#include +#include +#include +#include +#include -#include "../fusion/ekf.h" -#include "../fusion/ekf_core.h" -#include "../fusion/ekf_math.h" -#include "../fusion/ekf_flight_phase.h" +#include "AltitudeCalculator.h" +#include "FlightPhaseDetector.h" +#include "XYZgeomag.hpp" +#include "uNavINS.h" #define LOG_TAG "AhrsModuleJNI" #ifdef DEBUG #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) -#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) #else #define LOGI(...) ((void)0) -#define LOGE(...) ((void)0) #endif +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) -using namespace facebook::jsi; +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif -// Global EKF instance (using stack-allocated struct) -static ekf_t* g_ekf = nullptr; -static std::mutex g_ekf_mutex; +static const double RAD2DEG = 180.0 / M_PI; +static const double DEG2RAD = M_PI / 180.0; +static const float MIN_GROUND_SPEED_FOR_TRACK = 0.514444f; // 1 kt +static const float MIN_SPEED_FOR_FPV = 2.57222f; // 5 kt +static const double MIN_DECLINATION_UPDATE_M = 1852.0; -// Rotation enum matching iOS enum AhrsRotation { AHR_ROTATION_VERTICAL = 0, AHR_ROTATION_LEFT = 1, AHR_ROTATION_RIGHT = 2 }; -static AhrsRotation g_rotation = AHR_ROTATION_VERTICAL; -static bool g_ekf_attitude_initialized = false; -static uint64_t g_last_update_us = 0; +struct FilterContext { + uNavINS filter; + FlightPhaseDetector phase; + AhrsRotation rotation = AHR_ROTATION_VERTICAL; + uint64_t last_update_us = 0; + float qnh_hpa = 1013.25f; + float roll_offset_deg = 0.0f; + float pitch_offset_deg = 0.0f; + bool has_declination = false; + double last_decl_lat = 0.0; + double last_decl_lon = 0.0; + float declination_deg = 0.0f; + float bn = NAN; + float be = NAN; + float bd = NAN; + bool has_gps = false; + double last_lat_rad = 0.0; + double last_lon_rad = 0.0; + double last_alt_m = 0.0; + float last_track_deg = 0.0f; + bool has_track = false; + float last_body_ax = 0.0f; + float last_baro_hpa = -1.0f; +}; + +static std::mutex g_mutex; + +static FilterContext *ctxFrom(jlong ptr) { + return reinterpret_cast(ptr); +} -// Transform Android sensor readings to aviation body frame static void transformToBodyFrame(float android_x, float android_y, float android_z, AhrsRotation rotation, - float* out_x, float* out_y, float* out_z) { + float *out_x, float *out_y, float *out_z) { switch (rotation) { case AHR_ROTATION_LEFT: *out_x = -android_z; @@ -62,40 +90,77 @@ static void transformToBodyFrame(float android_x, float android_y, float android } } +static float wrap360(float deg) { + while (deg >= 360.0f) deg -= 360.0f; + while (deg < 0.0f) deg += 360.0f; + return deg; +} + +static double haversineMeters(double lat1, double lon1, double lat2, double lon2) { + const double R = 6378137.0; + double dLat = lat2 - lat1; + double dLon = lon2 - lon1; + double a = std::sin(dLat / 2.0) * std::sin(dLat / 2.0) + + std::cos(lat1) * std::cos(lat2) * std::sin(dLon / 2.0) * std::sin(dLon / 2.0); + return R * 2.0 * std::atan2(std::sqrt(a), std::sqrt(1.0 - a)); +} + +static float currentDecimalYear() { + time_t now = time(nullptr); + struct tm t{}; + gmtime_r(&now, &t); + return (float)(t.tm_year + 1900) + ((float)t.tm_yday / 365.25f); +} + +static void updateWmmIfNeeded(FilterContext *ctx, double lat_rad, double lon_rad, double alt_m) { + if (!std::isfinite(lat_rad) || !std::isfinite(lon_rad) || !std::isfinite(alt_m)) { + return; + } + if (std::fabs(lat_rad) < 1e-8 && std::fabs(lon_rad) < 1e-8) { + return; + } + bool need = !ctx->has_declination; + if (!need) { + need = haversineMeters(ctx->last_decl_lat, ctx->last_decl_lon, lat_rad, lon_rad) >= + MIN_DECLINATION_UPDATE_M; + } + if (!need) { + return; + } + double lat_deg = lat_rad * RAD2DEG; + double lon_deg = lon_rad * RAD2DEG; + float year = currentDecimalYear(); + geomag::Vector position = geomag::geodetic2ecef((float)lat_deg, (float)lon_deg, (float)alt_m); + geomag::Vector magField = geomag::GeoMag(year, position, geomag::WMM2025); + geomag::Elements elements = geomag::magField2Elements(magField, (float)lat_deg, (float)lon_deg); + ctx->declination_deg = elements.declination; + ctx->bn = elements.north; + ctx->be = elements.east; + ctx->bd = elements.down; + ctx->last_decl_lat = lat_rad; + ctx->last_decl_lon = lon_rad; + ctx->has_declination = true; + LOGI("WMM declination %.1f deg N=%.0f E=%.0f D=%.0f nT", + ctx->declination_deg, ctx->bn, ctx->be, ctx->bd); +} + extern "C" JNIEXPORT jlong JNICALL Java_com_ahrs_AhrsModule_initAhrs(JNIEnv *env, jobject thiz) { - std::lock_guard lock(g_ekf_mutex); - - if (g_ekf) { - delete g_ekf; - } - - g_ekf = new ekf_t(); - if (!g_ekf) { - LOGE("Failed to allocate EKF"); + std::lock_guard lock(g_mutex); + auto *ctx = new (std::nothrow) FilterContext(); + if (!ctx) { + LOGE("Failed to allocate filter context"); return 0; } - - ekf_init(g_ekf); - g_ekf_attitude_initialized = false; - g_last_update_us = 0; - - LOGI("EKF initialized successfully"); - return reinterpret_cast(g_ekf); + LOGI("uNavINS initialized"); + return reinterpret_cast(ctx); } extern "C" JNIEXPORT void JNICALL -Java_com_ahrs_AhrsModule_destroyAhrs(JNIEnv *env, jobject thiz) { - std::lock_guard lock(g_ekf_mutex); - - if (g_ekf) { - delete g_ekf; - g_ekf = nullptr; - } - - g_ekf_attitude_initialized = false; - g_last_update_us = 0; - LOGI("EKF destroyed"); +Java_com_ahrs_AhrsModule_destroyAhrs(JNIEnv *env, jobject thiz, jlong ekfPtr) { + std::lock_guard lock(g_mutex); + delete ctxFrom(ekfPtr); + LOGI("uNavINS destroyed"); } extern "C" JNIEXPORT void JNICALL @@ -107,343 +172,256 @@ Java_com_ahrs_AhrsModule_updateAhrs(JNIEnv *env, jobject thiz, jfloatArray mag, jfloatArray gps, jfloatArray baro) { - if (!ekfPtr) return; - - ekf_t* ekf = reinterpret_cast(ekfPtr); - - // Extract sensor data - jfloat* accelData = env->GetFloatArrayElements(accel, nullptr); - jfloat* gyroData = env->GetFloatArrayElements(gyro, nullptr); - jfloat* magData = env->GetFloatArrayElements(mag, nullptr); - - // Transform accelerometer (Android gives m/s², convert to body frame) - float acc_x, acc_y, acc_z; - transformToBodyFrame(accelData[0], accelData[1], accelData[2], - g_rotation, &acc_x, &acc_y, &acc_z); - - vector3_t accel_vec; - accel_vec.x = acc_x; - accel_vec.y = acc_y; - accel_vec.z = acc_z; - accel_vec.valid = true; - accel_vec.timestamp_us = timestampUs; - - // Transform gyroscope (Android gives rad/s, transform to body frame) - float gyro_x, gyro_y, gyro_z; - transformToBodyFrame(gyroData[0], gyroData[1], gyroData[2], - g_rotation, &gyro_x, &gyro_y, &gyro_z); - - vector3_t gyro_vec; - gyro_vec.x = gyro_x; - gyro_vec.y = gyro_y; - gyro_vec.z = gyro_z; - gyro_vec.valid = true; - gyro_vec.timestamp_us = timestampUs; - - // Transform magnetometer (Android gives µT, normalize and transform) - float mag_x, mag_y, mag_z; - transformToBodyFrame(magData[0], magData[1], magData[2], - g_rotation, &mag_x, &mag_y, &mag_z); - - // Normalize magnetometer - float mag_norm = sqrtf(mag_x*mag_x + mag_y*mag_y + mag_z*mag_z); - vector3_t mag_vec; - if (mag_norm > 0.01f) { - mag_vec.x = mag_x / mag_norm; - mag_vec.y = mag_y / mag_norm; - mag_vec.z = mag_z / mag_norm; - mag_vec.valid = true; - } else { - mag_vec.valid = false; - mag_vec.x = 0; - mag_vec.y = 0; - mag_vec.z = 0; + FilterContext *ctx = ctxFrom(ekfPtr); + if (!ctx || !accel || !gyro || !mag) { + return; } - mag_vec.timestamp_us = timestampUs; - - // Process GPS if available - gps_position_t* gps_pos = nullptr; - gps_position_t gps_data = {0}; + + jfloat *accelData = env->GetFloatArrayElements(accel, nullptr); + jfloat *gyroData = env->GetFloatArrayElements(gyro, nullptr); + jfloat *magData = env->GetFloatArrayElements(mag, nullptr); + if (!accelData || !gyroData || !magData) { + if (accelData) env->ReleaseFloatArrayElements(accel, accelData, JNI_ABORT); + if (gyroData) env->ReleaseFloatArrayElements(gyro, gyroData, JNI_ABORT); + if (magData) env->ReleaseFloatArrayElements(mag, magData, JNI_ABORT); + return; + } + + float ax, ay, az, p, q, r, hx, hy, hz; + transformToBodyFrame(accelData[0], accelData[1], accelData[2], ctx->rotation, &ax, &ay, &az); + transformToBodyFrame(gyroData[0], gyroData[1], gyroData[2], ctx->rotation, &p, &q, &r); + transformToBodyFrame(magData[0], magData[1], magData[2], ctx->rotation, &hx, &hy, &hz); + + env->ReleaseFloatArrayElements(accel, accelData, JNI_ABORT); + env->ReleaseFloatArrayElements(gyro, gyroData, JNI_ABORT); + env->ReleaseFloatArrayElements(mag, magData, JNI_ABORT); + + double vn = 0.0, ve = 0.0, vd = 0.0; + float hacc = -1.0f, vacc = -1.0f, sacc = -1.0f; + unsigned long tow = 0; if (gps) { jsize gpsLen = env->GetArrayLength(gps); - if (gpsLen >= 9) { - jfloat* gpsData = env->GetFloatArrayElements(gps, nullptr); - // GPS array format: [lat, lon, alt, vel_n, vel_e, vel_d, hdop, num_sats, valid] - if (gpsData[8] > 0.5f) { // valid flag - gps_data.lat_deg = gpsData[0]; - gps_data.lon_deg = gpsData[1]; - gps_data.alt_m = gpsData[2]; - // Note: New API computes velocity from track/speed, but we can set reference - // For now, we'll set the reference and let the EKF compute velocity - if (!ekf->gps_ref_init) { - ekf_set_gps_reference(ekf, gpsData[0], gpsData[1], gpsData[2]); - } - // Compute track and speed from velocity components - float vel_n = gpsData[3]; - float vel_e = gpsData[4]; - float speed = sqrtf(vel_n*vel_n + vel_e*vel_e); - float track = atan2f(vel_e, vel_n) * 180.0f / M_PI; - if (track < 0.0f) track += 360.0f; - - gps_data.track_deg = track; - gps_data.speed_ms = speed; - gps_data.vs_ms = -gpsData[5]; // Down is positive in NED, but vs_ms is positive up - gps_data.valid = true; - gps_data.timestamp_us = timestampUs; - gps_pos = &gps_data; + if (gpsLen >= 10) { + jfloat *gpsData = env->GetFloatArrayElements(gps, nullptr); + if (gpsData && gpsData[9] > 0.5f) { + ctx->last_lat_rad = gpsData[0] * DEG2RAD; + ctx->last_lon_rad = gpsData[1] * DEG2RAD; + ctx->last_alt_m = gpsData[2]; + vn = gpsData[3]; + ve = gpsData[4]; + vd = gpsData[5]; + hacc = gpsData[6]; + vacc = gpsData[7]; + sacc = gpsData[8]; + ctx->has_gps = true; + tow = (unsigned long)(timestampUs / 1000); + updateWmmIfNeeded(ctx, ctx->last_lat_rad, ctx->last_lon_rad, ctx->last_alt_m); } - env->ReleaseFloatArrayElements(gps, gpsData, JNI_ABORT); + if (gpsData) env->ReleaseFloatArrayElements(gps, gpsData, JNI_ABORT); } } - - // Process barometer if available - baro_pressure_t* baro_ptr = nullptr; - baro_pressure_t baro_data = {0}; + + float baro_hpa = -1.0f; if (baro) { jsize baroLen = env->GetArrayLength(baro); if (baroLen >= 3) { - jfloat* baroData = env->GetFloatArrayElements(baro, nullptr); - // Baro array format: [pressure_hpa, temperature_c, valid] - if (baroData[2] > 0.5f) { // valid flag - baro_data.pressure_hpa = baroData[0]; - baro_data.valid = true; - baro_data.timestamp_us = timestampUs; - baro_ptr = &baro_data; + jfloat *baroData = env->GetFloatArrayElements(baro, nullptr); + if (baroData && baroData[2] > 0.5f && baroData[0] > 0.0f) { + baro_hpa = baroData[0]; + ctx->last_baro_hpa = baro_hpa; } - env->ReleaseFloatArrayElements(baro, baroData, JNI_ABORT); - } - } - - // Calculate dt - float dt = 0.016f; // Default 60Hz - if (g_last_update_us > 0) { - dt = (timestampUs - g_last_update_us) / 1000000.0f; - if (dt <= 0.0f || dt > 1.0f) { - dt = 0.016f; // Clamp to reasonable values + if (baroData) env->ReleaseFloatArrayElements(baro, baroData, JNI_ABORT); } } - g_last_update_us = timestampUs; - - // Update EKF - std::lock_guard lock(g_ekf_mutex); - ekf_update(ekf, &gyro_vec, &accel_vec, &mag_vec, gps_pos, baro_ptr, dt); - - if (!g_ekf_attitude_initialized && ekf->initialized) { - g_ekf_attitude_initialized = true; - LOGI("EKF attitude initialized"); + + double dt = 0.0; + if (ctx->last_update_us > 0) { + dt = (double)(timestampUs - (jlong)ctx->last_update_us) / 1e6; + if (dt < 1e-4) dt = 1e-4; + if (dt > 0.2) dt = 0.2; } - - env->ReleaseFloatArrayElements(accel, accelData, JNI_ABORT); - env->ReleaseFloatArrayElements(gyro, gyroData, JNI_ABORT); - env->ReleaseFloatArrayElements(mag, magData, JNI_ABORT); + ctx->last_update_us = (uint64_t)timestampUs; + ctx->last_body_ax = ax; + + std::lock_guard lock(g_mutex); + ctx->filter.update(dt, tow, vn, ve, vd, ctx->last_lat_rad, ctx->last_lon_rad, + ctx->last_alt_m, p, q, r, ax, ay, az, hx, hy, hz, + ctx->bn, ctx->be, ctx->bd, hacc, vacc, sacc, + baro_hpa, ctx->qnh_hpa); } extern "C" JNIEXPORT void JNICALL Java_com_ahrs_AhrsModule_resetAhrs(JNIEnv *env, jobject thiz, jlong ekfPtr) { - if (!ekfPtr) return; - - ekf_t* ekf = reinterpret_cast(ekfPtr); - std::lock_guard lock(g_ekf_mutex); - - // Re-initialize the EKF - ekf_init(ekf); - g_ekf_attitude_initialized = false; - g_last_update_us = 0; - + FilterContext *ctx = ctxFrom(ekfPtr); + if (!ctx) return; + std::lock_guard lock(g_mutex); + ctx->filter.~uNavINS(); + new (&ctx->filter) uNavINS(); + ctx->phase.reset(); + ctx->last_update_us = 0; + ctx->roll_offset_deg = 0.0f; + ctx->pitch_offset_deg = 0.0f; + ctx->has_gps = false; + ctx->has_track = false; LOGI("AHRS reset"); } extern "C" JNIEXPORT void JNICALL Java_com_ahrs_AhrsModule_zeroAhrs(JNIEnv *env, jobject thiz, jlong ekfPtr) { - if (!ekfPtr) return; - - ekf_t* ekf = reinterpret_cast(ekfPtr); - std::lock_guard lock(g_ekf_mutex); - - ekf_zero_attitude(ekf); - - LOGI("AHRS leveled"); + FilterContext *ctx = ctxFrom(ekfPtr); + if (!ctx) return; + std::lock_guard lock(g_mutex); + ctx->roll_offset_deg = ctx->filter.getRoll_rad() * (float)RAD2DEG; + ctx->pitch_offset_deg = ctx->filter.getPitch_rad() * (float)RAD2DEG; + LOGI("AHRS leveled (roll offset %.2f pitch offset %.2f)", + ctx->roll_offset_deg, ctx->pitch_offset_deg); } extern "C" JNIEXPORT void JNICALL -Java_com_ahrs_AhrsModule_setAhrsRotation(JNIEnv *env, jobject thiz, jint rotation) { - g_rotation = static_cast(rotation); - g_ekf_attitude_initialized = false; // Force re-initialization +Java_com_ahrs_AhrsModule_setAhrsRotation(JNIEnv *env, jobject thiz, jlong ekfPtr, jint rotation) { + FilterContext *ctx = ctxFrom(ekfPtr); + if (!ctx) return; + std::lock_guard lock(g_mutex); + ctx->rotation = static_cast(rotation); LOGI("Rotation set to %d", rotation); } extern "C" JNIEXPORT void JNICALL -Java_com_ahrs_AhrsModule_setMagneticDeclination(JNIEnv *env, jobject thiz, jlong ekfPtr, jfloat declination) { - if (!ekfPtr) return; - ekf_t* ekf = reinterpret_cast(ekfPtr); - std::lock_guard lock(g_ekf_mutex); - ekf_set_magnetic_declination(ekf, declination); - LOGI("Magnetic declination set to %.1f°", declination); +Java_com_ahrs_AhrsModule_setMagneticDeclination(JNIEnv *env, jobject thiz, + jlong ekfPtr, jfloat declination) { + FilterContext *ctx = ctxFrom(ekfPtr); + if (!ctx) return; + std::lock_guard lock(g_mutex); + ctx->declination_deg = declination; + ctx->has_declination = true; } extern "C" JNIEXPORT void JNICALL Java_com_ahrs_AhrsModule_setQNH(JNIEnv *env, jobject thiz, jlong ekfPtr, jfloat qnh) { - if (!ekfPtr) return; - ekf_t* ekf = reinterpret_cast(ekfPtr); - std::lock_guard lock(g_ekf_mutex); - ekf_set_local_pressure(ekf, qnh); + FilterContext *ctx = ctxFrom(ekfPtr); + if (!ctx) return; + std::lock_guard lock(g_mutex); + ctx->qnh_hpa = qnh; LOGI("QNH set to %.2f hPa", qnh); } - extern "C" JNIEXPORT jdoubleArray JNICALL Java_com_ahrs_AhrsModule_getGpsPosition(JNIEnv *env, jobject thiz, jlong ekfPtr) { jdoubleArray result = env->NewDoubleArray(2); - if (!ekfPtr || !result) return nullptr; - - ekf_t* ekf = reinterpret_cast(ekfPtr); - std::lock_guard lock(g_ekf_mutex); - + if (!result) return nullptr; jdouble data[2] = {0.0, 0.0}; - - if (ekf->gps_ref_init) { - float pos_n, pos_e, pos_d; - ekf_get_position(ekf, &pos_n, &pos_e, &pos_d); - - // Convert NED (meters) to lat/lon offset - // 1 degree latitude ≈ 111,320 meters - // 1 degree longitude ≈ 111,320 * cos(latitude) meters - const double PI = 3.14159265358979323846; - double lat_offset = pos_n / 111320.0; - double lon_offset = pos_e / (111320.0 * std::cos(ekf->gps_ref_lat * PI / 180.0)); - data[0] = ekf->gps_ref_lat + lat_offset; - data[1] = ekf->gps_ref_lon + lon_offset; + FilterContext *ctx = ctxFrom(ekfPtr); + if (ctx) { + std::lock_guard lock(g_mutex); + if (ctx->filter.isInitialized()) { + data[0] = ctx->filter.getLatitude_rad() * RAD2DEG; + data[1] = ctx->filter.getLongitude_rad() * RAD2DEG; + } } - env->SetDoubleArrayRegion(result, 0, 2, data); return result; } extern "C" JNIEXPORT jboolean JNICALL Java_com_ahrs_AhrsModule_isPositionReliable(JNIEnv *env, jobject thiz, jlong ekfPtr) { - if (!ekfPtr) return JNI_FALSE; - - ekf_t* ekf = reinterpret_cast(ekfPtr); - std::lock_guard lock(g_ekf_mutex); - - // Position is reliable if GPS reference is set and filter is initialized - return (ekf->gps_ref_init && ekf->initialized) ? JNI_TRUE : JNI_FALSE; + FilterContext *ctx = ctxFrom(ekfPtr); + if (!ctx) return JNI_FALSE; + std::lock_guard lock(g_mutex); + return (ctx->has_gps && ctx->filter.isInitialized()) ? JNI_TRUE : JNI_FALSE; } extern "C" JNIEXPORT jfloatArray JNICALL Java_com_ahrs_AhrsModule_getAhrsOutput(JNIEnv *env, jobject thiz, jlong ekfPtr) { - jfloatArray result = env->NewFloatArray(22); - if (!ekfPtr || !result) return nullptr; - - ekf_t* ekf = reinterpret_cast(ekfPtr); - - std::lock_guard lock(g_ekf_mutex); - - // Get Euler angles - float roll_deg, pitch_deg, yaw_deg; - ekf_get_euler(ekf, &roll_deg, &pitch_deg, &yaw_deg); - - // Get velocity - float vel_n, vel_e, vel_d; - ekf_get_velocity(ekf, &vel_n, &vel_e, &vel_d); - - // Get position - float pos_n, pos_e, pos_d; - ekf_get_position(ekf, &pos_n, &pos_e, &pos_d); - - // Get barometer altitudes (QNE and QNH) - float raw_pressure, filtered_pressure, alt_qne, alt_qnh, baro_vel_d; - ekf_get_baro(ekf, &raw_pressure, &filtered_pressure, &alt_qne, &alt_qnh, &baro_vel_d); - - // Get all three altitudes (GPS, QNE, QNH) - float gps_alt = 0.0f; - ekf_get_altitudes(ekf, &gps_alt, &alt_qne, &alt_qnh); - - // Compute derived values - float horizontal_speed = sqrtf(vel_n*vel_n + vel_e*vel_e); - float total_speed = sqrtf(vel_n*vel_n + vel_e*vel_e + vel_d*vel_d); - float track_angle = atan2f(vel_e, vel_n) * 180.0f / M_PI; - if (track_angle < 0.0f) track_angle += 360.0f; - - // Calculate horizontal flight path angle (sideslip/crab angle) from velocity in body frame - // Transform NED velocity to body frame using Euler angles - float roll_rad = roll_deg * M_PI / 180.0f; - float pitch_rad = pitch_deg * M_PI / 180.0f; - float yaw_rad = yaw_deg * M_PI / 180.0f; - - // Build rotation matrix from NED to body frame (Euler ZYX convention) - float cr = cosf(roll_rad); - float sr = sinf(roll_rad); - float cp = cosf(pitch_rad); - float sp = sinf(pitch_rad); - float cy = cosf(yaw_rad); - float sy = sinf(yaw_rad); - - // DCM from NED to body: R = Rz(yaw) * Ry(pitch) * Rx(roll) - // Forward (x): cy*cp, cy*sp*sr - sy*cr, cy*sp*cr + sy*sr - // Right (y): sy*cp, sy*sp*sr + cy*cr, sy*sp*cr - cy*sr - // Down (z): -sp, cp*sr, cp*cr - float vx_body = (cy*cp) * vel_n + (cy*sp*sr - sy*cr) * vel_e + (cy*sp*cr + sy*sr) * vel_d; - float vy_body = (sy*cp) * vel_n + (sy*sp*sr + cy*cr) * vel_e + (sy*sp*cr - cy*sr) * vel_d; - - float horizontal_speed_body = sqrtf(vx_body*vx_body + vy_body*vy_body); - - // Flight path vector is only meaningful when moving - // Threshold: 1.0 m/s - below this, FPA calculations are unreliable - const float MIN_SPEED_FOR_FPV = 1.0f; - - // Calculate flight path angle (vertical) - only if moving faster than 1 m/s - float flight_path_angle = 0.0f; - if (horizontal_speed >= MIN_SPEED_FOR_FPV) { - flight_path_angle = atan2f(-vel_d, horizontal_speed) * 180.0f / M_PI; + // [roll, pitch, heading, fpa, hfpa, groundTrack, groundSpeed, + // altitude, qne, qnh, vs, vn, ve, vd, + // phase, phaseConf, attitudeValid, altitudeValid, positionValid, phaseValid, + // health, atRest, zupt, declination] + jfloatArray result = env->NewFloatArray(24); + if (!result) return nullptr; + jfloat data[24] = {0}; + FilterContext *ctx = ctxFrom(ekfPtr); + if (!ctx) { + env->SetFloatArrayRegion(result, 0, 24, data); + return result; } - - // Calculate horizontal flight path angle - only if moving faster than 1 m/s - float horizontal_flight_path_angle = 0.0f; - if (horizontal_speed_body >= 0.1f && horizontal_speed >= MIN_SPEED_FOR_FPV) { - horizontal_flight_path_angle = atan2f(vy_body, vx_body) * 180.0f / M_PI; + + std::lock_guard lock(g_mutex); + float roll = ctx->filter.getRoll_rad() * (float)RAD2DEG - ctx->roll_offset_deg; + float pitch = ctx->filter.getPitch_rad() * (float)RAD2DEG - ctx->pitch_offset_deg; + float heading = wrap360(ctx->filter.getHeading_rad() * (float)RAD2DEG); + double vel_n = ctx->filter.getVelNorth_ms(); + double vel_e = ctx->filter.getVelEast_ms(); + double vel_d = ctx->filter.getVelDown_ms(); + float ground_speed = std::sqrt((float)(vel_n * vel_n + vel_e * vel_e)); + + float fpa = 0.0f; + float hfpa = 0.0f; + if (ground_speed >= MIN_SPEED_FOR_FPV) { + fpa = ctx->filter.getFlightPathAngle_rad() * (float)RAD2DEG; + hfpa = ctx->filter.getHorizontalFlightPathAngle_rad() * (float)RAD2DEG; } - - // Get current timestamp for flight phase confidence/validity checks - uint64_t timestamp_us = (uint64_t)(std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count()); - - // Get flight phase (already updated in main filter loop) - flight_phase_t flight_phase = ekf->flight_phase_state.current_phase; - float flight_phase_confidence = ekf_flight_phase_get_confidence(&ekf->flight_phase_state, timestamp_us); - bool flight_phase_valid = ekf_flight_phase_is_valid(&ekf->flight_phase_state, timestamp_us); - - // Output array: [roll, pitch, heading, flightPathAngle, horizontalFlightPathAngle, trackAngle, - // horizontalSpeed, totalSpeed, altitude, altitudeQNE, altitudeQNH, - // verticalSpeed, - // velocityNorth, velocityEast, velocityDown, - // flightPhase, flightPhaseConfidence, - // attitudeValid, altitudeValid, positionValid, - // flightPhaseValid] - jfloat data[21] = { - roll_deg, // 0: roll - pitch_deg, // 1: pitch - yaw_deg, // 2: heading (magnetic) - flight_path_angle, // 3: flightPathAngle (0 if speed < 1 m/s) - horizontal_flight_path_angle, // 4: horizontalFlightPathAngle (0 if speed < 1 m/s) - track_angle, // 5: trackAngle - horizontal_speed, // 6: horizontalSpeed - total_speed, // 7: totalSpeed - gps_alt, // 8: altitude (GPS MSL) - alt_qne, // 9: altitudeQNE (barometric QNE, standard atmosphere) - alt_qnh, // 10: altitudeQNH (barometric QNH, local pressure) - -vel_d, // 11: verticalSpeed (positive up) - vel_n, // 12: velocityNorth - vel_e, // 13: velocityEast - vel_d, // 14: velocityDown - (float)(int)flight_phase, // 15: flightPhase - flight_phase_confidence, // 16: flightPhaseConfidence - ekf->initialized ? 1.0f : 0.0f, // 17: attitudeValid - ekf->baro_filter_init ? 1.0f : 0.0f, // 18: altitudeValid - ekf->gps_ref_init ? 1.0f : 0.0f, // 19: positionValid - flight_phase_valid ? 1.0f : 0.0f // 20: flightPhaseValid - }; - - env->SetFloatArrayRegion(result, 0, 21, data); + + float track; + if (ground_speed >= MIN_GROUND_SPEED_FOR_TRACK) { + track = wrap360(ctx->filter.getGroundTrack_rad() * (float)RAD2DEG); + ctx->last_track_deg = track; + ctx->has_track = true; + } else if (ctx->has_track) { + track = ctx->last_track_deg; + } else { + track = heading; + ctx->last_track_deg = track; + ctx->has_track = true; + } + + float altitude = (float)ctx->filter.getAltitude_m(); + float qne = altitude; + float qnh_alt = altitude; + if (ctx->last_baro_hpa > 0.0f) { + float qne_m = AltitudeCalculator::calculateQNE_m(ctx->last_baro_hpa); + if (std::isfinite(qne_m)) qne = qne_m; + float qnh_m = AltitudeCalculator::calculateQNH_m(ctx->last_baro_hpa, ctx->qnh_hpa); + if (std::isfinite(qnh_m)) qnh_alt = qnh_m; + } + + float lat_deg = ctx->filter.getLatitude_rad() * (float)RAD2DEG; + float lon_deg = ctx->filter.getLongitude_rad() * (float)RAD2DEG; + int health = ctx->filter.getHealthStatus(); + bool initialized = ctx->filter.isInitialized(); + bool attitude_valid = initialized && health < 3 && + std::isfinite(roll) && std::isfinite(pitch) && std::isfinite(heading); + + if (ctx->has_gps) { + ctx->phase.update(altitude, (float)(-vel_d), ground_speed, lat_deg, lon_deg, + ctx->last_update_us, std::numeric_limits::quiet_NaN(), + ctx->last_body_ax); + } + + data[0] = roll; + data[1] = pitch; + data[2] = heading; + data[3] = fpa; + data[4] = hfpa; + data[5] = track; + data[6] = ground_speed; + data[7] = altitude; + data[8] = qne; + data[9] = qnh_alt; + data[10] = (float)(-vel_d); + data[11] = (float)vel_n; + data[12] = (float)vel_e; + data[13] = (float)vel_d; + data[14] = (float)ctx->phase.getFlightPhase(); + data[15] = ctx->phase.getConfidence(); + data[16] = attitude_valid ? 1.0f : 0.0f; + data[17] = (ctx->has_gps) ? 1.0f : 0.0f; + data[18] = ctx->has_gps ? 1.0f : 0.0f; + data[19] = ctx->phase.isValid() ? 1.0f : 0.0f; + data[20] = (float)health; + data[21] = ctx->filter.isAtRest() ? 1.0f : 0.0f; + data[22] = ctx->filter.isZuptActive() ? 1.0f : 0.0f; + data[23] = ctx->declination_deg; + + env->SetFloatArrayRegion(result, 0, 24, data); return result; } diff --git a/android/src/main/java/com/ahrs/AhrsModule.kt b/android/src/main/java/com/ahrs/AhrsModule.kt index 8eff50c..5b261f5 100755 --- a/android/src/main/java/com/ahrs/AhrsModule.kt +++ b/android/src/main/java/com/ahrs/AhrsModule.kt @@ -63,7 +63,7 @@ class AhrsModule(reactContext: ReactApplicationContext) : // JNI functions private external fun initAhrs(): Long - private external fun destroyAhrs() + private external fun destroyAhrs(ekfPtr: Long) private external fun updateAhrs( ekfPtr: Long, timestampUs: Long, @@ -75,8 +75,7 @@ class AhrsModule(reactContext: ReactApplicationContext) : ) private external fun resetAhrs(ekfPtr: Long) private external fun zeroAhrs(ekfPtr: Long) - private external fun setAhrsRotation(rotation: Int) - private external fun setMagneticDeclination(ekfPtr: Long, declination: Float) + private external fun setAhrsRotation(ekfPtr: Long, rotation: Int) private external fun setQNH(ekfPtr: Long, qnh: Float) private external fun getGpsPosition(ekfPtr: Long): DoubleArray private external fun isPositionReliable(ekfPtr: Long): Boolean @@ -129,20 +128,51 @@ class AhrsModule(reactContext: ReactApplicationContext) : private var xplaneHost: String? = null private var xplaneWasPaused: Boolean = false // Previous pause state for transition detection - private fun headingOffsetDeg(): Float { - return when (rotation) { - 1 -> 0.0f // Left: portrait offset (-90°) + 90° CW - 2 -> -180.0f // Right: portrait offset (-90°) - 90° CCW - else -> -90.0f // Vertical: portrait offset - } - } - private fun normalizeHeadingDeg(heading: Float): Float { var result = heading % 360.0f if (result < 0f) result += 360.0f return result } + private fun emitAhrsMap(output: FloatArray) { + if (output.size < 24) return + val map = Arguments.createMap() + map.putDouble("roll", output[0].toDouble()) + map.putDouble("pitch", output[1].toDouble()) + map.putDouble("heading", normalizeHeadingDeg(output[2]).toDouble()) + map.putDouble("magneticDeclination", output[23].toDouble()) + map.putDouble("groundTrack", normalizeHeadingDeg(output[5]).toDouble()) + map.putDouble("groundSpeed", output[6].toDouble()) + map.putDouble("flightPathAngle", output[3].toDouble()) + map.putDouble("horizontalFlightPathAngle", output[4].toDouble()) + map.putDouble("altitude", output[7].toDouble()) + map.putDouble("altitudeQNE", output[8].toDouble()) + map.putDouble("altitudeQNH", output[9].toDouble()) + map.putDouble("verticalSpeed", output[10].toDouble()) + val baroPressure = synchronized(baroLock) { + baroData?.getOrNull(0)?.toDouble() ?: 0.0 + } + map.putDouble("barometricPressure", baroPressure) + map.putDouble("velocityNorth", output[11].toDouble()) + map.putDouble("velocityEast", output[12].toDouble()) + map.putDouble("velocityDown", output[13].toDouble()) + val gpsPos = getGpsPosition(ekfPtr) + if (gpsPos != null && (gpsPos[0] != 0.0 || gpsPos[1] != 0.0)) { + map.putDouble("latitude", gpsPos[0]) + map.putDouble("longitude", gpsPos[1]) + } + map.putInt("flightPhase", output[14].toInt()) + map.putDouble("flightPhaseConfidence", output[15].toDouble()) + map.putBoolean("attitudeValid", output[16] > 0.5f) + map.putBoolean("altitudeValid", output[17] > 0.5f) + map.putBoolean("positionValid", output[18] > 0.5f) + map.putBoolean("flightPhaseValid", output[19] > 0.5f) + map.putInt("filterHealthStatus", output[20].toInt()) + map.putBoolean("atRest", output[21] > 0.5f) + map.putBoolean("zuptActive", output[22] > 0.5f) + emitOnAhrsUpdate(map) + } + override fun getName(): String = NAME /** @@ -166,7 +196,7 @@ class AhrsModule(reactContext: ReactApplicationContext) : disconnectFromXPlane() stopAhrs() if (ekfPtr != 0L) { - destroyAhrs() + destroyAhrs(ekfPtr) ekfPtr = 0L } } @@ -332,7 +362,9 @@ class AhrsModule(reactContext: ReactApplicationContext) : "right", "landscape_right", "landscaperight" -> 2 else -> 0 } - setAhrsRotation(rotation) + if (ekfPtr != 0L) { + setAhrsRotation(ekfPtr, rotation) + } if (oldRotation != rotation) { ekfAttitudeInitialized = false @@ -357,20 +389,28 @@ class AhrsModule(reactContext: ReactApplicationContext) : promise.resolve(hasGyro && hasAccel && hasMag) } - /** - * Sets magnetic declination for true heading calculation - * - * Magnetic declination is the angle between magnetic north and true north. - * Critical for navigation - converts magnetic heading to true heading. - * - * @param declination Declination in degrees - * Positive = east (magnetic north is east of true north) - * Negative = west (magnetic north is west of true north) - */ - override fun setMagneticDeclination(declination: Double) { - if (ekfPtr == 0L) return - setMagneticDeclination(ekfPtr, declination.toFloat()) - logI(TAG, "Magnetic declination set to ${declination}°") + override fun startRecording() { + logW(TAG, "Recording is not implemented on Android yet") + } + + override fun stopRecording() { + logW(TAG, "Recording is not implemented on Android yet") + } + + override fun playbackRecording(filename: String) { + logW(TAG, "Playback is not implemented on Android yet") + } + + override fun stopPlayback() { + logW(TAG, "Playback is not implemented on Android yet") + } + + override fun getRecordingFiles(promise: Promise) { + promise.resolve(Arguments.createArray()) + } + + override fun deleteRecording(filename: String) { + logW(TAG, "Recording delete is not implemented on Android yet") } /** @@ -626,8 +666,9 @@ class AhrsModule(reactContext: ReactApplicationContext) : gps = floatArrayOf( lat, lon, alt, velN, velE, velD, - 1.0f, // hdop (assume good) - 12.0f, // numSats (assume good) + 5.0f, // hacc m + 8.0f, // vacc m + 0.5f, // sacc m/s 1.0f // valid ) } @@ -649,43 +690,8 @@ class AhrsModule(reactContext: ReactApplicationContext) : val currentTimeNs = System.nanoTime() if (currentTimeNs > nextEmitTime) { val output = getAhrsOutput(ekfPtr) - if (output != null && output.size >= 24) { - val map = Arguments.createMap() - val headingOffset = headingOffsetDeg() - - map.putDouble("roll", output[0].toDouble()) - map.putDouble("pitch", output[1].toDouble()) - map.putDouble("heading", normalizeHeadingDeg(output[2] + headingOffset).toDouble()) - map.putDouble("flightPathAngle", output[3].toDouble()) - map.putDouble("horizontalFlightPathAngle", output[4].toDouble()) - map.putDouble("trackAngle", output[5].toDouble()) - map.putDouble("horizontalSpeed", output[6].toDouble()) - map.putDouble("totalSpeed", output[7].toDouble()) - map.putDouble("altitude", output[8].toDouble()) - map.putDouble("altitudeQNE", output[9].toDouble()) - map.putDouble("altitudeQNH", output[10].toDouble()) - map.putDouble("verticalSpeed", output[11].toDouble()) - // Get barometric pressure from baroData if available - val baroPressure = synchronized(baroLock) { - baroData?.getOrNull(0)?.toDouble() ?: 0.0 - } - map.putDouble("barometricPressure", baroPressure) - map.putDouble("velocityNorth", output[12].toDouble()) - map.putDouble("velocityEast", output[13].toDouble()) - map.putDouble("velocityDown", output[14].toDouble()) - val gpsPos = getGpsPosition(ekfPtr) - if (gpsPos[0] != 0.0 || gpsPos[1] != 0.0) { - map.putDouble("latitude", gpsPos[0]) - map.putDouble("longitude", gpsPos[1]) - } - map.putInt("flightPhase", output[15].toInt()) - map.putDouble("flightPhaseConfidence", output[16].toDouble()) - map.putBoolean("attitudeValid", output[17] > 0.5f) - map.putBoolean("altitudeValid", output[18] > 0.5f) - map.putBoolean("positionValid", output[19] > 0.5f) - map.putBoolean("flightPhaseValid", output[20] > 0.5f) - - emitOnAhrsUpdate(map) + if (output != null) { + emitAhrsMap(output) } nextEmitTime = currentTimeNs + emitRateHz.hz } @@ -725,7 +731,7 @@ class AhrsModule(reactContext: ReactApplicationContext) : // Calibrate barometer against GPS if available synchronized(locationLock) { val gps = locationData - if (!baroCalibrated && gps != null && gps.size >= 9 && gps[8] > 0.5f) { + if (!baroCalibrated && gps != null && gps.size >= 10 && gps[9] > 0.5f) { val gpsAltMsl = gps[2] val pressureRatio = Math.pow( 1.0 - gpsAltMsl / 44330.0, @@ -877,9 +883,25 @@ class AhrsModule(reactContext: ReactApplicationContext) : var velD = 0.0f var speed = 0.0f - if (location.hasSpeed() && location.speedAccuracy >= 0) { + val speedAcc = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && location.hasSpeedAccuracy()) { + location.speedAccuracyMetersPerSecond + } else { + -1.0f + } + val bearingAcc = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && location.hasBearingAccuracy()) { + location.bearingAccuracyDegrees + } else { + -1.0f + } + val verticalAcc = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && location.hasVerticalAccuracy()) { + location.verticalAccuracyMeters + } else { + -1.0f + } + + if (location.hasSpeed() && (speedAcc < 0.0f || speedAcc < 5.0f)) { speed = location.speed - if (location.hasBearing() && location.bearingAccuracy >= 0) { + if (location.hasBearing() && (bearingAcc < 0.0f || bearingAcc < 45.0f)) { val courseRad = Math.toRadians(location.bearing.toDouble()).toFloat() velN = speed * kotlin.math.cos(courseRad) velE = speed * kotlin.math.sin(courseRad) @@ -907,17 +929,9 @@ class AhrsModule(reactContext: ReactApplicationContext) : } val horizontalAccuracy = location.accuracy - val numSats = when { - horizontalAccuracy < 0 -> 0 - horizontalAccuracy < 5.0f -> 12 - horizontalAccuracy < 10.0f -> 10 - horizontalAccuracy < 20.0f -> 8 - horizontalAccuracy < 50.0f -> 6 - else -> 4 - } - - val hdop = (horizontalAccuracy / 3.0f).coerceIn(1.0f, 20.0f) - val vdop = (location.verticalAccuracy / 3.0f).coerceIn(1.0f, 20.0f) + val hacc = if (horizontalAccuracy > 0.0f) horizontalAccuracy else -1.0f + val vacc = if (verticalAcc > 0.0f) verticalAcc else -1.0f + val sacc = if (speedAcc >= 0.0f) speedAcc else -1.0f val valid = isValidLocation(location) && speed <= 200.0f @@ -929,8 +943,9 @@ class AhrsModule(reactContext: ReactApplicationContext) : velN, velE, velD, - hdop, - numSats.toFloat(), + hacc, + vacc, + sacc, if (valid) 1.0f else 0.0f ) } @@ -1009,7 +1024,7 @@ class AhrsModule(reactContext: ReactApplicationContext) : var gpsCopy: FloatArray? = null synchronized(locationLock) { val gps = locationData - if (gps != null && (gps[8] > 0.5f) && timestampUs != lastUsedLocationTimestamp) { + if (gps != null && gps.size >= 10 && gps[9] > 0.5f && timestampUs != lastUsedLocationTimestamp) { gpsCopy = gps.clone() lastUsedLocationTimestamp = timestampUs } @@ -1031,43 +1046,8 @@ class AhrsModule(reactContext: ReactApplicationContext) : // Emit output at configured rate if (currentTimeNs > nextEmitTime) { val output = getAhrsOutput(ekfPtr) - if (output != null && output.size >= 24) { - val map = Arguments.createMap() - val headingOffset = headingOffsetDeg() - - map.putDouble("roll", output[0].toDouble()) - map.putDouble("pitch", output[1].toDouble()) - map.putDouble("heading", normalizeHeadingDeg(output[2] + headingOffset)) - map.putDouble("flightPathAngle", output[3].toDouble()) - map.putDouble("horizontalFlightPathAngle", output[4].toDouble()) - map.putDouble("trackAngle", output[5].toDouble()) - map.putDouble("horizontalSpeed", output[6].toDouble()) - map.putDouble("totalSpeed", output[7].toDouble()) - map.putDouble("altitude", output[8].toDouble()) - map.putDouble("altitudeQNE", output[9].toDouble()) - map.putDouble("altitudeQNH", output[10].toDouble()) - map.putDouble("verticalSpeed", output[11].toDouble()) - // Get barometric pressure from baroData if available - val baroPressure = synchronized(baroLock) { - baroData?.getOrNull(0)?.toDouble() ?: 0.0 - } - map.putDouble("barometricPressure", baroPressure) - map.putDouble("velocityNorth", output[12].toDouble()) - map.putDouble("velocityEast", output[13].toDouble()) - map.putDouble("velocityDown", output[14].toDouble()) - val gpsPos = getGpsPosition(ekfPtr) - if (gpsPos[0] != 0.0 || gpsPos[1] != 0.0) { - map.putDouble("latitude", gpsPos[0]) - map.putDouble("longitude", gpsPos[1]) - } - map.putInt("flightPhase", output[15].toInt()) - map.putDouble("flightPhaseConfidence", output[16].toDouble()) - map.putBoolean("attitudeValid", output[17] > 0.5f) - map.putBoolean("altitudeValid", output[18] > 0.5f) - map.putBoolean("positionValid", output[19] > 0.5f) - map.putBoolean("flightPhaseValid", output[20] > 0.5f) - - emitOnAhrsUpdate(map) + if (output != null) { + emitAhrsMap(output) } nextEmitTime = currentTimeNs + emitRateHz.hz } diff --git a/eslint.config.mjs b/eslint.config.mjs index 16b00bb..40b3ac7 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -24,6 +24,6 @@ export default defineConfig([ }, }, { - ignores: ['node_modules/', 'lib/'], + ignores: ['node_modules/', 'lib/', 'example/'], }, ]); diff --git a/example/package.json b/example/package.json index a5f7fc1..ab58863 100644 --- a/example/package.json +++ b/example/package.json @@ -11,27 +11,27 @@ }, "dependencies": { "@react-native-async-storage/async-storage": "^2.2.0", - "@react-native-community/slider": "^5.1.2", - "@shopify/react-native-skia": "^2.4.16", - "react": "19.1.0", - "react-native": "0.83.1", + "@react-native-community/slider": "^5.2.1", + "@shopify/react-native-skia": "2.9.1", + "react": "19.2.8", + "react-native": "0.83.10", "react-native-orientation-director": "^2.6.5" }, "devDependencies": { - "@babel/core": "^7.28.6", - "@babel/preset-env": "^7.28.6", - "@babel/runtime": "^7.28.6", - "@react-native-community/cli": "20.1.1", - "@react-native-community/cli-platform-android": "20.1.1", - "@react-native-community/cli-platform-ios": "20.1.1", - "@react-native/babel-preset": "0.83.1", - "@react-native/metro-config": "0.83.1", - "@react-native/typescript-config": "0.83.1", - "@types/react": "^19.2.10", - "react-native-builder-bob": "^0.40.17", - "react-native-monorepo-config": "^0.3.2" + "@babel/core": "^7.29.7", + "@babel/preset-env": "^7.29.7", + "@babel/runtime": "^7.29.7", + "@react-native-community/cli": "20.2.0", + "@react-native-community/cli-platform-android": "20.2.0", + "@react-native-community/cli-platform-ios": "20.2.0", + "@react-native/babel-preset": "0.83.10", + "@react-native/metro-config": "0.83.10", + "@react-native/typescript-config": "0.83.10", + "@types/react": "^19.2.18", + "react-native-builder-bob": "^0.40.18", + "react-native-monorepo-config": "^0.3.5" }, "engines": { - "node": ">=20" + "node": ">=20.19.4" } } diff --git a/example/src/App.tsx b/example/src/App.tsx index afdae6a..7fdef63 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -4,6 +4,7 @@ * Main application component for the React Native AHRS example app. * Provides a user interface for: * - Displaying real-time aircraft attitude and navigation data + * - Filter health, IMU rest, and ZUPT status * - Controlling AHRS settings (rate, QNH, orientation) * - Recording and playback of flight data * - X-Plane flight simulator integration @@ -71,6 +72,8 @@ function getInitialAttitude(): AhrsData { positionValid: false, flightPhaseValid: false, filterHealthStatus: 0, + atRest: false, + zuptActive: false, }; } @@ -102,6 +105,12 @@ function createSafeData(data: AhrsData): AhrsData { flightPhase: data.flightPhase ?? 0, flightPhaseConfidence: data.flightPhaseConfidence ?? 0, filterHealthStatus: data.filterHealthStatus ?? 0, + attitudeValid: data.attitudeValid ?? false, + altitudeValid: data.altitudeValid ?? false, + positionValid: data.positionValid ?? false, + flightPhaseValid: data.flightPhaseValid ?? false, + atRest: data.atRest ?? false, + zuptActive: data.zuptActive ?? false, }; } @@ -186,15 +195,16 @@ export default function App() { const [xplaneHost, setXplaneHost] = useState(''); const [xplaneConnected, setXplaneConnected] = useState(false); const [xplaneConnecting, setXplaneConnecting] = useState(false); - + // Handle IP address input with filtering const handleXPlaneHostChange = (text: string) => { const filtered = filterIPv4Input(text); setXplaneHost(filtered); }; - + // Check if current IP address is valid - const isXPlaneHostValid = xplaneHost.trim() !== '' && isValidIPv4(xplaneHost.trim()); + const isXPlaneHostValid = + xplaneHost.trim() !== '' && isValidIPv4(xplaneHost.trim()); // Load saved X-Plane host on mount useEffect(() => { @@ -267,11 +277,16 @@ export default function App() { * Rounds and formats attitude values for display. * Angles are rounded to integers, speeds/altitudes to 1 decimal place. */ - const roundedAttitude = useMemo( - () => ({ + const roundedAttitude = useMemo(() => { + const magneticHeading = attitude.heading ?? 0; + const declination = attitude.magneticDeclination ?? 0; + const trueHeading = (((magneticHeading + declination) % 360) + 360) % 360; + return { roll: Math.round(attitude.roll), pitch: Math.round(attitude.pitch), - heading: Math.round(attitude.heading), + heading: Math.round(magneticHeading), + trueHeading: Math.round(trueHeading), + groundTrack: Math.round(attitude.groundTrack ?? 0), flightPathAngle: (attitude.flightPathAngle ?? 0).toFixed(1), horizontalFlightPathAngle: ( attitude.horizontalFlightPathAngle ?? 0 @@ -279,9 +294,11 @@ export default function App() { altitudeQNE: Math.round(attitude.altitudeQNE), altitudeQNH: Math.round(attitude.altitudeQNH), verticalSpeed: (attitude.verticalSpeed ?? 0).toFixed(1), - }), - [attitude] - ); + velocityNorth: (attitude.velocityNorth ?? 0).toFixed(1), + velocityEast: (attitude.velocityEast ?? 0).toFixed(1), + velocityDown: (attitude.velocityDown ?? 0).toFixed(1), + }; + }, [attitude]); const listenerUnsubscribeRef = useRef<(() => void) | null>(null); @@ -560,7 +577,7 @@ export default function App() { Alert.alert('Invalid IP Address', 'Please enter a valid IPv4 address.'); return; } - + if (!isValidIPv4(host)) { Alert.alert( 'Invalid IP Address', @@ -568,7 +585,7 @@ export default function App() { ); return; } - + // Save the host for next time AsyncStorage.setItem(XPLANE_HOST_STORAGE_KEY, host).catch((error) => { console.warn('Failed to save X-Plane host:', error); @@ -601,6 +618,7 @@ export default function App() { heading: attitude.heading, flightPathAngle: attitude.flightPathAngle ?? 0, horizontalFlightPathAngle: attitude.horizontalFlightPathAngle ?? 0, + attitudeValid: attitude.attitudeValid, }} /> - {/* Status Section */} - - Status - - Data Source: - - {xplaneConnected ? 'X-Plane' : 'Device Sensors'} - - - - Orientation: - {rotationString} - - - Rate: - {rate} Hz - - - Filter health: - - - - - {/* Flight Phase Section */} - - Flight Phase - - Valid: - - {attitude.flightPhaseValid ? 'TRUE' : 'FALSE'} - - - - Phase: - {flightPhaseString} - - - Confidence: - - {flightPhaseConfidencePercent}% - - - - - {/* Attitude Section */} - - Attitude - - Valid: - - {attitude.attitudeValid ? 'TRUE' : 'FALSE'} - - - - Roll: - {roundedAttitude.roll}° - - - Pitch: - {roundedAttitude.pitch}° - - - Heading: - {roundedAttitude.heading}° - - - - {/* Altitude Section */} - - Altitude - - Valid: - - {attitude.altitudeValid ? 'TRUE' : 'FALSE'} - - - - V/S: - - {roundedAttitude.verticalSpeed} m/s - - - - GPS: - - {Math.round(attitude.altitude)} m - - - - QNE: - - {roundedAttitude.altitudeQNE} m - - - - QNH: - - {roundedAttitude.altitudeQNH} m - - - - Baro Pressure: - - {attitude.barometricPressure > 0 - ? `${attitude.barometricPressure.toFixed(1)} hPa` - : 'N/A'} - - - - QNH Pressure: - {qnh.toFixed(1)} hPa - - - - {QNH_MIN} - {QNH_MAX} + {/* Status Section */} + + Status + + Data Source: + + {xplaneConnected ? 'X-Plane' : 'Device Sensors'} + + + + Orientation: + {rotationString} + + + Rate: + {rate} Hz + + + Filter health: + + + + IMU rest: + + + + ZUPT: + - - - - - {/* Positional Data Section */} - - Positional Data - - Valid: - - {attitude.positionValid ? 'TRUE' : 'FALSE'} - - - {attitude.latitude !== undefined && - attitude.longitude !== undefined && ( - <> - - Latitude: - - {(attitude.latitude ?? 0).toFixed(6)}° - - - - Longitude: - - {(attitude.longitude ?? 0).toFixed(6)}° - - - - Mag Declination: - - {attitude.magneticDeclination >= 0 ? '+' : ''} - {attitude.magneticDeclination.toFixed(1)}° - - - - )} - - Ground Speed: - - {(attitude.groundSpeed ?? 0).toFixed(1)} m/s - - - - Vertical FPA: - - {attitude.flightPathAngle !== undefined - ? `${attitude.flightPathAngle >= 0 ? '+' : ''}${roundedAttitude.flightPathAngle}°` - : 'N/A'} - - - Horizontal FPA: - - {attitude.horizontalFlightPathAngle !== undefined - ? `${attitude.horizontalFlightPathAngle >= 0 ? '+' : ''}${roundedAttitude.horizontalFlightPathAngle}°` - : 'N/A'} - + + {/* Flight Phase Section */} + + Flight Phase + + Valid: + + {attitude.flightPhaseValid ? 'TRUE' : 'FALSE'} + + + + Phase: + {flightPhaseString} + + + Confidence: + + {flightPhaseConfidencePercent}% + + - - - {/* X-Plane Connection Section */} - - X-Plane Connection - - Status: - + + {/* Attitude Section */} + + Attitude + + Valid: + + {attitude.attitudeValid ? 'TRUE' : 'FALSE'} + + + + Roll: + {roundedAttitude.roll}° + + + Pitch: + {roundedAttitude.pitch}° + + + Heading (mag): + {roundedAttitude.heading}° + + + Heading (true): + + {roundedAttitude.trueHeading}° + + + + Ground Track: + + {roundedAttitude.groundTrack}° + + - {xplaneConnected && ( - - Host: - {xplaneHost} + + {/* Altitude Section */} + + Altitude + + Valid: + + {attitude.altitudeValid ? 'TRUE' : 'FALSE'} + - )} - {!xplaneConnected && ( - - + + V/S: + + {roundedAttitude.verticalSpeed} m/s + - )} - {!xplaneConnected ? ( - - - - {xplaneConnecting ? 'Connecting...' : 'Connect to X-Plane'} - - - - ) : ( - - - Disconnect from X-Plane + + GPS: + + {Math.round(attitude.altitude)} m + + + + QNE: + + {roundedAttitude.altitudeQNE} m + + + + QNH: + + {roundedAttitude.altitudeQNH} m + + + + Baro Pressure: + + {attitude.barometricPressure > 0 + ? `${attitude.barometricPressure.toFixed(1)} hPa` + : 'N/A'} + + + + QNH Pressure: + {qnh.toFixed(1)} hPa + + + + {QNH_MIN} + {QNH_MAX} - - )} - - - {/* Controls Section */} - - - - Rotate + - + - - - - Level - - - Ahrs.reset()} - underlayColor="#e0e0e0" - style={styles.buttonWrapper} - > - - Reset - - + {/* Positional Data Section */} + + Positional Data + + Valid: + + {attitude.positionValid ? 'TRUE' : 'FALSE'} + + + {attitude.latitude !== undefined && + attitude.longitude !== undefined && ( + <> + + Latitude: + + {(attitude.latitude ?? 0).toFixed(6)}° + + + + Longitude: + + {(attitude.longitude ?? 0).toFixed(6)}° + + + + Mag Declination: + + {attitude.magneticDeclination >= 0 ? '+' : ''} + {attitude.magneticDeclination.toFixed(1)}° + + + + )} + + Ground Speed: + + {(attitude.groundSpeed ?? 0).toFixed(1)} m/s + + + + Ground Track: + + {roundedAttitude.groundTrack}° + + + + Vel N/E/D: + + {roundedAttitude.velocityNorth} / {roundedAttitude.velocityEast}{' '} + / {roundedAttitude.velocityDown} m/s + + + + Vertical FPA: + + {attitude.flightPathAngle !== undefined + ? `${attitude.flightPathAngle >= 0 ? '+' : ''}${roundedAttitude.flightPathAngle}°` + : 'N/A'} + + + + Horizontal FPA: + + {attitude.horizontalFlightPathAngle !== undefined + ? `${attitude.horizontalFlightPathAngle >= 0 ? '+' : ''}${roundedAttitude.horizontalFlightPathAngle}°` + : 'N/A'} + + - {/* Recording Controls */} + {/* X-Plane Connection Section */} - Recording - {!isRecording ? ( + X-Plane Connection + + Status: + + + {xplaneConnected && ( + + Host: + {xplaneHost} + + )} + {!xplaneConnected && ( + + + + )} + {!xplaneConnected ? ( - Start Recording + + {xplaneConnecting ? 'Connecting...' : 'Connect to X-Plane'} + ) : ( - + + + Disconnect from X-Plane + + + )} + + + {/* Controls Section */} + + + + Rotate + + + + + + + Level + + + Ahrs.reset()} + underlayColor="#e0e0e0" + style={styles.buttonWrapper} + > + + Reset + + + + + {/* Recording Controls */} + + Recording + {!isRecording ? ( + + + Start Recording + + + ) : ( + + + Recording: + + + + + Stop Recording + + + + )} + + + {/* Playback Controls */} + {isPlaying && ( + + Playback - Recording: - + Playing: + - Stop Recording + Stop Playback )} - - {/* Playback Controls */} - {isPlaying && ( + {/* File Explorer */} - Playback - - Playing: - - + Recordings - Stop Playback + Refresh - - )} - - {/* File Explorer */} - - Recordings - - - Refresh - - - {recordingFiles.length === 0 ? ( - No recordings found - ) : ( - recordingFiles.map((file) => ( - - - {file.filename} - - {(file.size / 1024).toFixed(1)} KB •{' '} - {new Date(file.date).toLocaleString()} - - - - handlePlayback(file.filename)} - underlayColor="#e0e0e0" - disabled={isRecording || isPlaying} - style={{ borderRadius: 6 }} - > - No recordings found + ) : ( + recordingFiles.map((file) => ( + + + {file.filename} + - Play - - - handleDeleteRecording(file.filename)} - underlayColor="#e0e0e0" - disabled={isRecording || isPlaying} - style={{ borderRadius: 6 }} - > - + + + handlePlayback(file.filename)} + underlayColor="#e0e0e0" + disabled={isRecording || isPlaying} + style={{ borderRadius: 6 }} + > + + Play + + + handleDeleteRecording(file.filename)} + underlayColor="#e0e0e0" + disabled={isRecording || isPlaying} + style={{ borderRadius: 6 }} > - Delete - - + + Delete + + + - - )) - )} - + )) + )} + - - setRate(getNextRate(rate, 'decrease'))} - underlayColor="#e0e0e0" - accessibilityLabel="Decrease rate" - accessibilityRole="button" - style={styles.buttonWrapper} - disabled={rate <= (RATE_VALUES[0] as number)} - > - + setRate(getNextRate(rate, 'decrease'))} + underlayColor="#e0e0e0" + accessibilityLabel="Decrease rate" + accessibilityRole="button" + style={styles.buttonWrapper} + disabled={rate <= (RATE_VALUES[0] as number)} > - − + + − + + + + {rate} Hz - - - {rate} Hz - - setRate(getNextRate(rate, 'increase'))} - underlayColor="#e0e0e0" - accessibilityLabel="Increase rate" - accessibilityRole="button" - style={styles.buttonWrapper} - disabled={rate >= (RATE_VALUES[RATE_VALUES.length - 1] as number)} - > - = (RATE_VALUES[RATE_VALUES.length - 1] as number) - ? COLORS.DISABLED - : COLORS.SUCCESS, - }, - ]} + setRate(getNextRate(rate, 'increase'))} + underlayColor="#e0e0e0" + accessibilityLabel="Increase rate" + accessibilityRole="button" + style={styles.buttonWrapper} + disabled={ + rate >= (RATE_VALUES[RATE_VALUES.length - 1] as number) + } > - + - - + = (RATE_VALUES[RATE_VALUES.length - 1] as number) + ? COLORS.DISABLED + : COLORS.SUCCESS, + }, + ]} + > + + + + + - diff --git a/example/src/OrientationVisualizer.tsx b/example/src/OrientationVisualizer.tsx index 4dd05d1..26dead8 100755 --- a/example/src/OrientationVisualizer.tsx +++ b/example/src/OrientationVisualizer.tsx @@ -11,12 +11,18 @@ * - Heading display with bug indicator * - Flight Path Vector (FPV) symbol * - Aircraft symbol overlay + * - ALIGNING overlay while attitude is not yet valid * * @module OrientationVisualizer */ import React, { useMemo } from 'react'; -import { View, StyleSheet, useWindowDimensions } from 'react-native'; +import { + View, + StyleSheet, + Text as RNText, + useWindowDimensions, +} from 'react-native'; import { Canvas, Circle, @@ -113,6 +119,8 @@ interface Attitude { flightPathAngle: number; /** Horizontal flight path angle in degrees (sideslip/crab angle) */ horizontalFlightPathAngle: number; + /** False while the EKF is still aligning or attitude is unhealthy */ + attitudeValid?: boolean; } /** @@ -167,14 +175,18 @@ function getVisiblePitchRange( pfdSize: number ): { min: number; max: number } { // Validate inputs to prevent NaN/invalid calculations - if (!Number.isFinite(pitchOffset) || !Number.isFinite(pfdSize) || pfdSize <= 0) { + if ( + !Number.isFinite(pitchOffset) || + !Number.isFinite(pfdSize) || + pfdSize <= 0 + ) { // Return full range if inputs are invalid return { min: LIMITS.PITCH_MIN, max: LIMITS.PITCH_MAX, }; } - + // Calculate how many degrees are visible above and below center const visibleDegrees = pfdSize / (2 * DIMENSIONS.PITCH_SCALE); // Round to 2.5-degree increments @@ -190,8 +202,12 @@ function getVisiblePitchRange( ) * PITCH_INTERVALS.MINOR; // Ensure valid range is returned - const min = Number.isFinite(minPitch) ? Math.max(LIMITS.PITCH_MIN, minPitch) : LIMITS.PITCH_MIN; - const max = Number.isFinite(maxPitch) ? Math.min(LIMITS.PITCH_MAX, maxPitch) : LIMITS.PITCH_MAX; + const min = Number.isFinite(minPitch) + ? Math.max(LIMITS.PITCH_MIN, minPitch) + : LIMITS.PITCH_MIN; + const max = Number.isFinite(maxPitch) + ? Math.min(LIMITS.PITCH_MAX, maxPitch) + : LIMITS.PITCH_MAX; return { min, @@ -324,7 +340,8 @@ function AircraftSymbol({ center }: { center: number }) { */ function FPVSymbol({ x, y }: { x: number; y: number }) { // Calculate FPV symbol radius using extracted constant - const fpvRadius = (DIMENSIONS.FPV_SYMBOL_SIZE / 2) * DIMENSIONS.FPV_SYMBOL_SIZE_FACTOR; + const fpvRadius = + (DIMENSIONS.FPV_SYMBOL_SIZE / 2) * DIMENSIONS.FPV_SYMBOL_SIZE_FACTOR; return ( @@ -538,13 +555,11 @@ function OrientationVisualizer({ attitude }: Props) { // Handle NaN/undefined/null values by defaulting to 0 const rollValue = Number.isFinite(attitude.roll) ? attitude.roll : 0; const pitchValue = Number.isFinite(attitude.pitch) ? attitude.pitch : 0; - const headingValue = Number.isFinite(attitude.heading) ? attitude.heading : 0; - - const rollClamped = clamp( - rollValue, - LIMITS.ROLL_MIN, - LIMITS.ROLL_MAX - ); + const headingValue = Number.isFinite(attitude.heading) + ? attitude.heading + : 0; + + const rollClamped = clamp(rollValue, LIMITS.ROLL_MIN, LIMITS.ROLL_MAX); const pitchClamped = clamp( pitchValue, LIMITS.PITCH_MIN, @@ -658,7 +673,12 @@ function OrientationVisualizer({ attitude }: Props) { ]); return ( - + {/* Artificial Horizon Group: rotate roll */} {/* Horizon line */} @@ -714,6 +748,11 @@ function OrientationVisualizer({ attitude }: Props) { font={headingFont} /> + {!attitude.attitudeValid && ( + + ALIGNING + + )} ); } @@ -723,6 +762,18 @@ const styles = StyleSheet.create({ justifyContent: 'center', alignItems: 'center', }, + aligningOverlay: { + ...StyleSheet.absoluteFillObject, + backgroundColor: 'rgba(0, 0, 0, 0.45)', + justifyContent: 'center', + alignItems: 'center', + }, + aligningText: { + color: COLORS.TEXT, + fontSize: 28, + fontWeight: '700', + letterSpacing: 2, + }, }); // Memoize component to prevent unnecessary re-renders diff --git a/example/tsconfig.json b/example/tsconfig.json new file mode 100644 index 0000000..0acf1d5 --- /dev/null +++ b/example/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "@react-native/typescript-config", + "compilerOptions": { + "types": [], + "baseUrl": ".", + "paths": { + "react-native-ahrs": ["../src/index"] + } + }, + "include": ["src/**/*.ts", "src/**/*.tsx"], + "exclude": ["**/node_modules", "**/Pods"] +} diff --git a/fusionml/src/uNavINS.cpp b/fusionml/src/uNavINS.cpp index 5ee73be..e71482b 100644 --- a/fusionml/src/uNavINS.cpp +++ b/fusionml/src/uNavINS.cpp @@ -47,91 +47,117 @@ void uNavINS::update(double dt_in, unsigned long TOW, double vn, double ve, doub float bn, float be, float bd, float hacc, float vacc, float sacc, float baro_pressure, float baro_qnh) { - if (dt_in <= 0.0) { - return; - } if (!initialized) { - // initial attitude and heading - theta = asinf(ax/G); - phi = asinf(-ay/(G*cosf(theta))); - // magnetic heading correction due to roll and pitch angle - Bxc = hx*cosf(theta) + (hy*sinf(phi) + hz*cosf(phi))*sinf(theta); - Byc = hy*cosf(phi) - hz*sinf(phi); - // finding initial heading - if (-Byc > 0) { - psi = M_PI/2.0f - atanf(Bxc/-Byc); + if (!std::isfinite(ax) || !std::isfinite(ay) || !std::isfinite(az) || + !std::isfinite(p) || !std::isfinite(q) || !std::isfinite(r)) { + return; + } + float accel_norm = sqrtf(ax * ax + ay * ay + az * az); + if (accel_norm < 0.5f * G || accel_norm > 1.5f * G) { + return; + } + float ax_over_g = ax / G; + if (fabsf(ax_over_g) > 1.0f) { + return; + } + theta = asinf(ax_over_g); + float cos_theta = cosf(theta); + if (fabsf(cos_theta) < 1e-3f) { + return; + } + float ay_term = -ay / (G * cos_theta); + if (fabsf(ay_term) > 1.0f) { + return; + } + phi = asinf(ay_term); + + float mag_norm_uT = sqrtf(hx * hx + hy * hy + hz * hz); + bool mag_ok = std::isfinite(hx) && std::isfinite(hy) && std::isfinite(hz) && mag_norm_uT > 5.0f; + if (mag_ok) { + Bxc = hx * cosf(theta) + (hy * sinf(phi) + hz * cosf(phi)) * sinf(theta); + Byc = hy * cosf(phi) - hz * sinf(phi); + psi = constrainAngle180(atan2f(-Byc, Bxc)); } else { - psi= 3.0f*M_PI/2.0f - atanf(Bxc/-Byc); + psi = 0.0f; } - psi = constrainAngle180(psi); psi_initial = psi; // euler to quaternion quat(0) = cosf(psi/2.0f)*cosf(theta/2.0f)*cosf(phi/2.0f) + sinf(psi/2.0f)*sinf(theta/2.0f)*sinf(phi/2.0f); quat(1) = cosf(psi/2.0f)*cosf(theta/2.0f)*sinf(phi/2.0f) - sinf(psi/2.0f)*sinf(theta/2.0f)*cosf(phi/2.0f); quat(2) = cosf(psi/2.0f)*sinf(theta/2.0f)*cosf(phi/2.0f) + sinf(psi/2.0f)*cosf(theta/2.0f)*sinf(phi/2.0f); quat(3) = sinf(psi/2.0f)*cosf(theta/2.0f)*cosf(phi/2.0f) - cosf(psi/2.0f)*sinf(theta/2.0f)*sinf(phi/2.0f); + if (!quat.allFinite()) { + return; + } // Assemble the matrices - // ... gravity grav(2,0) = G; - // ... H + H.setZero(); H.block(0,0,6,6) = Eigen::Matrix::Identity(); - // // ... Rw - // Rw.block(0,0,3,3) = powf(SIG_W_A,2.0f)*Eigen::Matrix::Identity(); - // Rw.block(3,3,3,3) = powf(SIG_W_G,2.0f)*Eigen::Matrix::Identity(); - // Rw.block(6,6,3,3) = 2.0f*powf(SIG_A_D,2.0f)/TAU_A*Eigen::Matrix::Identity(); - // Rw.block(9,9,3,3) = 2.0f*powf(SIG_G_D,2.0f)/TAU_G*Eigen::Matrix::Identity(); - // // ... P - // P.block(0,0,3,3) = powf(P_P_INIT,2.0f)*Eigen::Matrix::Identity(); - // P.block(3,3,3,3) = powf(P_V_INIT,2.0f)*Eigen::Matrix::Identity(); - // P.block(6,6,2,2) = powf(P_A_INIT,2.0f)*Eigen::Matrix::Identity(); - // P(8,8) = powf(P_HDG_INIT,2.0f); - // P.block(9,9,3,3) = powf(P_AB_INIT,2.0f)*Eigen::Matrix::Identity(); - // P.block(12,12,3,3) = powf(P_GB_INIT,2.0f)*Eigen::Matrix::Identity(); - // // ... R - // R.block(0,0,2,2) = powf(SIG_GPS_P_NE,2.0f)*Eigen::Matrix::Identity(); - // R(2,2) = powf(SIG_GPS_P_D,2.0f); - // R.block(3,3,3,3) = powf(SIG_GPS_V,2.0f)*Eigen::Matrix::Identity(); - // ... Rw (15 process noise sources: 3 accel, 3 gyro, 3 accel bias, 3 gyro bias, 3 mag bias) + // Rw (15 process noise sources: 3 accel, 3 gyro, 3 accel bias, 3 gyro bias, 3 mag bias) + // Mag bias is estimated by a slow LPF, not Kalman, so keep that process noise tiny. Rw(0,0) = SIG_W_A*SIG_W_A; Rw(1,1) = SIG_W_A*SIG_W_A; Rw(2,2) = SIG_W_A*SIG_W_A; Rw(3,3) = SIG_W_G*SIG_W_G; Rw(4,4) = SIG_W_G*SIG_W_G; Rw(5,5) = SIG_W_G*SIG_W_G; Rw(6,6) = 2.0f*SIG_A_D*SIG_A_D/TAU_A; Rw(7,7) = 2.0f*SIG_A_D*SIG_A_D/TAU_A; Rw(8,8) = 2.0f*SIG_A_D*SIG_A_D/TAU_A; Rw(9,9) = 2.0f*SIG_G_D*SIG_G_D/TAU_G; Rw(10,10) = 2.0f*SIG_G_D*SIG_G_D/TAU_G; Rw(11,11) = 2.0f*SIG_G_D*SIG_G_D/TAU_G; - Rw(12,12) = 2.0f*SIG_M_D*SIG_M_D/TAU_M; Rw(13,13) = 2.0f*SIG_M_D*SIG_M_D/TAU_M; Rw(14,14) = 2.0f*SIG_M_D*SIG_M_D/TAU_M; + Rw(12,12) = 1.0e-8f; Rw(13,13) = 1.0e-8f; Rw(14,14) = 1.0e-8f; // ... P + P.setZero(); P(0,0) = P_P_INIT*P_P_INIT; P(1,1) = P_P_INIT*P_P_INIT; P(2,2) = P_P_INIT*P_P_INIT; P(3,3) = P_V_INIT*P_V_INIT; P(4,4) = P_V_INIT*P_V_INIT; P(5,5) = P_V_INIT*P_V_INIT; P(6,6) = P_A_INIT*P_A_INIT; P(7,7) = P_A_INIT*P_A_INIT; P(8,8) = P_HDG_INIT*P_HDG_INIT; P(9,9) = P_AB_INIT*P_AB_INIT; P(10,10) = P_AB_INIT*P_AB_INIT; P(11,11) = P_AB_INIT*P_AB_INIT; P(12,12) = P_GB_INIT*P_GB_INIT; P(13,13) = P_GB_INIT*P_GB_INIT; P(14,14) = P_GB_INIT*P_GB_INIT; P(15,15) = P_MB_INIT*P_MB_INIT; P(16,16) = P_MB_INIT*P_MB_INIT; P(17,17) = P_MB_INIT*P_MB_INIT; - // ... R + if (!mag_ok) { + P(8,8) = P_HDG_INIT * P_HDG_INIT; + } + // GPS R (6x6) + R.setZero(); R(0,0) = SIG_GPS_P_NE*SIG_GPS_P_NE; R(1,1) = SIG_GPS_P_NE*SIG_GPS_P_NE; R(2,2) = SIG_GPS_P_D*SIG_GPS_P_D; R(3,3) = SIG_GPS_V*SIG_GPS_V; R(4,4) = SIG_GPS_V*SIG_GPS_V; R(5,5) = SIG_GPS_V*SIG_GPS_V; - R(6,6) = SIG_MAG*SIG_MAG; R(7,7) = SIG_MAG*SIG_MAG; R(8,8) = SIG_MAG*SIG_MAG; - - // Initialize all biases to zero - abx = 0.0f; aby = 0.0f; abz = 0.0f; // accel bias - gbx = 0.0f; gby = 0.0f; gbz = 0.0f; // gyro bias - mbx = 0.0f; mby = 0.0f; mbz = 0.0f; // mag bias - previousTOW = 0; // GPS time of week + abx = 0.0f; aby = 0.0f; abz = 0.0f; + gbx = 0.0f; gby = 0.0f; gbz = 0.0f; + mbx = 0.0f; mby = 0.0f; mbz = 0.0f; + + previousTOW = 0; + gps_has_updated = false; + have_gps_speed = false; + last_gps_ground_speed = 0.0f; + last_gps_fusion_time = 0.0; + gps_delay_estimate = GPS_DELAY_NOMINAL; + filter_time = 0.0; + last_mag_update_time = -1.0; + mag_filter_initialized = false; + mag_meas_filtered.setZero(); + last_baro_update_time = -1.0; + last_accel_tilt_time = -1.0; + last_zupt_time = -1.0f; + low_speed_timer = 0.0f; + zupt_active = false; + rest_detector.reset(); - // .. then initialize states with GPS Data lat_ins = lat; lon_ins = lon; alt_ins = alt; vn_ins = vn; ve_ins = ve; vd_ins = vd; - // specific force f_b(0,0) = ax; f_b(1,0) = ay; f_b(2,0) = az; - /* initialize the time */ + om_ib(0,0) = p; + om_ib(1,0) = q; + om_ib(2,0) = r; _t = 0; - // initialized flag initialized = true; - } else { + return; + } + + if (dt_in <= 0.0) { + return; + } + { // get the change in time _dt = (float)dt_in; if (_dt < 1e-4f) _dt = 1e-4f; @@ -139,10 +165,6 @@ void uNavINS::update(double dt_in, unsigned long TOW, double vn, double ve, doub filter_time += _dt; - // Store raw IMU data before any processing. We'll need this for re-propagation after delayed GPS updates - addImuSample(filter_time, p, q, r, ax, ay, az); - - // are we at rest? bool at_rest = rest_detector.update(p, q, r, ax, ay, az); lla_ins(0,0) = lat_ins; @@ -233,11 +255,18 @@ void uNavINS::update(double dt_in, unsigned long TOW, double vn, double ve, doub // SPEED-BASED ZUPT TRIGGER LOGIC // Track sustained low ground speed to enable ZUPT for stationary scenarios // (person holding phone while standing still, car at stoplight, etc.) - float ground_speed = sqrtf(vn_ins*vn_ins + ve_ins*ve_ins); - // Hysteresis thresholds - const float ZUPT_SPEED_ENTER = 1.0f; // m/s - sustained low speed to enable ZUPT - const float ZUPT_SPEED_EXIT = 1.5f; // m/s - exit ZUPT (prevent flicker) - const float ZUPT_TIME_REQUIRED = 3.0f; // seconds sustained below threshold + // Trigger ZUPT from fresh GPS speed so a diverging INS cannot ZUPT itself, + // and so a stale last-fix of 0 m/s cannot ZUPT a moving vehicle after GPS loss. + const float GPS_SPEED_STALE_S = 2.0f; + bool gps_speed_fresh = have_gps_speed && + ((filter_time - last_gps_fusion_time) < GPS_SPEED_STALE_S); + float zupt_speed = gps_speed_fresh + ? last_gps_ground_speed + : sqrtf(vn_ins * vn_ins + ve_ins * ve_ins); + float ground_speed = zupt_speed; + const float ZUPT_SPEED_ENTER = 1.0f; + const float ZUPT_SPEED_EXIT = 1.5f; + const float ZUPT_TIME_REQUIRED = 3.0f; // Update timer based on current ground speed if (ground_speed < ZUPT_SPEED_ENTER) { low_speed_timer += _dt; @@ -252,35 +281,32 @@ void uNavINS::update(double dt_in, unsigned long TOW, double vn, double ve, doub if (low_speed_timer > ZUPT_TIME_REQUIRED * 2.0f) { low_speed_timer = ZUPT_TIME_REQUIRED * 2.0f; } - // GPS measurement update - if ((TOW - previousTOW) > 0) { - // GPS measurements represent vehicle state at (t_current - GPS_delay), not t_current. - // We compensate for this in two ways: // - // 1. ADAPTIVE DELAY ESTIMATION: Estimate GPS latency from update rate - // 2. DELAY-AWARE MEASUREMENT NOISE: Scale GPS noise based on delay - // - Longer delay → less reliable → higher measurement noise - // - This reduces Kalman gain for stale measurements - double gps_interval = (double)(TOW - previousTOW) / 1000.0; // seconds + bool new_gps = false; + if (gps_has_updated) { + new_gps = (TOW - previousTOW) > 0; + } else { + // First GPS: accept a non-zero TOW, or a non-origin lat/lon (X-Plane starts TOW at 0) + new_gps = (TOW > 0) || (fabs(lat) > 1e-12 || fabs(lon) > 1e-12); + } + if (new_gps && std::isfinite(lat) && std::isfinite(lon) && std::isfinite(alt)) { + double gps_interval = gps_has_updated ? ((double)(TOW - previousTOW) / 1000.0) : 1.0; previousTOW = TOW; - // Adaptive delay estimation based on update rate - if (gps_interval > 0.1 && gps_interval < 5.0) { // Sanity check - // High rate GPS (>2 Hz, interval <0.5s) → lower delay - // Low rate GPS (≤1 Hz, interval ≥1.0s) → higher delay + gps_has_updated = true; + last_gps_ground_speed = sqrtf((float)(vn * vn + ve * ve)); + have_gps_speed = true; + if (gps_interval > 0.1 && gps_interval < 5.0) { float rate_based_delay = GPS_DELAY_NOMINAL; if (gps_interval < 0.5f) { - rate_based_delay = GPS_DELAY_MIN + 0.05f; // ~100ms for high-rate GPS + rate_based_delay = GPS_DELAY_MIN + 0.05f; } else if (gps_interval >= 1.0f) { - rate_based_delay = GPS_DELAY_NOMINAL + 0.05f; // ~250ms for 1 Hz GPS + rate_based_delay = GPS_DELAY_NOMINAL + 0.05f; } - // Exponential filter for smooth delay estimate const float DELAY_FILTER_ALPHA = 0.1f; gps_delay_estimate = (1.0f - DELAY_FILTER_ALPHA) * gps_delay_estimate + DELAY_FILTER_ALPHA * rate_based_delay; - // Clamp to reasonable range if (gps_delay_estimate < GPS_DELAY_MIN) gps_delay_estimate = GPS_DELAY_MIN; if (gps_delay_estimate > GPS_DELAY_MAX) gps_delay_estimate = GPS_DELAY_MAX; } - // Compute scale factor for measurement noise based on delay float delay_scale = computeDelayScaleFactor(gps_delay_estimate); last_gps_fusion_time = filter_time; lla_gps(0,0) = lat; @@ -295,102 +321,57 @@ void uNavINS::update(double dt_in, unsigned long TOW, double vn, double ve, doub V_ins(0,0) = vn_ins; V_ins(1,0) = ve_ins; V_ins(2,0) = vd_ins; - // Position, converted to NED pos_ecef_ins = lla2ecef(lla_ins); pos_ned_ins = ecef2ned(pos_ecef_ins,lla_ins); pos_ecef_gps = lla2ecef(lla_gps); pos_ned_gps = ecef2ned(pos_ecef_gps,lla_ins); - // Create GPS measurement innovation (y[0-5]) y(0,0) = (float)(pos_ned_gps(0,0) - pos_ned_ins(0,0)); y(1,0) = (float)(pos_ned_gps(1,0) - pos_ned_ins(1,0)); y(2,0) = (float)(pos_ned_gps(2,0) - pos_ned_ins(2,0)); y(3,0) = (float)(V_gps(0,0) - V_ins(0,0)); y(4,0) = (float)(V_gps(1,0) - V_ins(1,0)); y(5,0) = (float)(V_gps(2,0) - V_ins(2,0)); - // Build H matrix for GPS (first 6 rows, maps to pos/vel states 0-5) H.setZero(); H.block(0,0,6,6) = Eigen::Matrix::Identity(); - // GPS-only update: zero out magnetometer rows - y(6,0) = 0.0f; - y(7,0) = 0.0f; - y(8,0) = 0.0f; - H.block(6,0,3,18).setZero(); - // ADAPTIVE MEASUREMENT NOISE SCALING - // Scale GPS measurement noise based on: - // 1. GPS delay (longer delay = less trust) - // 2. Reported GPS accuracy (worse accuracy = less trust) - Eigen::Matrix R_scaled = R; - // 1. Delay-based scaling (already computed) + Eigen::Matrix R_scaled = R; float delay_scale_sq = delay_scale * delay_scale; - // 2. GPS accuracy-based scaling - // Use reported accuracy if valid, otherwise use nominal values bool has_valid_hacc = (hacc > 0.0f && std::isfinite(hacc)); bool has_valid_vacc = (vacc > 0.0f && std::isfinite(vacc)); bool has_valid_sacc = (sacc > 0.0f && std::isfinite(sacc)); - // Horizontal position noise: max(nominal, reported_accuracy) float gps_pos_noise_ne = SIG_GPS_P_NE; if (has_valid_hacc) { gps_pos_noise_ne = fmaxf(SIG_GPS_P_NE, hacc); } - // Vertical position noise: max(nominal, reported_accuracy) float gps_pos_noise_d = SIG_GPS_P_D; if (has_valid_vacc) { gps_pos_noise_d = fmaxf(SIG_GPS_P_D, vacc); } - // Velocity noise: max(nominal, reported_accuracy) float gps_vel_noise = SIG_GPS_V; if (has_valid_sacc) { gps_vel_noise = fmaxf(SIG_GPS_V, sacc); } - // Apply adaptive noise (replace nominal R values) R_scaled(0,0) = gps_pos_noise_ne * gps_pos_noise_ne; R_scaled(1,1) = gps_pos_noise_ne * gps_pos_noise_ne; R_scaled(2,2) = gps_pos_noise_d * gps_pos_noise_d; R_scaled(3,3) = gps_vel_noise * gps_vel_noise; R_scaled(4,4) = gps_vel_noise * gps_vel_noise; R_scaled(5,5) = gps_vel_noise * gps_vel_noise; - // Apply delay scaling on top of adaptive noise - R_scaled.block<6,6>(0,0) *= delay_scale_sq; - // Kalman gain (18x9) using fully adaptive measurement noise - K = P*H.transpose()*(H*P*H.transpose() + R_scaled).inverse(); - // Covariance update (Joseph form for numerical stability) - // Use R_scaled (not R) for consistency with Kalman gain - Eigen::Matrix I_KH = Eigen::Matrix::Identity() - K*H; - P = I_KH*P*I_KH.transpose() + K*R_scaled*K.transpose(); - P = 0.5f*(P+P.transpose()); - // State update - x = K*y; - denom = (1.0 - (ECC2 * pow(sin(lla_ins(0,0)),2.0))); - denom = sqrt(denom*denom); - Re = EARTH_RADIUS / sqrt(denom); - Rn = EARTH_RADIUS*(1.0-ECC2) / denom*sqrt(denom); - alt_ins = alt_ins - x(2,0); - lat_ins = lat_ins + x(0,0) / (Re + alt_ins); - lon_ins = lon_ins + x(1,0) / (Rn + alt_ins) / cos(lat_ins); - vn_ins = vn_ins + x(3,0); - ve_ins = ve_ins + x(4,0); - vd_ins = vd_ins + x(5,0); - // Attitude correction - dq(0,0) = 1.0f; - dq(1,0) = x(6,0); - dq(2,0) = x(7,0); - dq(3,0) = x(8,0); - quat = qmult(quat,dq); - quat.normalize(); - // Obtain euler angles from quaternion - theta = asinf(-2.0f*(quat(1,0)*quat(3,0)-quat(0,0)*quat(2,0))); - phi = atan2f(2.0f*(quat(0,0)*quat(1,0)+quat(2,0)*quat(3,0)),1.0f-2.0f*(quat(1,0)*quat(1,0)+quat(2,0)*quat(2,0))); - psi = atan2f(2.0f*(quat(1,0)*quat(2,0)+quat(0,0)*quat(3,0)),1.0f-2.0f*(quat(2,0)*quat(2,0)+quat(3,0)*quat(3,0))); - // Update bias estimates - abx = abx + x(9,0); - aby = aby + x(10,0); - abz = abz + x(11,0); - gbx = gbx + x(12,0); - gby = gby + x(13,0); - gbz = gbz + x(14,0); - mbx = mbx + x(15,0); - mby = mby + x(16,0); - mbz = mbz + x(17,0); + R_scaled *= delay_scale_sq; + Eigen::Matrix S_gps = H * P * H.transpose() + R_scaled; + // Gross-outlier gate only. A tight 3-sigma gate rejects usable GPS when + // the IMU model is slightly wrong (typical on a phone). + bool gps_innovation_ok = + (fabsf(y(0, 0)) < 100.0f) && (fabsf(y(1, 0)) < 100.0f) && + (fabsf(y(2, 0)) < 80.0f) && (fabsf(y(3, 0)) < 80.0f) && + (fabsf(y(4, 0)) < 80.0f) && (fabsf(y(5, 0)) < 80.0f); + if (gps_innovation_ok) { + K = P * H.transpose() * S_gps.inverse(); + Eigen::Matrix I_KH = Eigen::Matrix::Identity() - K * H; + P = I_KH * P * I_KH.transpose() + K * R_scaled * K.transpose(); + P = 0.5f * (P + P.transpose()); + x = K * y; + applyNavigationCorrection(x); + } } // ZUPT MEASUREMENT UPDATE // Apply zero-velocity update when stationary (speed-based trigger) @@ -400,7 +381,11 @@ void uNavINS::update(double dt_in, unsigned long TOW, double vn, double ve, doub // Safety gate: only apply if current 3D velocity is reasonable // (prevents ZUPT during actual motion with stale GPS or filter divergence) float speed_3d = sqrtf(vn_ins*vn_ins + ve_ins*ve_ins + vd_ins*vd_ins); - if (speed_3d < 3.0f) { + // When GPS confirms a stop, ZUPT must still run — that is how a + // diverged INS recovers. The 3 m/s cap is only for INS-speed ZUPT + // after GPS has gone stale. + float zupt_speed_gate = gps_speed_fresh ? 80.0f : 3.0f; + if (speed_3d < zupt_speed_gate) { // ZUPT: observe velocity = [0, 0, 0] in NED frame Eigen::Matrix v_obs; v_obs << 0.0f, 0.0f, 0.0f; @@ -430,41 +415,7 @@ void uNavINS::update(double dt_in, unsigned long TOW, double vn, double ve, doub Eigen::Matrix K_zupt = P * H_zupt.transpose() * S_zupt.inverse(); // State correction Eigen::Matrix x_zupt = K_zupt * y_zupt; - // Apply state corrections (same pattern as GPS update) - // Position correction - denom = (1.0 - (ECC2 * pow(sin(lat_ins),2.0))); - denom = sqrt(denom*denom); - Re = EARTH_RADIUS / sqrt(denom); - Rn = EARTH_RADIUS*(1.0-ECC2) / denom*sqrt(denom); - alt_ins = alt_ins - x_zupt(2,0); - lat_ins = lat_ins + x_zupt(0,0) / (Re + alt_ins); - lon_ins = lon_ins + x_zupt(1,0) / (Rn + alt_ins) / cos(lat_ins); - // Velocity correction - vn_ins = vn_ins + x_zupt(3,0); - ve_ins = ve_ins + x_zupt(4,0); - vd_ins = vd_ins + x_zupt(5,0); - // Attitude correction - dq(0,0) = 1.0f; - dq(1,0) = x_zupt(6,0); - dq(2,0) = x_zupt(7,0); - dq(3,0) = x_zupt(8,0); - quat = qmult(quat,dq); - quat.normalize(); - // Obtain euler angles from quaternion - theta = asinf(-2.0f*(quat(1,0)*quat(3,0)-quat(0,0)*quat(2,0))); - phi = atan2f(2.0f*(quat(0,0)*quat(1,0)+quat(2,0)*quat(3,0)),1.0f-2.0f*(quat(1,0)*quat(1,0)+quat(2,0)*quat(2,0))); - psi = atan2f(2.0f*(quat(1,0)*quat(2,0)+quat(0,0)*quat(3,0)),1.0f-2.0f*(quat(2,0)*quat(2,0)+quat(3,0)*quat(3,0))); - // Update bias estimates - abx = abx + x_zupt(9,0); - aby = aby + x_zupt(10,0); - abz = abz + x_zupt(11,0); - gbx = gbx + x_zupt(12,0); - gby = gby + x_zupt(13,0); - gbz = gbz + x_zupt(14,0); - mbx = mbx + x_zupt(15,0); - mby = mby + x_zupt(16,0); - mbz = mbz + x_zupt(17,0); - // Covariance update (Joseph form for numerical stability) + applyNavigationCorrection(x_zupt); Eigen::Matrix I_KH_zupt = Eigen::Matrix::Identity() - K_zupt * H_zupt; P = I_KH_zupt * P * I_KH_zupt.transpose() + @@ -473,122 +424,139 @@ void uNavINS::update(double dt_in, unsigned long TOW, double vn, double ve, doub } } - // Magnetometer measurement update (independent of GPS TOW) - static elapsedMicros mag_update_timer; - const float MAG_UPDATE_PERIOD_MS = 100.0f; // 10 Hz max - bool mag_update_ready = (mag_update_timer / 1000.0f) >= MAG_UPDATE_PERIOD_MS; + // Magnetometer: tilt-compensated heading at up to 10 Hz of filter time + const float MAG_UPDATE_PERIOD_S = 0.1f; + bool mag_update_ready = (last_mag_update_time < 0.0) || + ((filter_time - last_mag_update_time) >= MAG_UPDATE_PERIOD_S); bool mag_field_valid = std::isfinite(bn) && std::isfinite(be) && std::isfinite(bd); + bool mag_meas_valid = std::isfinite(hx) && std::isfinite(hy) && std::isfinite(hz); - if (mag_update_ready && mag_field_valid) { - mag_update_timer = 0; - // Convert measured mag from body frame to nT + if (mag_update_ready && mag_field_valid && mag_meas_valid) { + last_mag_update_time = filter_time; + C_N2B = quat2dcm(quat); + C_B2N = C_N2B.transpose(); float hx_meas = hx * 1000.0f; float hy_meas = hy * 1000.0f; float hz_meas = hz * 1000.0f; - // Expected mag field in NED frame (passed as parameters, already in nT) - Eigen::Matrix mag_ned; - mag_ned << bn, be, bd; - // Rotate expected NED field to body frame using current attitude DCM - mag_expected_body = C_N2B * mag_ned; - // ENHANCED MAGNETIC INTERFERENCE REJECTION - // Multiple gates to detect and reject magnetic interference: - // 1. Field magnitude gate (existing) - // 2. Inclination angle gate (VQF-inspired) - // 3. Temporal consistency gate (detect sudden changes) - // Gate 1: Field magnitude check + Eigen::Matrix mag_ned_exp; + mag_ned_exp << bn, be, bd; + mag_expected_body = C_N2B * mag_ned_exp; + float measured_field = sqrtf(hx_meas*hx_meas + hy_meas*hy_meas + hz_meas*hz_meas); - float expected_field = sqrtf(bn*bn + be*be + bd*bd); + float expected_field = mag_ned_exp.norm(); float field_ratio = (expected_field > 0.0f) ? (measured_field / expected_field) : 0.0f; bool mag_magnitude_ok = (field_ratio > 0.5f && field_ratio < 2.0f); - // Gate 2: Inclination angle check (VQF approach) - // Magnetic field inclination should be consistent (doesn't change quickly) - // Interference typically affects horizontal more than vertical component - float horizontal_meas = sqrtf(hx_meas*hx_meas + hy_meas*hy_meas); - float horizontal_exp = sqrtf(bn*bn + be*be); - float inclination_meas = atan2f(-hz_meas, horizontal_meas); // Note: -hz because body z is down - float inclination_exp = atan2f(-bd, horizontal_exp); - float inclination_error = fabsf(inclination_meas - inclination_exp); - bool mag_inclination_ok = (inclination_error < 0.35f); // ~20 degrees tolerance - // Gate 3: Temporal consistency check (detect transient interference) - static Eigen::Matrix mag_meas_filtered = Eigen::Matrix::Zero(); - static bool mag_filter_initialized = false; + + // Attitude gate: horizontal mag is only a heading measurement near level + const float MAG_ATT_LIMIT = 0.52f; // ~30 deg + bool mag_attitude_ok = (fabsf(phi) < MAG_ATT_LIMIT && fabsf(theta) < MAG_ATT_LIMIT); + + // Inclination in NED for both measured and expected + Eigen::Matrix mag_body_corr; + mag_body_corr << (hx_meas - mbx), (hy_meas - mby), (hz_meas - mbz); + Eigen::Matrix mag_ned_meas = C_B2N * mag_body_corr; + float Bh_meas = sqrtf(mag_ned_meas(0)*mag_ned_meas(0) + mag_ned_meas(1)*mag_ned_meas(1)); + float Bh_exp = sqrtf(bn*bn + be*be); + float inclination_meas = atan2f(mag_ned_meas(2), fmaxf(Bh_meas, 1.0f)); + float inclination_exp = atan2f(bd, fmaxf(Bh_exp, 1.0f)); + float inclination_error = fabsf(constrainAngle180(inclination_meas - inclination_exp)); + bool mag_inclination_ok = (inclination_error < 0.35f); + Eigen::Matrix mag_meas_current; mag_meas_current << hx_meas, hy_meas, hz_meas; if (!mag_filter_initialized) { mag_meas_filtered = mag_meas_current; mag_filter_initialized = true; } - // Compute innovation (difference from filtered value) Eigen::Matrix mag_innovation = mag_meas_current - mag_meas_filtered; float innovation_magnitude = mag_innovation.norm(); - // Update filtered magnetometer reading (low-pass filter) const float MAG_LPF_ALPHA = 0.1f; mag_meas_filtered = MAG_LPF_ALPHA * mag_meas_current + (1.0f - MAG_LPF_ALPHA) * mag_meas_filtered; - // Reject if sudden large change (likely transient interference like power lines, metal) - bool mag_temporally_stable = (innovation_magnitude < 10000.0f); // 10 µT threshold - // Combined gate: all checks must pass - bool mag_reasonable = mag_magnitude_ok && mag_inclination_ok && mag_temporally_stable; + bool mag_temporally_stable = (innovation_magnitude < 10000.0f); + + bool mag_reasonable = mag_magnitude_ok && mag_attitude_ok && mag_inclination_ok && mag_temporally_stable; if (mag_reasonable) { - // Yaw-only magnetometer update: compare horizontal mag direction - float mx_meas = hx_meas - mbx; - float my_meas = hy_meas - mby; - float mx_exp = mag_expected_body(0); - float my_exp = mag_expected_body(1); - // Compute signed yaw error between expected and measured horizontal vectors - float cross_z = mx_exp * my_meas - my_exp * mx_meas; - float dot_xy = mx_exp * mx_meas + my_exp * my_meas; - float yaw_error = constrainAngle180(atan2f(cross_z, dot_xy)); - // Innovation gate (3-sigma) using equivalent yaw noise - float sigma_yaw = (expected_field > 0.0f) ? (SIG_MAG / expected_field) : 0.0f; + float hx_c = hx_meas - mbx; + float hy_c = hy_meas - mby; + float hz_c = hz_meas - mbz; + Bxc = hx_c * cosf(theta) + (hy_c * sinf(phi) + hz_c * cosf(phi)) * sinf(theta); + Byc = hy_c * cosf(phi) - hz_c * sinf(phi); + float mag_heading = constrainAngle180(atan2f(-Byc, Bxc)); + float yaw_error = constrainAngle180(mag_heading - psi); + float sigma_yaw = (Bh_exp > 1.0f) ? (SIG_MAG / Bh_exp) : 0.0f; float innov_gate = 3.0f * sigma_yaw; if (sigma_yaw > 0.0f && fabsf(yaw_error) < innov_gate) { - // Measurement model: yaw error directly observes attitude yaw state (state 8) Eigen::Matrix H_mag; H_mag.setZero(); H_mag(0,8) = 1.0f; float R_mag = sigma_yaw * sigma_yaw; - // Kalman gain for yaw-only magnetometer update (18x1) float S_mag = (H_mag * P * H_mag.transpose())(0,0) + R_mag; Eigen::Matrix K_mag = (P * H_mag.transpose()) / S_mag; - // Covariance update (Joseph form for numerical stability) Eigen::Matrix I_KH_mag = Eigen::Matrix::Identity() - K_mag * H_mag; P = I_KH_mag * P * I_KH_mag.transpose() + K_mag * R_mag * K_mag.transpose(); P = 0.5f*(P + P.transpose()); - // State update with yaw-only measurement x = K_mag * yaw_error; - // Apply attitude correction from yaw-only magnetometer update - dq(0,0) = 1.0f; - dq(1,0) = x(6,0); - dq(2,0) = x(7,0); - dq(3,0) = x(8,0); - quat = qmult(quat,dq); - quat.normalize(); - // Obtain euler angles from quaternion - theta = asinf(-2.0f*(quat(1,0)*quat(3,0)-quat(0,0)*quat(2,0))); - phi = atan2f(2.0f*(quat(0,0)*quat(1,0)+quat(2,0)*quat(3,0)),1.0f-2.0f*(quat(1,0)*quat(1,0)+quat(2,0)*quat(2,0))); - psi = atan2f(2.0f*(quat(1,0)*quat(2,0)+quat(0,0)*quat(3,0)),1.0f-2.0f*(quat(2,0)*quat(2,0)+quat(3,0)*quat(3,0))); - // Simple horizontal mag bias estimator (yaw-only) - // Slowly adapt mbx/mby to reduce steady-state yaw error without affecting roll/pitch - const float MAG_BIAS_ALPHA = 0.01f; // small adaptation rate + applyNavigationCorrection(x); + const float MAG_BIAS_ALPHA = 0.01f; mbx += MAG_BIAS_ALPHA * (hx_meas - mag_expected_body(0) - mbx); mby += MAG_BIAS_ALPHA * (hy_meas - mag_expected_body(1) - mby); } } } - // ========================================================================== - // BAROMETRIC ALTITUDE FUSION (ArduPilot-inspired) - // Fuse barometric altitude when: - // 1. Barometer data is available and valid - // 2. GPS vertical accuracy is poor (>10m) or unavailable - // - // This provides better altitude estimation in GPS-degraded conditions - // (urban canyons, tree cover, etc.) + + // Weak accelerometer roll/pitch aid when specific force is ~1 g. + // Coordinated turns measure nG along body z, so they are excluded. + const float ACCEL_TILT_PERIOD_S = 0.1f; + bool tilt_rate_ok = (last_accel_tilt_time < 0.0) || + ((filter_time - last_accel_tilt_time) >= ACCEL_TILT_PERIOD_S); + float accel_norm_tilt = sqrtf(ax * ax + ay * ay + az * az); + bool near_1g = (fabsf(accel_norm_tilt - G) < 0.8f); + bool gyro_slow = (fabsf(p) < 0.15f && fabsf(q) < 0.15f && fabsf(r) < 0.15f); + if (tilt_rate_ok && near_1g && gyro_slow && fabsf(ax) <= G) { + last_accel_tilt_time = filter_time; + float theta_acc = asinf(ax / G); + float cos_theta_acc = cosf(theta_acc); + if (fabsf(cos_theta_acc) > 1e-3f) { + float ay_term = -ay / (G * cos_theta_acc); + if (fabsf(ay_term) <= 1.0f) { + float phi_acc = asinf(ay_term); + float roll_err = constrainAngle180(phi_acc - phi); + float pitch_err = constrainAngle180(theta_acc - theta); + const float SIG_TILT = 0.14f; // ~8 deg + float tilt_gate = 3.0f * SIG_TILT; + if (fabsf(roll_err) < tilt_gate && fabsf(pitch_err) < tilt_gate) { + Eigen::Matrix H_tilt; + H_tilt.setZero(); + H_tilt(0, 6) = 1.0f; + H_tilt(1, 7) = 1.0f; + Eigen::Matrix y_tilt; + y_tilt(0, 0) = roll_err; + y_tilt(1, 0) = pitch_err; + Eigen::Matrix R_tilt; + R_tilt.setZero(); + R_tilt(0, 0) = SIG_TILT * SIG_TILT; + R_tilt(1, 1) = SIG_TILT * SIG_TILT; + Eigen::Matrix S_tilt = H_tilt * P * H_tilt.transpose() + R_tilt; + Eigen::Matrix K_tilt = P * H_tilt.transpose() * S_tilt.inverse(); + Eigen::Matrix I_KH_tilt = + Eigen::Matrix::Identity() - K_tilt * H_tilt; + P = I_KH_tilt * P * I_KH_tilt.transpose() + + K_tilt * R_tilt * K_tilt.transpose(); + P = 0.5f * (P + P.transpose()); + applyNavigationCorrection(K_tilt * y_tilt); + } + } + } + } + bool has_valid_baro = (baro_pressure > 0.0f && std::isfinite(baro_pressure) && baro_pressure > 500.0f && baro_pressure < 1100.0f); bool vacc_available = (vacc > 0.0f && std::isfinite(vacc)); - bool gps_vert_poor = (!vacc_available || vacc > 10.0f); // GPS vacc >10m or unavailable - if (has_valid_baro && gps_vert_poor) { - // Compute barometric altitude + bool gps_vert_poor = (!vacc_available || vacc > 10.0f); + bool baro_rate_ok = (last_baro_update_time < 0.0) || + ((filter_time - last_baro_update_time) >= 0.2); + if (has_valid_baro && gps_vert_poor && baro_rate_ok) { + last_baro_update_time = filter_time; float baro_alt_m; if (baro_qnh > 0.0f && std::isfinite(baro_qnh)) { baro_alt_m = AltitudeCalculator::calculateQNH_m(baro_pressure, baro_qnh); @@ -596,34 +564,23 @@ void uNavINS::update(double dt_in, unsigned long TOW, double vn, double ve, doub baro_alt_m = AltitudeCalculator::calculateQNE_m(baro_pressure); } if (std::isfinite(baro_alt_m)) { - // Barometric altitude innovation - // Note: alt_ins is altitude MSL, baro_alt_m is also MSL (via QNH) or FL (via QNE) float alt_innovation = baro_alt_m - (float)alt_ins; - // Barometric altitude measurement noise - // Typical barometer: 1-3m std dev depending on conditions - const float BARO_NOISE_BASE = 2.0f; // 2m baseline - // Scale by atmospheric conditions (higher altitude = less accurate) - float alt_scale = 1.0f + fabsf((float)alt_ins) / 10000.0f; // +10% per 1000m + const float BARO_NOISE_BASE = 2.0f; + float alt_scale = 1.0f + fabsf((float)alt_ins) / 10000.0f; float baro_noise = BARO_NOISE_BASE * alt_scale; - // Innovation gate (3-sigma) for barometer float baro_innov_gate = 3.0f * baro_noise; if (fabsf(alt_innovation) < baro_innov_gate) { - // Measurement model: barometer observes altitude (down position, state 2) Eigen::Matrix H_baro; H_baro.setZero(); - H_baro(0,2) = -1.0f; // Negative because state 2 is "down" (positive down) + H_baro(0,2) = -1.0f; float R_baro = baro_noise * baro_noise; - // Kalman gain for barometric altitude update (18x1) float S_baro = (H_baro * P * H_baro.transpose())(0,0) + R_baro; Eigen::Matrix K_baro = (P * H_baro.transpose()) / S_baro; - // Covariance update (Joseph form) Eigen::Matrix I_KH_baro = Eigen::Matrix::Identity() - K_baro * H_baro; P = I_KH_baro * P * I_KH_baro.transpose() + K_baro * R_baro * K_baro.transpose(); P = 0.5f*(P + P.transpose()); - // State update with barometric altitude x = K_baro * alt_innovation; - // Apply altitude correction - alt_ins = alt_ins - x(2,0); // Subtract because down is positive + alt_ins = alt_ins - x(2,0); } } } @@ -886,8 +843,8 @@ Eigen::Matrix uNavINS::qmult(Eigen::Matrix p, Eigen::Matri // bound yaw angle between -180 and 180 float uNavINS::constrainAngle180(float dta) { - if(dta > M_PI) dta -= (M_PI*2.0f); - if(dta < -M_PI) dta += (M_PI*2.0f); + while (dta > M_PI) dta -= (M_PI * 2.0f); + while (dta < -M_PI) dta += (M_PI * 2.0f); return dta; } @@ -899,95 +856,137 @@ float uNavINS::constrainAngle360(float dta){ return dta; } -// ============================================================================= -// SENSOR DELAY COMPENSATION - IMU Buffer Implementation -// ============================================================================= - -// Add IMU sample to circular buffer -void uNavINS::addImuSample(double timestamp, float p, float q, float r, float ax, float ay, float az) { - imu_buffer[imu_buffer_head].timestamp = timestamp; - imu_buffer[imu_buffer_head].p = p; - imu_buffer[imu_buffer_head].q = q; - imu_buffer[imu_buffer_head].r = r; - imu_buffer[imu_buffer_head].ax = ax; - imu_buffer[imu_buffer_head].ay = ay; - imu_buffer[imu_buffer_head].az = az; - imu_buffer_head = (imu_buffer_head + 1) % IMU_BUFFER_SIZE; - if (imu_buffer_count < IMU_BUFFER_SIZE) { - imu_buffer_count++; +// Apply error-state correction to navigation states. Mag bias is LPF-only. +void uNavINS::applyNavigationCorrection(const Eigen::Matrix& dx) { + denom = (1.0 - (ECC2 * pow(sin(lat_ins), 2.0))); + denom = sqrt(denom * denom); + Re = EARTH_RADIUS / sqrt(denom); + Rn = EARTH_RADIUS * (1.0 - ECC2) / denom * sqrt(denom); + alt_ins = alt_ins - dx(2, 0); + lat_ins = lat_ins + dx(0, 0) / (Re + alt_ins); + lon_ins = lon_ins + dx(1, 0) / (Rn + alt_ins) / cos(lat_ins); + vn_ins = vn_ins + dx(3, 0); + ve_ins = ve_ins + dx(4, 0); + vd_ins = vd_ins + dx(5, 0); + dq(0, 0) = 1.0f; + dq(1, 0) = dx(6, 0); + dq(2, 0) = dx(7, 0); + dq(3, 0) = dx(8, 0); + quat = qmult(quat, dq); + quat.normalize(); + if (quat(0) < 0) { + quat = -1.0f * quat; } + float sin_theta = -2.0f * (quat(1, 0) * quat(3, 0) - quat(0, 0) * quat(2, 0)); + if (sin_theta > 1.0f) sin_theta = 1.0f; + if (sin_theta < -1.0f) sin_theta = -1.0f; + theta = asinf(sin_theta); + phi = atan2f(2.0f * (quat(0, 0) * quat(1, 0) + quat(2, 0) * quat(3, 0)), + 1.0f - 2.0f * (quat(1, 0) * quat(1, 0) + quat(2, 0) * quat(2, 0))); + psi = atan2f(2.0f * (quat(1, 0) * quat(2, 0) + quat(0, 0) * quat(3, 0)), + 1.0f - 2.0f * (quat(2, 0) * quat(2, 0) + quat(3, 0) * quat(3, 0))); + abx = abx + dx(9, 0); + aby = aby + dx(10, 0); + abz = abz + dx(11, 0); + gbx = gbx + dx(12, 0); + gby = gby + dx(13, 0); + gbz = gbz + dx(14, 0); } -// Compute scale factor for measurement noise based on GPS delay -// Longer delays mean the GPS measurement is more stale and less reliable +// ============================================================================= +// GPS interval noise scale +// ============================================================================= + float uNavINS::computeDelayScaleFactor(float delay_seconds) { - // Scale factor increases with delay - // delay = 0ms → scale = 1.0 (nominal) - // delay = 200ms → scale ≈ 2.0 - // delay = 500ms → scale ≈ 2.8 - // Formula: scale = 1 + sqrt(delay / 0.2s) - float scale = 1.0f + sqrtf(delay_seconds / 0.2f); - return scale; + return 1.0f + sqrtf(delay_seconds / 0.2f); } // ============================================================================= -// REST DETECTION IMPLEMENTATION (VQF-inspired) +// REST DETECTION (sliding window) // ============================================================================= -// Update rest detector with new IMU sample -// Returns true if device is detected to be at rest bool uNavINS::RestDetector::update(float gx, float gy, float gz, float ax, float ay, float az) { - // Welford's online variance algorithm for numerical stability - sample_count++; - // Update gyro statistics - Eigen::Matrix gyro_sample; - gyro_sample << gx, gy, gz; - Eigen::Matrix gyro_delta = gyro_sample - gyro_mean; - gyro_mean += gyro_delta / (float)sample_count; - Eigen::Matrix gyro_delta2 = gyro_sample - gyro_mean; - gyro_M2 = gyro_M2.array() + (gyro_delta.array() * gyro_delta2.array()); - // Update accel statistics - Eigen::Matrix accel_sample; - accel_sample << ax, ay, az; - Eigen::Matrix accel_delta = accel_sample - accel_mean; - accel_mean += accel_delta / (float)sample_count; - Eigen::Matrix accel_delta2 = accel_sample - accel_mean; - accel_M2 = accel_M2.array() + (accel_delta.array() * accel_delta2.array()); - // Need minimum samples to compute variance - const int MIN_SAMPLES = 30; // 0.5 seconds at 60 Hz - if (sample_count < MIN_SAMPLES) { + gx_buf[head] = gx; + gy_buf[head] = gy; + gz_buf[head] = gz; + ax_buf[head] = ax; + ay_buf[head] = ay; + az_buf[head] = az; + head = (head + 1) % WINDOW_SIZE; + if (count < WINDOW_SIZE) { + count++; + } + + const int MIN_SAMPLES = 30; + if (count < MIN_SAMPLES) { is_at_rest = false; return false; } - // Compute variance (M2 / (n-1)) - Eigen::Matrix gyro_variance = gyro_M2 / (float)(sample_count - 1); - Eigen::Matrix accel_variance = accel_M2 / (float)(sample_count - 1); - // Rest thresholds - const float GYRO_THRESHOLD_SQ = 0.0004f; // (0.02 rad/s)² ≈ (1 deg/s)² - const float ACCEL_THRESHOLD_SQ = 0.25f; // (0.5 m/s²)² - // Check if all axes are below threshold - bool gyro_still = (gyro_variance.array() < GYRO_THRESHOLD_SQ).all(); - bool accel_still = (accel_variance.array() < ACCEL_THRESHOLD_SQ).all(); - bool currently_still = gyro_still && accel_still; - // Require sustained stillness (hysteresis) - const int REST_SAMPLES_REQUIRED = 60; // 1 second at 60 Hz - const int MOVING_SAMPLES_EXIT = 10; // 0.17 seconds to exit rest + + float gx_mean = 0, gy_mean = 0, gz_mean = 0; + float ax_mean = 0, ay_mean = 0, az_mean = 0; + for (int i = 0; i < count; i++) { + gx_mean += gx_buf[i]; + gy_mean += gy_buf[i]; + gz_mean += gz_buf[i]; + ax_mean += ax_buf[i]; + ay_mean += ay_buf[i]; + az_mean += az_buf[i]; + } + float inv = 1.0f / (float)count; + gx_mean *= inv; gy_mean *= inv; gz_mean *= inv; + ax_mean *= inv; ay_mean *= inv; az_mean *= inv; + + float gx_var = 0, gy_var = 0, gz_var = 0; + float ax_var = 0, ay_var = 0, az_var = 0; + for (int i = 0; i < count; i++) { + float dgx = gx_buf[i] - gx_mean; + float dgy = gy_buf[i] - gy_mean; + float dgz = gz_buf[i] - gz_mean; + float dax = ax_buf[i] - ax_mean; + float day = ay_buf[i] - ay_mean; + float daz = az_buf[i] - az_mean; + gx_var += dgx * dgx; + gy_var += dgy * dgy; + gz_var += dgz * dgz; + ax_var += dax * dax; + ay_var += day * day; + az_var += daz * daz; + } + float inv_n1 = 1.0f / (float)(count - 1); + gx_var *= inv_n1; gy_var *= inv_n1; gz_var *= inv_n1; + ax_var *= inv_n1; ay_var *= inv_n1; az_var *= inv_n1; + + const float GYRO_THRESHOLD_SQ = 0.0004f; // (0.02 rad/s)^2 + const float ACCEL_THRESHOLD_SQ = 0.25f; // (0.5 m/s^2)^2 + bool gyro_still = (gx_var < GYRO_THRESHOLD_SQ) && (gy_var < GYRO_THRESHOLD_SQ) && (gz_var < GYRO_THRESHOLD_SQ); + bool accel_still = (ax_var < ACCEL_THRESHOLD_SQ) && (ay_var < ACCEL_THRESHOLD_SQ) && (az_var < ACCEL_THRESHOLD_SQ); + float gyro_mean_sq = gx_mean * gx_mean + gy_mean * gy_mean + gz_mean * gz_mean; + const float GYRO_MEAN_SQ_LIMIT = 0.0025f; // (0.05 rad/s)^2 ≈ 3 deg/s + bool gyro_quiet = (gyro_mean_sq < GYRO_MEAN_SQ_LIMIT); + bool currently_still = gyro_still && accel_still && gyro_quiet; + + const int REST_SAMPLES_REQUIRED = 60; + const int MOVING_SAMPLES_EXIT = 10; if (currently_still) { + motion_samples = 0; rest_samples++; + if (rest_samples > REST_SAMPLES_REQUIRED * 2) { + rest_samples = REST_SAMPLES_REQUIRED * 2; + } if (rest_samples >= REST_SAMPLES_REQUIRED) { is_at_rest = true; } } else { - rest_samples--; - if (rest_samples < -MOVING_SAMPLES_EXIT) { + rest_samples = 0; + motion_samples++; + if (motion_samples > MOVING_SAMPLES_EXIT * 2) { + motion_samples = MOVING_SAMPLES_EXIT * 2; + } + if (motion_samples >= MOVING_SAMPLES_EXIT) { is_at_rest = false; - // Reset statistics when motion detected - reset(); } } - // Clamp rest_samples - if (rest_samples < -MOVING_SAMPLES_EXIT) rest_samples = -MOVING_SAMPLES_EXIT; - if (rest_samples > REST_SAMPLES_REQUIRED * 2) rest_samples = REST_SAMPLES_REQUIRED * 2; return is_at_rest; } @@ -1123,12 +1122,15 @@ bool uNavINS::checkCovarianceHealth() { return (filter_health_status == 0); } +bool uNavINS::isInitialized() { + return initialized; +} + bool uNavINS::isHealthy() { - return checkCovarianceHealth(); + return filter_health_status == 0; } int uNavINS::getHealthStatus() { - checkCovarianceHealth(); return filter_health_status; } diff --git a/fusionml/src/uNavINS.h b/fusionml/src/uNavINS.h index ef1e1ba..2ba03c0 100644 --- a/fusionml/src/uNavINS.h +++ b/fusionml/src/uNavINS.h @@ -42,21 +42,16 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SO // ENHANCEMENTS FROM BASELINE CHANGELOG // ============================================================================= // -// Enhancement 1: Full 3D magnetometer measurement with WMM expected field comparison +// Enhancement 1: Tilt-compensated magnetometer heading with WMM // ------------------------------------------------------------------ // Expected Earth field in NED from World Magnetic Model (XYZgeomag) -// Measured field (after subtracting estimated mag bias) compared to expected field rotated into body frame -// Yaw error derived from comparison drives scalar heading update +// Measured field (after subtracting estimated mag bias) tilt-compensated to horizontal +// Scalar yaw error drives heading update only (roll/pitch from IMU/GPS) // -// Enhancement 2: Sensor Delay Compensation +// Enhancement 2: GPS rate-aware measurement noise // ------------------------------------------------------------------ -// GPS latency (100-300ms) compensation via: -// 1. IMU sample buffering (40 samples, 667ms) -// 2. Adaptive GPS delay estimation (50-500ms range) -// 3. Delay-aware measurement noise scaling -// -// Key innovation: Scale GPS measurement noise based on delay, reducing trust in stale measurements. -// Simpler and more stable than state replay approach. Supports variable GPS rates (0.5-10 Hz) automatically. +// Scale GPS measurement noise from observed update interval. +// This is not delayed-state replay; it only reduces trust in slow/stale GPS. // // Enhancement 3: GPS Adaptive Noise // ---------------------------------------------------------- @@ -117,7 +112,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SO // * Phone in car mount at stoplight (engine vibration okay) // * Phone on desk (truly motionless) // - When active: observe velocity = [0,0,0] at 5 Hz with R = (0.1 m/s)² -// - Safety gate: only applies if 3D velocity < 3 m/s (prevents ZUPT during motion) +// - Safety gate: INS speed < 3 m/s when GPS is stale; when GPS confirms a stop, +// ZUPT may still pull back a diverged INS (up to ~80 m/s) // - Tighter noise (0.05 m/s) when variance-based rest also detected // // 2. VARIANCE-BASED REST DETECTION (Supplementary - VQF-inspired) @@ -264,6 +260,7 @@ class uNavINS { float getPitchStd_rad(); float getYawStd_rad(); // Filter health monitoring + bool isInitialized(); bool isHealthy(); int getHealthStatus(); // Returns health code: 0=healthy, 1=warning, 2=error, 3=critical // Rest detection and ZUPT @@ -378,47 +375,33 @@ class uNavINS { Eigen::Matrix quat = Eigen::Matrix::Zero(); // dquat Eigen::Matrix dq = Eigen::Matrix::Zero(); - // Measurement innovation (9 measurements: 3 pos, 3 vel, 3 mag) - Eigen::Matrix y = Eigen::Matrix::Zero(); - // Measurement noise covariance (9x9: GPS pos/vel + mag) - Eigen::Matrix R = Eigen::Matrix::Zero(); + // GPS measurement innovation (6 measurements: 3 pos, 3 vel) + Eigen::Matrix y = Eigen::Matrix::Zero(); + // GPS measurement noise covariance + Eigen::Matrix R = Eigen::Matrix::Zero(); // State correction vector Eigen::Matrix x = Eigen::Matrix::Zero(); - // Kalman Gain (18 states x 9 measurements) - Eigen::Matrix K = Eigen::Matrix::Zero(); - // Measurement matrix (9 measurements x 18 states) - Eigen::Matrix H = Eigen::Matrix::Zero(); + // Kalman Gain (18 states x 6 GPS measurements) + Eigen::Matrix K = Eigen::Matrix::Zero(); + // GPS measurement matrix + Eigen::Matrix H = Eigen::Matrix::Zero(); // Expected mag field in body frame (computed from C_N2B * mag_ned) Eigen::Matrix mag_expected_body = Eigen::Matrix::Zero(); - // ========================================================================== - // SENSOR DELAY COMPENSATION - IMU Buffer - // ========================================================================== - // Buffer to store recent IMU samples (reserved for future advanced fusion) - // Currently populated but not used by delay-aware noise scaling implementation. - // Available for future enhancements requiring state replay. - struct ImuSample { - double timestamp; // Seconds since filter initialization - float p, q, r; // Gyro rates (rad/s, raw - bias correction applied during use) - float ax, ay, az; // Accel (m/s², raw - bias correction applied during use) - }; - - static const int IMU_BUFFER_SIZE = 40; // ~667ms at 60 Hz - ImuSample imu_buffer[IMU_BUFFER_SIZE]; - int imu_buffer_head = 0; // Write position (next slot to fill) - int imu_buffer_count = 0; // Number of valid samples in buffer double filter_time = 0.0; // Time since initialization (seconds) - // GPS delay parameters - static constexpr float GPS_DELAY_NOMINAL = 0.200f; // 200ms typical GPS latency - static constexpr float GPS_DELAY_MAX = 0.500f; // Maximum expected GPS delay - static constexpr float GPS_DELAY_MIN = 0.050f; // Minimum expected GPS delay - float gps_delay_estimate = GPS_DELAY_NOMINAL; // Adaptive GPS delay estimate - double last_gps_fusion_time = 0.0; // Time of last GPS update + // GPS interval-based measurement-noise scale (not a delayed-state replay) + static constexpr float GPS_DELAY_NOMINAL = 0.200f; + static constexpr float GPS_DELAY_MAX = 0.500f; + static constexpr float GPS_DELAY_MIN = 0.050f; + float gps_delay_estimate = GPS_DELAY_NOMINAL; + double last_gps_fusion_time = 0.0; + bool gps_has_updated = false; + float last_gps_ground_speed = 0.0f; + bool have_gps_speed = false; - // Helper functions for IMU buffer and delayed fusion - void addImuSample(double timestamp, float p, float q, float r, float ax, float ay, float az); float computeDelayScaleFactor(float delay_seconds); + void applyNavigationCorrection(const Eigen::Matrix& dx); // ========================================================================== // COVARIANCE HEALTH MONITORING @@ -432,25 +415,27 @@ class uNavINS { // ========================================================================== // REST DETECTION (VQF-inspired) // ========================================================================== - // Detect when device is stationary for improved bias estimation + // Detect when device is stationary for improved bias estimation. + // Variance is computed over a sliding 1 s window so rest can exit after a long sit. struct RestDetector { - // Gyro variance tracking (rad²/s²) - Eigen::Matrix gyro_mean; - Eigen::Matrix gyro_M2; // Sum of squared deviations - // Accel variance tracking (m²/s⁴) - Eigen::Matrix accel_mean; - Eigen::Matrix accel_M2; - int sample_count = 0; + static const int WINDOW_SIZE = 60; // 1 second at 60 Hz + float gx_buf[WINDOW_SIZE] = {}; + float gy_buf[WINDOW_SIZE] = {}; + float gz_buf[WINDOW_SIZE] = {}; + float ax_buf[WINDOW_SIZE] = {}; + float ay_buf[WINDOW_SIZE] = {}; + float az_buf[WINDOW_SIZE] = {}; + int head = 0; + int count = 0; int rest_samples = 0; + int motion_samples = 0; bool is_at_rest = false; void reset() { - gyro_mean.setZero(); - gyro_M2.setZero(); - accel_mean.setZero(); - accel_M2.setZero(); - sample_count = 0; + head = 0; + count = 0; rest_samples = 0; + motion_samples = 0; is_at_rest = false; } @@ -458,13 +443,23 @@ class uNavINS { }; RestDetector rest_detector; + // Magnetometer update state (must live on the instance so resetAhrs() clears it) + double last_mag_update_time = -1.0; + Eigen::Matrix mag_meas_filtered = Eigen::Matrix::Zero(); + bool mag_filter_initialized = false; + + // Barometer update rate limit + double last_baro_update_time = -1.0; + // Accelerometer tilt aid (when specific force is ~1 g) + double last_accel_tilt_time = -1.0; + // ========================================================================== // SPEED-BASED ZUPT (Zero-Velocity Update) // ========================================================================== // Detect sustained low ground speed for stationary scenarios float low_speed_timer = 0.0f; // seconds below speed threshold bool zupt_active = false; // ZUPT currently enabled - float last_zupt_time = 0.0f; // for rate limiting (5 Hz) + float last_zupt_time = -1.0f; // for rate limiting (5 Hz) // skew symmetric Eigen::Matrix sk(Eigen::Matrix w); diff --git a/fusionml/tests/README.md b/fusionml/tests/README.md index b3a8f11..4937464 100644 --- a/fusionml/tests/README.md +++ b/fusionml/tests/README.md @@ -1,8 +1,8 @@ # fusionml Tests -Standalone C++ unit tests for the `fusionml` components (AltitudeCalculator, JsonRecorder, FlightPhaseDetector, XYZgeomag). +Standalone C++ tests for `fusionml` (uNavINS, AltitudeCalculator, JsonRecorder, FlightPhaseDetector, XYZgeomag). -## Run via Python wrapper (recommended) +## Run From the repo root: @@ -18,22 +18,16 @@ Options: - `--build-only` — build all tests but do not run them - `-v`, `--verbose` — print compiler and runner commands -Requires `clang++` or `g++` and zlib development headers (e.g. `brew install zlib` on macOS). +Requires `g++` or `clang++` and zlib development headers. -## Manual build and run (macOS/Linux) +## uNavINS (`test_unavins.cpp`) -From the repo root: - -```bash -clang++ -std=c++14 -Ifusionml/src fusionml/tests/test_altitude_calculator.cpp \ - fusionml/src/AltitudeCalculator.cpp -o /tmp/test_altitude && /tmp/test_altitude +The filter is driven like iOS: 60 Hz IMU, GPS frozen between Core Location fixes (1 Hz on the ground, 5 Hz in the air), first sample `dt = 0`. Specific force is z-down (`az = -G` when level). -clang++ -std=c++14 -Ifusionml/src fusionml/tests/test_json_recorder.cpp \ - fusionml/src/JsonRecorder.cpp -lz -o /tmp/test_json && /tmp/test_json - -clang++ -std=c++14 -Ifusionml/src fusionml/tests/test_flight_phase_detector.cpp \ - fusionml/src/FlightPhaseDetector.cpp -o /tmp/test_flight_phase && /tmp/test_flight_phase - -clang++ -std=c++14 -Ifusionml/src fusionml/tests/test_xyzgeomag.cpp \ - -o /tmp/test_xyzgeomag && /tmp/test_xyzgeomag -``` +| Suite | What it covers | +| --- | --- | +| Init / safety | `dt = 0` first sample, reject `|ax| > g`, missing mag, independent instances | +| Static | Desk rest + ZUPT, handheld still (tremor, no rest), pickup | +| Walking | 1.4 m/s pedestrian, 90° corner, mag spike | +| Car | 20 m/s cruise, accel, brake, level 90° and 360° turns, stoplight ZUPT, urban GPS | +| Aircraft | Cruise, accel/decel, climb, descent, coordinated / climbing / descending turns, 360° horizon, baro, GPS outage | diff --git a/fusionml/tests/run_fusion_tests.py b/fusionml/tests/run_fusion_tests.py index 93d740f..9507145 100644 --- a/fusionml/tests/run_fusion_tests.py +++ b/fusionml/tests/run_fusion_tests.py @@ -89,11 +89,12 @@ def _find_repo_root() -> Path: # ----------------------------------------------------------------------------- def _get_compiler() -> str: - for name in ("clang++", "g++"): + # Prefer g++ on Linux (libstdc++ headers). clang++ may lack a C++ stdlib here. + for name in ("g++", "clang++"): path = shutil.which(name) if path: return path - raise RuntimeError("No C++ compiler found (tried clang++, g++)") + raise RuntimeError("No C++ compiler found (tried g++, clang++)") # ----------------------------------------------------------------------------- # Build & run @@ -172,10 +173,18 @@ def main() -> int: print(f"fusionml: {FUSIONML}") built: list[tuple[str, Path]] = [] + build_failed: list[str] = [] for name, test_cpp, sources, extra in TESTS: b = _build_one(compiler, name, test_cpp, sources, extra, args.verbose) if b is not None: built.append((name, b)) + else: + build_failed.append(name) + + if build_failed: + print(f"fusionml tests: {len(build_failed)} failed to build — {', '.join(build_failed)}", file=sys.stderr) + if not built: + return 1 if not built: print("No tests built.", file=sys.stderr) @@ -185,7 +194,7 @@ def main() -> int: print(f"Built {len(built)} test(s) under {BUILD_DIR}") return 0 - failed: list[str] = [] + failed: list[str] = list(build_failed) for name, binary in built: ok, out = _run_one(binary, args.verbose) if out.strip(): diff --git a/fusionml/tests/test_unavins.cpp b/fusionml/tests/test_unavins.cpp index 3f6a0f4..c4ed217 100644 --- a/fusionml/tests/test_unavins.cpp +++ b/fusionml/tests/test_unavins.cpp @@ -1,27 +1,21 @@ /* - * Comprehensive unit tests for uNavINS Extended Kalman Filter. - * - * Core Functionality Tests: - * - Basic initialization and convergence - * - Straight and level flight - * - Coordinated turns (proper roll-in/out dynamics) - * - Sustained turn horizon drift validation - * - GPS outages and recovery - * - Bias estimation - * - * Enhancement Tests (v2.0): - * - Filter health monitoring (4-level status) - * - GPS adaptive noise (varying accuracy scenarios) - * - Barometer fusion (poor GPS vacc activation) - * - Rest detection (accelerated bias convergence) - * - Enhanced magnetic rejection (3-gate interference detection) - * - Sensor delay compensation (variable GPS rates) + * uNavINS tests for the iOS GPS/INS plant. + * + * IMU at 60 Hz (Core Motion). GPS at 1 Hz on the ground and 5 Hz in the air + * (Core Location). First IMU sample uses dt = 0, matching NativeAhrs. + * + * Suites: + * Init/safety — first sample, bad accel, missing mag, independent instances + * Static — desk rest, handheld ZUPT, pickup + * Walking — pedestrian speed, corner, mag interference + * Car — cruise, level yaw turn, stoplight, urban GPS + * Aircraft — level, coordinated turn, 360° horizon, climb/baro, GPS outage + * Outputs — vertical/horizontal FPA, ground track, NED vel, lat/lon */ +#include #include #include -#include -#include #include "uNavINS.h" @@ -42,7 +36,7 @@ static void expectNear(float actual, float expected, float tolerance, const char float diff = std::fabs(actual - expected); if (diff > tolerance) { g_failed++; - std::printf("✗ %s (actual=%.4f expected=%.4f diff=%.4f tol=%.4f)\n", + std::printf("✗ %s (actual=%.4f expected=%.4f diff=%.4f tol=%.4f)\n", message, actual, expected, diff, tolerance); } else { g_passed++; @@ -50,14 +44,14 @@ static void expectNear(float actual, float expected, float tolerance, const char } } -static void expectNearDeg(float actual_rad, float expected_deg, float tolerance_deg, const char *message) { - float actual_deg = actual_rad * 180.0f / M_PI; +static void expectNearDeg(float actual_rad, float expected_deg, float tolerance_deg, + const char *message) { + float actual_deg = actual_rad * 180.0f / (float)M_PI; float diff = std::fabs(actual_deg - expected_deg); - // Handle angle wrapping if (diff > 180.0f) diff = 360.0f - diff; if (diff > tolerance_deg) { g_failed++; - std::printf("✗ %s (actual=%.2f° expected=%.2f° diff=%.2f°)\n", + std::printf("✗ %s (actual=%.2f° expected=%.2f° diff=%.2f°)\n", message, actual_deg, expected_deg, diff); } else { g_passed++; @@ -65,1082 +59,1054 @@ static void expectNearDeg(float actual_rad, float expected_deg, float tolerance_ } } -// Constants static const float G = 9.807f; -static const float DEG_TO_RAD = M_PI / 180.0f; -static const float RAD_TO_DEG = 180.0f / M_PI; +static const float DEG_TO_RAD = (float)M_PI / 180.0f; +static const float RAD_TO_DEG = 180.0f / (float)M_PI; +static const float IMU_DT = 1.0f / 60.0f; +static const int IMU_HZ = 60; +static const double EARTH_R = 6378137.0; +// NED field matching World::bn/be/bd (nT / 1000). Used for body-frame mag. +static const float MAG_N_UT = 19.0f; +static const float MAG_E_UT = -1.0f; +static const float MAG_D_UT = 45.0f; + +// Rotate NED mag into body (X fwd, Y right, Z down). C_N2B = Rx(roll)*Ry(pitch)*Rz(yaw) +// with Ry chosen so a level pitched-up IMU reads ax = G*sin(pitch). +static void nedMagToBody(float heading_deg, float pitch_deg, float roll_deg, + float *mx, float *my, float *mz) { + float yaw = heading_deg * DEG_TO_RAD; + float th = pitch_deg * DEG_TO_RAD; + float ph = roll_deg * DEG_TO_RAD; + float cy = std::cos(yaw), sy = std::sin(yaw); + float ct = std::cos(th), st = std::sin(th); + float cp = std::cos(ph), sp = std::sin(ph); + float n = MAG_N_UT, e = MAG_E_UT, d = MAG_D_UT; + float xn = cy * n + sy * e; + float yn = -sy * n + cy * e; + float zn = d; + float xp = ct * xn - st * zn; + float yp = yn; + float zp = st * xn + ct * zn; + *mx = xp; + *my = cp * yp + sp * zp; + *mz = -sp * yp + cp * zp; +} -// Simulate sensor readings for a given state struct SimulatedSensors { - float ax, ay, az; // Accelerometer (m/s²) - float gx, gy, gz; // Gyroscope (rad/s) - float mx, my, mz; // Magnetometer (µT) + float ax, ay, az; + float gx, gy, gz; + float mx, my, mz; }; -// Generate accelerometer reading for level flight (only gravity) -static SimulatedSensors generateLevelFlightSensors(float heading_deg) { +static float wrap360(float deg) { + while (deg >= 360.0f) deg -= 360.0f; + while (deg < 0.0f) deg += 360.0f; + return deg; +} + +// Level, z-down specific force. Mag is the World NED field rotated into body. +static SimulatedSensors levelSensors(float heading_deg, float yaw_rate_rads = 0.0f, + float ax = 0.0f, float ay = 0.0f) { SimulatedSensors s; - // Level flight: accel measures only gravity (0, 0, +G in body frame when level) - s.ax = 0.0f; - s.ay = 0.0f; - s.az = G; - // No rotation + s.ax = ax; + s.ay = ay; + s.az = -G; s.gx = 0.0f; s.gy = 0.0f; - s.gz = 0.0f; - // Simplified magnetometer (assuming inclination ~60° down, pointing north) - float heading_rad = heading_deg * DEG_TO_RAD; - float field_h = 20.0f; // Horizontal component µT - float field_z = 40.0f; // Vertical component µT (down positive) - s.mx = field_h * std::cos(heading_rad); - s.my = field_h * std::sin(heading_rad); - s.mz = field_z; + s.gz = yaw_rate_rads; + nedMagToBody(heading_deg, 0.0f, 0.0f, &s.mx, &s.my, &s.mz); return s; } -// Generate sensors for coordinated turn -// In a coordinated turn: -// - Bank angle creates lateral component of lift to provide centripetal force -// - The accelerometer measures load factor along body z-axis -// - Gyro measures the angular rates in body frame -static SimulatedSensors generateCoordinatedTurnSensors( - float heading_deg, - float bank_angle_deg, - float turn_rate_deg_s, - float airspeed_ms) { +static SimulatedSensors coordinatedTurnSensors(float heading_deg, float bank_deg, + float turn_rate_deg_s) { SimulatedSensors s; - - float bank_rad = bank_angle_deg * DEG_TO_RAD; + float bank_rad = bank_deg * DEG_TO_RAD; float turn_rate_rad = turn_rate_deg_s * DEG_TO_RAD; - - // In a coordinated turn, the load factor n = 1/cos(bank) - // The accelerometer measures the total apparent gravity along body z float load_factor = 1.0f / std::cos(bank_rad); - - // Body frame accelerations in coordinated turn: - // ax = 0 (no longitudinal accel, assuming constant speed) - // ay = 0 (coordinated turn - ball centered, no slip) - // az = load_factor * G (along body z-axis) s.ax = 0.0f; s.ay = 0.0f; - s.az = load_factor * G; - - // Gyro rates for STEADY coordinated turn: - // The heading rate (psi_dot) is the turn rate in NED frame - // For steady turn: roll_dot = 0, pitch_dot = 0 - // - // Body rates from Euler rates: - // p = phi_dot - psi_dot * sin(theta) - // q = theta_dot * cos(phi) + psi_dot * cos(theta) * sin(phi) - // r = -theta_dot * sin(phi) + psi_dot * cos(theta) * cos(phi) - // - // For steady turn with zero pitch: phi_dot=0, theta_dot=0, theta=0: - // p = 0 - // q = psi_dot * sin(phi) - // r = psi_dot * cos(phi) - // - s.gx = 0.0f; // Roll rate = 0 in steady turn - s.gy = turn_rate_rad * std::sin(bank_rad); // Pitch rate component - s.gz = turn_rate_rad * std::cos(bank_rad); // Yaw rate component - - // Magnetometer in body frame - // Earth's field in NED: (Bn, Be, Bd) with Bd positive (pointing down) - // Transform to body using current heading and bank angle - float heading_rad = heading_deg * DEG_TO_RAD; - - // Simplified magnetic field (typical Northern hemisphere) - float Bn = 20.0f; // North component (µT) - float Be = 0.0f; // East component (µT) - simplified - float Bd = 40.0f; // Down component (µT) - - // First rotate by heading (about NED down axis) - float mx_n = Bn * std::cos(heading_rad) + Be * std::sin(heading_rad); - float my_n = -Bn * std::sin(heading_rad) + Be * std::cos(heading_rad); - float mz_n = Bd; - - // Then rotate by bank angle (about body x-axis, forward) - float cb = std::cos(bank_rad); - float sb = std::sin(bank_rad); - s.mx = mx_n; - s.my = my_n * cb + mz_n * sb; - s.mz = -my_n * sb + mz_n * cb; - + s.az = -load_factor * G; + s.gx = 0.0f; + s.gy = turn_rate_rad * std::sin(bank_rad); + s.gz = turn_rate_rad * std::cos(bank_rad); + nedMagToBody(heading_deg, 0.0f, bank_deg, &s.mx, &s.my, &s.mz); return s; } -// Test fixture helper -struct INSTestFixture { +// Shared IMU/GPS plant. GPS lat/lon/vel freeze between fixes, as on iOS. +struct World { uNavINS filter; unsigned long tow_ms = 0; - double lat_rad = 51.5 * DEG_TO_RAD; // London + double lat_rad = 51.5 * DEG_TO_RAD; double lon_rad = -0.1 * DEG_TO_RAD; - double alt_m = 1000.0; - double vn_ms = 50.0; // 50 m/s north (about 100 kts) - double ve_ms = 0.0; - double vd_ms = 0.0; - - // WMM field for London (approximate, nT) + double alt_m = 50.0; + double vn = 0.0; + double ve = 0.0; + double vd = 0.0; + float heading_deg = 0.0f; + float hacc = 5.0f; + float vacc = 8.0f; + float sacc = 0.5f; + float baro_hpa = -1.0f; + float qnh = 1013.25f; float bn = 19000.0f; float be = -1000.0f; float bd = 45000.0f; - - void initialize() { - SimulatedSensors s = generateLevelFlightSensors(0.0f); - filter.update(0.016, tow_ms, vn_ms, ve_ms, vd_ms, - lat_rad, lon_rad, alt_m, - s.gx, s.gy, s.gz, - s.ax, s.ay, s.az, - s.mx, s.my, s.mz, - bn, be, bd); - tow_ms += 16; + int gps_every = IMU_HZ; // 1 Hz + int k = 0; + bool begun = false; + double gps_vn = 0.0, gps_ve = 0.0, gps_vd = 0.0; + double gps_lat = 51.5 * DEG_TO_RAD; + double gps_lon = -0.1 * DEG_TO_RAD; + double gps_alt = 50.0; + + void set_gps_hz(int hz) { + gps_every = IMU_HZ / hz; + if (gps_every < 1) gps_every = 1; } - - void runStraightAndLevel(int iterations, float heading_deg = 0.0f) { - SimulatedSensors s = generateLevelFlightSensors(heading_deg); - for (int i = 0; i < iterations; i++) { - // Update GPS position based on velocity - double dt = 0.016; // 60 Hz - lat_rad += vn_ms * dt / 6378137.0; - lon_rad += ve_ms * dt / (6378137.0 * std::cos(lat_rad)); - alt_m -= vd_ms * dt; - - filter.update(dt, tow_ms, vn_ms, ve_ms, vd_ms, - lat_rad, lon_rad, alt_m, - s.gx, s.gy, s.gz, - s.ax, s.ay, s.az, - s.mx, s.my, s.mz, - bn, be, bd); - tow_ms += 16; - } + + void set_course(float speed_ms, float hdg_deg) { + heading_deg = wrap360(hdg_deg); + float h = heading_deg * DEG_TO_RAD; + vn = speed_ms * std::cos(h); + ve = speed_ms * std::sin(h); } - - // Simulate rolling into a turn (transition from level to banked) - void rollIntoTurn(float target_bank_deg, float roll_rate_deg_s, float turn_rate_deg_s) { - float heading_deg = filter.getHeading_rad() * RAD_TO_DEG; - float airspeed = std::sqrt(vn_ms*vn_ms + ve_ms*ve_ms); - float current_bank_deg = 0.0f; - float dt = 0.016f; - - // Roll into the turn over several iterations - int roll_iters = (int)(std::fabs(target_bank_deg) / roll_rate_deg_s / dt); - float bank_step = target_bank_deg / roll_iters; - - for (int i = 0; i < roll_iters; i++) { - current_bank_deg += bank_step; - - // Partially developed turn - turn rate builds as bank increases - float partial_turn_rate = turn_rate_deg_s * (current_bank_deg / target_bank_deg); - - // Sensors during roll-in - SimulatedSensors s; - float bank_rad = current_bank_deg * DEG_TO_RAD; - float turn_rad = partial_turn_rate * DEG_TO_RAD; - - // Load factor builds during roll-in - float load_factor = 1.0f / std::cos(bank_rad); - s.ax = 0.0f; - s.ay = 0.0f; // Coordinated throughout - s.az = load_factor * G; - - // Gyro during roll-in: has roll rate! - float roll_rate_rad = bank_step / dt * DEG_TO_RAD; // Roll rate - s.gx = roll_rate_rad; // Rolling into the turn - s.gy = turn_rad * std::sin(bank_rad); // Pitch component - s.gz = turn_rad * std::cos(bank_rad); // Yaw component - - // Simple magnetometer - float heading_rad = heading_deg * DEG_TO_RAD; - float cb = std::cos(bank_rad); - float sb = std::sin(bank_rad); - s.mx = 20.0f * std::cos(heading_rad); - s.my = 20.0f * std::sin(heading_rad) * cb + 40.0f * sb; - s.mz = -20.0f * std::sin(heading_rad) * sb + 40.0f * cb; - - // Update heading - heading_deg += partial_turn_rate * dt; - if (heading_deg > 360.0f) heading_deg -= 360.0f; - if (heading_deg < 0.0f) heading_deg += 360.0f; - - // Update velocity - float new_heading_rad = heading_deg * DEG_TO_RAD; - vn_ms = airspeed * std::cos(new_heading_rad); - ve_ms = airspeed * std::sin(new_heading_rad); - - // Update position - lat_rad += vn_ms * dt / 6378137.0; - lon_rad += ve_ms * dt / (6378137.0 * std::cos(lat_rad)); - - filter.update(dt, tow_ms, vn_ms, ve_ms, vd_ms, - lat_rad, lon_rad, alt_m, - s.gx, s.gy, s.gz, - s.ax, s.ay, s.az, - s.mx, s.my, s.mz, - bn, be, bd); - tow_ms += 16; - } + + // Velocity along a ground track that may differ from heading (crab / sideslip). + void set_velocity_track(float speed_ms, float track_deg) { + float t = wrap360(track_deg) * DEG_TO_RAD; + vn = speed_ms * std::cos(t); + ve = speed_ms * std::sin(t); + } + + void begin() { + gps_vn = vn; + gps_ve = ve; + gps_vd = vd; + gps_lat = lat_rad; + gps_lon = lon_rad; + gps_alt = alt_m; + SimulatedSensors s = levelSensors(heading_deg); + // iOS first IMU sample has dt = 0 + filter.update(0.0, tow_ms, gps_vn, gps_ve, gps_vd, gps_lat, gps_lon, gps_alt, + s.gx, s.gy, s.gz, s.ax, s.ay, s.az, s.mx, s.my, s.mz, + bn, be, bd, hacc, vacc, sacc, baro_hpa, qnh); + begun = true; } - - void runCoordinatedTurn(int iterations, float bank_deg, float turn_rate_deg_s) { - float heading_deg = filter.getHeading_rad() * RAD_TO_DEG; - float airspeed = std::sqrt(vn_ms*vn_ms + ve_ms*ve_ms); - - for (int i = 0; i < iterations; i++) { - SimulatedSensors s = generateCoordinatedTurnSensors( - heading_deg, bank_deg, turn_rate_deg_s, airspeed); - - double dt = 0.016; - - // Update heading based on turn rate - heading_deg += turn_rate_deg_s * dt; - if (heading_deg > 360.0f) heading_deg -= 360.0f; - if (heading_deg < 0.0f) heading_deg += 360.0f; - - // Update velocity vector based on new heading - float heading_rad = heading_deg * DEG_TO_RAD; - vn_ms = airspeed * std::cos(heading_rad); - ve_ms = airspeed * std::sin(heading_rad); - - // Update position - lat_rad += vn_ms * dt / 6378137.0; - lon_rad += ve_ms * dt / (6378137.0 * std::cos(lat_rad)); - - filter.update(dt, tow_ms, vn_ms, ve_ms, vd_ms, - lat_rad, lon_rad, alt_m, - s.gx, s.gy, s.gz, - s.ax, s.ay, s.az, - s.mx, s.my, s.mz, - bn, be, bd); - tow_ms += 16; + + void step(const SimulatedSensors &s) { + if (!begun) begin(); + lat_rad += vn * (double)IMU_DT / EARTH_R; + lon_rad += ve * (double)IMU_DT / (EARTH_R * std::cos(lat_rad)); + alt_m -= vd * (double)IMU_DT; + if (k % gps_every == 0) { + tow_ms += (unsigned long)((double)gps_every * IMU_DT * 1000.0 + 0.5); + gps_vn = vn; + gps_ve = ve; + gps_vd = vd; + gps_lat = lat_rad; + gps_lon = lon_rad; + gps_alt = alt_m; } + k++; + filter.update((double)IMU_DT, tow_ms, gps_vn, gps_ve, gps_vd, gps_lat, gps_lon, + gps_alt, s.gx, s.gy, s.gz, s.ax, s.ay, s.az, s.mx, s.my, s.mz, + bn, be, bd, hacc, vacc, sacc, baro_hpa, qnh); } - - // Roll out of turn back to level - void rollOutOfTurn(float current_bank_deg, float roll_rate_deg_s, float initial_turn_rate_deg_s = 6.0f) { - float heading_deg = filter.getHeading_rad() * RAD_TO_DEG; - float airspeed = std::sqrt(vn_ms*vn_ms + ve_ms*ve_ms); - float dt = 0.016f; - - int roll_iters = (int)(std::fabs(current_bank_deg) / roll_rate_deg_s / dt); - float bank_step = -current_bank_deg / roll_iters; // Negative to roll out - float bank_deg = current_bank_deg; - - for (int i = 0; i < roll_iters; i++) { - bank_deg += bank_step; - - // Sensors during roll-out - SimulatedSensors s; - float bank_rad = bank_deg * DEG_TO_RAD; - - // Load factor reduces as bank reduces - float load_factor = (std::fabs(bank_deg) > 1.0f) ? (1.0f / std::cos(bank_rad)) : 1.0f; - s.ax = 0.0f; - s.ay = 0.0f; - s.az = load_factor * G; - - // Gyro during roll-out - float roll_rate_rad = bank_step / dt * DEG_TO_RAD; - // Turn rate decays proportionally to bank reduction - float bank_fraction = std::fabs(bank_deg / current_bank_deg); - float decaying_turn_rate = initial_turn_rate_deg_s * bank_fraction * DEG_TO_RAD; - - s.gx = roll_rate_rad; // Rolling out - s.gy = decaying_turn_rate * std::sin(bank_rad); // Decaying pitch component - s.gz = decaying_turn_rate * std::cos(bank_rad); // Decaying yaw component - - // Update heading with decaying turn - heading_deg += initial_turn_rate_deg_s * bank_fraction * dt; - if (heading_deg > 360.0f) heading_deg -= 360.0f; - - // Simple magnetometer - float heading_rad = heading_deg * DEG_TO_RAD; - float cb = std::cos(bank_rad); - float sb = std::sin(bank_rad); - s.mx = 20.0f * std::cos(heading_rad); - s.my = 20.0f * std::sin(heading_rad) * cb + 40.0f * sb; - s.mz = -20.0f * std::sin(heading_rad) * sb + 40.0f * cb; - - // Update velocity based on heading - float new_heading_rad = heading_deg * DEG_TO_RAD; - vn_ms = airspeed * std::cos(new_heading_rad); - ve_ms = airspeed * std::sin(new_heading_rad); - - lat_rad += vn_ms * dt / 6378137.0; - lon_rad += ve_ms * dt / (6378137.0 * std::cos(lat_rad)); - - filter.update(dt, tow_ms, vn_ms, ve_ms, vd_ms, - lat_rad, lon_rad, alt_m, - s.gx, s.gy, s.gz, - s.ax, s.ay, s.az, - s.mx, s.my, s.mz, - bn, be, bd); - tow_ms += 16; + + void run_level(int n, float ax = 0.0f, float ay = 0.0f, float yaw_rate = 0.0f) { + for (int i = 0; i < n; i++) { + step(levelSensors(heading_deg, yaw_rate, ax, ay)); } } + + float speed() { + return std::sqrt((float)(filter.getVelNorth_ms() * filter.getVelNorth_ms() + + filter.getVelEast_ms() * filter.getVelEast_ms())); + } + + float speed_3d() { + double vn_i = filter.getVelNorth_ms(); + double ve_i = filter.getVelEast_ms(); + double vd_i = filter.getVelDown_ms(); + return std::sqrt((float)(vn_i * vn_i + ve_i * ve_i + vd_i * vd_i)); + } + + float fpa_deg() { return filter.getFlightPathAngle_rad() * RAD_TO_DEG; } + float hfpa_deg() { return filter.getHorizontalFlightPathAngle_rad() * RAD_TO_DEG; } + float track_deg() { return filter.getGroundTrack_rad() * RAD_TO_DEG; } }; +static SimulatedSensors walkSensors(float heading_deg, int i, float yaw_rate = 0.0f) { + SimulatedSensors s = levelSensors(heading_deg, yaw_rate); + float step_phase = (float)i * 2.0f * (float)M_PI * 1.8f / (float)IMU_HZ; + s.az += 1.2f * std::sin(step_phase); + s.ax += 0.35f * std::sin(step_phase); + s.gx += 0.03f * std::sin(step_phase * 0.5f); + s.gy += 0.02f * std::cos(step_phase * 0.4f); + return s; +} + +static SimulatedSensors pitchedSensors(float heading_deg, float pitch_deg, + float pitch_rate = 0.0f, float yaw_rate = 0.0f) { + SimulatedSensors s = levelSensors(heading_deg, yaw_rate); + float th = pitch_deg * DEG_TO_RAD; + s.ax = G * std::sin(th); + s.ay = 0.0f; + s.az = -G * std::cos(th); + s.gy = pitch_rate; + s.gz = yaw_rate * std::cos(th); + nedMagToBody(heading_deg, pitch_deg, 0.0f, &s.mx, &s.my, &s.mz); + return s; +} + +static SimulatedSensors carVibeSensors(float heading_deg, int i, float yaw_rate = 0.0f, + float ax = 0.0f, float ay = 0.0f) { + SimulatedSensors s = levelSensors(heading_deg, yaw_rate, ax, ay); + s.gx += 0.04f * std::sin((float)i * 0.7f); + s.gy += 0.035f * std::cos((float)i * 0.55f); + s.gz += 0.03f * std::sin((float)i * 0.4f); + s.ax += 0.6f * std::sin((float)i * 0.9f); + s.ay += 0.5f * std::cos((float)i * 0.8f); + return s; +} + // ============================================================================ -// Test Cases +// Init / safety // ============================================================================ -static void testInitialization() { - std::printf("\n=== Test: Initialization ===\n"); - - INSTestFixture fix; - fix.initialize(); - - // Run a few iterations to let filter settle - fix.runStraightAndLevel(60); // 1 second - - // Check initial attitude is approximately level - expectNearDeg(fix.filter.getRoll_rad(), 0.0f, 5.0f, - "Initial roll near zero"); - expectNearDeg(fix.filter.getPitch_rad(), 0.0f, 5.0f, - "Initial pitch near zero"); - - // Check velocity matches GPS - expectNear(fix.filter.getVelNorth_ms(), fix.vn_ms, 1.0f, - "Velocity north matches GPS"); +static void testInitFirstSampleDtZero() { + std::printf("\n=== Init: first iOS sample dt=0 ===\n"); + uNavINS filter; + SimulatedSensors s = levelSensors(0.0f); + filter.update(0.0, 0, 0, 0, 0, 0.9, 0.1, 20, + s.gx, s.gy, s.gz, s.ax, s.ay, s.az, s.mx, s.my, s.mz, + 19000, -1000, 45000); + expectTrue(filter.isInitialized(), "Initializes on dt=0 first sample"); + expectNearDeg(filter.getRoll_rad(), 0.0f, 5.0f, "Roll near zero at init"); + expectNearDeg(filter.getPitch_rad(), 0.0f, 5.0f, "Pitch near zero at init"); } -static void testStraightAndLevelFlight() { - std::printf("\n=== Test: Straight and Level Flight ===\n"); - - INSTestFixture fix; - fix.initialize(); - - // Run 10 seconds of straight and level flight - fix.runStraightAndLevel(600); - - // Attitude should remain level - expectNearDeg(fix.filter.getRoll_rad(), 0.0f, 2.0f, - "Roll remains level after 10s"); - expectNearDeg(fix.filter.getPitch_rad(), 0.0f, 2.0f, - "Pitch remains level after 10s"); - - // Position uncertainty should have decreased - float pos_std = fix.filter.getPositionNorthStd_m(); - expectTrue(pos_std < 10.0f, "Position uncertainty decreased"); - - // Attitude uncertainty should be reasonable - float att_std = fix.filter.getRollStd_rad() * RAD_TO_DEG; - expectTrue(att_std < 5.0f, "Attitude uncertainty reasonable"); +static void testInitRejectsBadAccel() { + std::printf("\n=== Init: reject |ax| > g ===\n"); + uNavINS filter; + SimulatedSensors s = levelSensors(0.0f); + s.ax = 20.0f; + filter.update(0.016, 0, 0, 0, 0, 0.9, 0.1, 20, + s.gx, s.gy, s.gz, s.ax, s.ay, s.az, s.mx, s.my, s.mz, + 19000, -1000, 45000); + expectTrue(!filter.isInitialized(), "Does not initialize on |ax| > g"); } -static void testCoordinatedTurn() { - std::printf("\n=== Test: Coordinated Turn (with proper roll-in/out dynamics) ===\n"); - - INSTestFixture fix; - fix.initialize(); - - // First establish straight and level - fix.runStraightAndLevel(120); // 2 seconds - - float roll_before_turn = fix.filter.getRoll_rad() * RAD_TO_DEG; - std::printf(" Roll before turn: %.2f°\n", roll_before_turn); - - // Bank angle and turn rate for coordinated turn - // For 50 m/s and 30° bank: turn_rate = g*tan(bank)/v ≈ 6.7°/s - float bank_angle = 30.0f; - float turn_rate = 6.0f; // deg/s - float roll_rate = 15.0f; // deg/s roll rate during entry/exit - - // Roll into the turn (this gives the gyro roll rate cues) - std::printf(" Rolling into turn...\n"); - fix.rollIntoTurn(bank_angle, roll_rate, turn_rate); - - float roll_after_entry = fix.filter.getRoll_rad() * RAD_TO_DEG; - std::printf(" Roll after entry: %.2f° (target: %.2f°)\n", roll_after_entry, bank_angle); - - // Hold steady coordinated turn for 5 seconds - fix.runCoordinatedTurn(300, bank_angle, turn_rate); - - float roll_during_turn = fix.filter.getRoll_rad() * RAD_TO_DEG; - std::printf(" Roll during steady turn: %.2f°\n", roll_during_turn); - - // KEY TEST: Roll should track the actual bank angle - expectNearDeg(fix.filter.getRoll_rad(), bank_angle, 8.0f, - "Roll tracks bank angle during coordinated turn"); - - // Roll out of the turn - std::printf(" Rolling out of turn...\n"); - fix.rollOutOfTurn(bank_angle, roll_rate, turn_rate); - - // Stabilize in straight and level (longer settling time) - fix.runStraightAndLevel(300); // 5 seconds to allow filter to settle - - float roll_after_turn = fix.filter.getRoll_rad() * RAD_TO_DEG; - std::printf(" Roll after turn: %.2f°\n", roll_after_turn); - - // Roll should return to approximately zero (relaxed tolerance for settling) - expectNearDeg(fix.filter.getRoll_rad(), 0.0f, 8.0f, - "Roll returns to level after turn"); +static void testInitZeroMag() { + std::printf("\n=== Init: missing magnetometer ===\n"); + uNavINS filter; + SimulatedSensors s = levelSensors(0.0f); + filter.update(0.0, 16, 0, 0, 0, 0.9, 0.1, 20, + s.gx, s.gy, s.gz, s.ax, s.ay, s.az, 0.0f, 0.0f, 0.0f, + 19000, -1000, 45000); + expectTrue(filter.isInitialized(), "Initializes without mag"); + expectTrue(std::isfinite(filter.getHeading_rad()), "Heading finite without mag"); + expectTrue(std::isfinite(filter.getRoll_rad()) && std::isfinite(filter.getPitch_rad()), + "Roll/pitch finite without mag"); } -static void testSustainedTurnHorizonDrift() { - std::printf("\n=== Test: Sustained Turn Horizon Drift ===\n"); - - INSTestFixture fix; - fix.initialize(); - fix.runStraightAndLevel(120); - - // Perform a 360° turn (60 seconds at 6°/s) - float bank_angle = 30.0f; - float turn_rate = 6.0f; - float roll_rate = 15.0f; - - std::printf(" Rolling into 360° turn at %.0f° bank...\n", bank_angle); - - // Roll into the turn first - fix.rollIntoTurn(bank_angle, roll_rate, turn_rate); - - float roll_after_entry = fix.filter.getRoll_rad() * RAD_TO_DEG; - std::printf(" Roll after entry: %.2f° (target: %.2f°)\n", roll_after_entry, bank_angle); - - // Sample roll at various points during the turn - float max_roll_error = 0.0f; - for (int seg = 0; seg < 6; seg++) { - fix.runCoordinatedTurn(600, bank_angle, turn_rate); // 10 seconds each - float roll = fix.filter.getRoll_rad() * RAD_TO_DEG; - float error = std::fabs(roll - bank_angle); - if (error > max_roll_error) max_roll_error = error; - std::printf(" Segment %d: Roll=%.2f° (error=%.2f°)\n", seg+1, roll, error); +static void testInitHeadingCardinals() { + std::printf("\n=== Init: cardinal magnetic headings ===\n"); + const float headings[] = {0.0f, 90.0f, 180.0f, 270.0f}; + for (int i = 0; i < 4; i++) { + float hdg = headings[i]; + uNavINS filter; + SimulatedSensors s = levelSensors(hdg); + filter.update(0.0, 0, 0, 0, 0, 0.9, 0.1, 20, + s.gx, s.gy, s.gz, s.ax, s.ay, s.az, s.mx, s.my, s.mz, + MAG_N_UT * 1000.0f, MAG_E_UT * 1000.0f, MAG_D_UT * 1000.0f); + char msg[80]; + std::snprintf(msg, sizeof(msg), "Init heading at %.0f°", hdg); + expectNearDeg(filter.getHeading_rad(), hdg, 8.0f, msg); } - - // The key metric: maximum roll error during sustained turn - // A well-functioning filter should track bank angle with < 10° error - expectTrue(max_roll_error < 15.0f, - "Maximum roll error during 360° turn < 15°"); } -static void testGPSOutage() { - std::printf("\n=== Test: GPS Outage ===\n"); - - INSTestFixture fix; - fix.initialize(); - fix.runStraightAndLevel(120); - - // Record state before outage - double lat_before = fix.filter.getLatitude_rad(); - float roll_before = fix.filter.getRoll_rad(); - - // Simulate GPS outage by not incrementing TOW (filter won't do GPS update) - unsigned long frozen_tow = fix.tow_ms; - SimulatedSensors s = generateLevelFlightSensors(0.0f); - - for (int i = 0; i < 300; i++) { // 5 seconds of outage - double dt = 0.016; - fix.lat_rad += fix.vn_ms * dt / 6378137.0; - fix.lon_rad += fix.ve_ms * dt / (6378137.0 * std::cos(fix.lat_rad)); - - // Don't update TOW - this prevents GPS measurement update - fix.filter.update(dt, frozen_tow, fix.vn_ms, fix.ve_ms, fix.vd_ms, - fix.lat_rad, fix.lon_rad, fix.alt_m, - s.gx, s.gy, s.gz, - s.ax, s.ay, s.az, - s.mx, s.my, s.mz, - fix.bn, fix.be, fix.bd); +static void testInitIndependentInstances() { + std::printf("\n=== Init: independent instances ===\n"); + World a; + a.set_course(0.0f, 0.0f); + a.run_level(120); + World b; + b.set_course(0.0f, 0.0f); + b.begin(); + expectTrue(b.filter.isInitialized(), "Second instance initializes independently"); + expectTrue(std::isfinite(b.filter.getHeading_rad()), "Second instance heading is finite"); +} + +// ============================================================================ +// Static +// ============================================================================ + +static void testStaticDesk() { + std::printf("\n=== Static: phone on a desk ===\n"); + World w; + w.alt_m = 20.0; + w.set_course(0.0f, 0.0f); + const float gyro_bias = 0.02f; // ~1.1 deg/s + + for (int i = 0; i < 300; i++) { // 5 s + SimulatedSensors s = levelSensors(0.0f); + s.gx = gyro_bias + 0.001f * std::sin((float)i * 0.1f); + s.gy = 0.001f * std::cos((float)i * 0.1f); + s.gz = 0.001f * std::sin((float)i * 0.15f); + w.step(s); } - - // Attitude should remain stable during GPS outage (IMU-only) - float roll_after = fix.filter.getRoll_rad(); - expectNearDeg(roll_after, roll_before * RAD_TO_DEG, 3.0f, - "Roll stable during 5s GPS outage"); - - // Position uncertainty should have grown - float pos_std = fix.filter.getPositionNorthStd_m(); - expectTrue(pos_std > 5.0f, "Position uncertainty grew during outage"); + + expectTrue(w.filter.isAtRest(), "Rest detection on a still desk"); + expectTrue(w.filter.isZuptActive(), "ZUPT after 3 s of GPS speed 0"); + expectTrue(w.speed_3d() < 0.5f, "Velocity stays near zero on a desk"); + expectNearDeg(w.filter.getRoll_rad(), 0.0f, 5.0f, "Roll level on a desk"); + expectNearDeg(w.filter.getPitch_rad(), 0.0f, 5.0f, "Pitch level on a desk"); + expectTrue(w.filter.getHealthStatus() <= 1, "Health OK while static"); + expectNear(w.fpa_deg(), 0.0f, 1.0f, "Vertical FPA is zero when stationary"); + expectNear(w.hfpa_deg(), 0.0f, 1.0f, "Horizontal FPA is zero when stationary"); } -static void testBiasEstimation() { - std::printf("\n=== Test: Bias Estimation ===\n"); - - INSTestFixture fix; - fix.initialize(); - - // Add known bias to sensors - float true_gyro_bias = 0.01f; // rad/s (about 0.5°/s) - float true_accel_bias = 0.1f; // m/s² - - SimulatedSensors s = generateLevelFlightSensors(0.0f); - s.gx += true_gyro_bias; - s.ax += true_accel_bias; - - // Run for 30 seconds with biased sensors - for (int i = 0; i < 1800; i++) { - double dt = 0.016; - fix.lat_rad += fix.vn_ms * dt / 6378137.0; - fix.filter.update(dt, fix.tow_ms, fix.vn_ms, fix.ve_ms, fix.vd_ms, - fix.lat_rad, fix.lon_rad, fix.alt_m, - s.gx, s.gy, s.gz, - s.ax, s.ay, s.az, - s.mx, s.my, s.mz, - fix.bn, fix.be, fix.bd); - fix.tow_ms += 16; +static void testStaticHandheld() { + std::printf("\n=== Static: standing, phone in hand ===\n"); + World w; + w.set_course(0.0f, 0.0f); + + bool zupt_at_2s = false; + bool zupt_at_4s = false; + for (int i = 0; i < 300; i++) { // 5 s + SimulatedSensors s = levelSensors(0.0f); + s.gx += 0.03f * std::sin((float)i * 0.2f); + s.gy += 0.02f * std::cos((float)i * 0.3f); + s.gz += 0.01f * std::sin((float)i * 0.15f); + s.ax += 0.8f * std::sin((float)i * 0.1f); + s.ay += 0.6f * std::cos((float)i * 0.12f); + w.step(s); + if (i == 120) zupt_at_2s = w.filter.isZuptActive(); + if (i == 240) zupt_at_4s = w.filter.isZuptActive(); } - - // Check if biases are being estimated (should converge toward true values) - float est_gyro_bias_x = fix.filter.getGyroBiasX_rads(); - float est_accel_bias_x = fix.filter.getAccelBiasX_mss(); - - std::printf(" True gyro bias X: %.5f rad/s, Estimated: %.5f rad/s\n", - true_gyro_bias, est_gyro_bias_x); - std::printf(" True accel bias X: %.3f m/s², Estimated: %.3f m/s²\n", - true_accel_bias, est_accel_bias_x); - - // Bias estimates should be moving toward true values - // (may not fully converge without longer observation) - expectTrue(std::fabs(est_gyro_bias_x) > 0.001f, - "Gyro bias estimate is non-zero"); - - // Attitude should still be reasonable despite biased sensors - expectNearDeg(fix.filter.getRoll_rad(), 0.0f, 10.0f, - "Roll reasonable despite sensor bias"); + + expectTrue(!zupt_at_2s, "ZUPT does not fire before 3 s"); + expectTrue(zupt_at_4s, "ZUPT fires while handheld and GPS says stopped"); + expectTrue(!w.filter.isAtRest(), "Hand tremor is not variance-based rest"); + expectTrue(w.speed_3d() < 1.0f, "Handheld ZUPT keeps speed small"); } -static void testHealthMonitoring() { - std::printf("\n=== Test: Filter Health Monitoring ===\n"); - - INSTestFixture fix; - fix.initialize(); - - // Normal operation should be healthy - fix.runStraightAndLevel(60); - - int health_status = fix.filter.getHealthStatus(); - std::printf(" Health status: %d (0=healthy, 1=warning, 2=error, 3=critical)\n", health_status); - std::printf(" Position std: N=%.2fm E=%.2fm D=%.2fm\n", - fix.filter.getPositionNorthStd_m(), - fix.filter.getPositionEastStd_m(), - fix.filter.getPositionDownStd_m()); - std::printf(" Velocity std: N=%.2fm/s E=%.2fm/s D=%.2fm/s\n", - fix.filter.getVelocityNorthStd_ms(), - fix.filter.getVelocityEastStd_ms(), - fix.filter.getVelocityDownStd_ms()); - std::printf(" Attitude std: R=%.2f° P=%.2f° Y=%.2f°\n", - fix.filter.getRollStd_rad() * RAD_TO_DEG, - fix.filter.getPitchStd_rad() * RAD_TO_DEG, - fix.filter.getYawStd_rad() * RAD_TO_DEG); - - // Health should be healthy or warning (not error/critical) in normal operation - expectTrue(health_status <= 1, "Filter healthy or warning during normal operation"); +static void testStaticPickup() { + std::printf("\n=== Static: pick up the phone ===\n"); + World w; + w.set_course(0.0f, 0.0f); + w.run_level(120); + expectTrue(w.filter.isAtRest(), "At rest before pickup"); + + for (int i = 0; i < 60; i++) { + SimulatedSensors s = levelSensors(0.0f); + s.gx = 0.15f; + s.gy = 0.10f; + s.gz = 0.05f; + w.vn = 0.3; + w.step(s); + } + expectTrue(!w.filter.isAtRest(), "Rest exits when the phone is picked up"); } -static void testGpsAdaptiveNoise() { - std::printf("\n=== Test: GPS Adaptive Noise ===\n"); - - INSTestFixture fix; - fix.initialize(); - - // Test 1: Good GPS (3m accuracy) - should converge well - std::printf(" Phase 1: Good GPS (3m accuracy)\n"); - for (int i = 0; i < 300; i++) { // 5 seconds - SimulatedSensors s = generateLevelFlightSensors(0.0f); - fix.filter.update(0.01667, fix.tow_ms, 50.0, 0.0, 0.0, - fix.lat_rad, fix.lon_rad, 1000.0, - s.gx, s.gy, s.gz, s.ax, s.ay, s.az, - s.mx, s.my, s.mz, fix.bn, fix.be, fix.bd, - 3.0f, 5.0f, 0.5f); // Good accuracy - fix.tow_ms += 16; +// ============================================================================ +// Walking +// ============================================================================ + +static void testWalkingStraight() { + std::printf("\n=== Walking: straight at pedestrian pace ===\n"); + World w; + w.hacc = 6.0f; + w.vacc = 10.0f; + w.sacc = 0.8f; + w.set_course(1.4f, 0.0f); + + for (int i = 0; i < 600; i++) { // 10 s + w.step(walkSensors(w.heading_deg, i)); } - float pos_std_good = fix.filter.getPositionNorthStd_m(); - std::printf(" Position std with good GPS: %.2fm\n", pos_std_good); - - // Test 2: Poor GPS (20m accuracy) - should have larger uncertainty - std::printf(" Phase 2: Poor GPS (20m accuracy)\n"); - INSTestFixture fix2; - fix2.initialize(); - for (int i = 0; i < 300; i++) { - SimulatedSensors s = generateLevelFlightSensors(0.0f); - fix2.filter.update(0.01667, fix2.tow_ms, 50.0, 0.0, 0.0, - fix2.lat_rad, fix2.lon_rad, 1000.0, - s.gx, s.gy, s.gz, s.ax, s.ay, s.az, - s.mx, s.my, s.mz, fix2.bn, fix2.be, fix2.bd, - 20.0f, 30.0f, 2.0f); // Poor accuracy - fix2.tow_ms += 16; + + expectTrue(!w.filter.isZuptActive(), "ZUPT stays off while walking"); + expectTrue(!w.filter.isAtRest(), "Walking is not rest"); + expectNear(w.speed(), 1.4f, 0.8f, "Ground speed tracks ~1.4 m/s"); + expectNearDeg(w.filter.getRoll_rad(), 0.0f, 12.0f, "Roll roughly level while walking"); + expectNearDeg(w.filter.getPitch_rad(), 0.0f, 12.0f, "Pitch roughly level while walking"); + expectTrue(w.filter.getHealthStatus() <= 2, "Health not critical while walking"); + expectNearDeg(w.filter.getGroundTrack_rad(), 0.0f, 25.0f, "Ground track follows walking heading"); +} + +static void testWalkingCorner() { + std::printf("\n=== Walking: 90° street corner ===\n"); + World w; + w.set_course(1.4f, 0.0f); + for (int i = 0; i < 180; i++) { + w.step(walkSensors(w.heading_deg, i)); } - float pos_std_poor = fix2.filter.getPositionNorthStd_m(); - std::printf(" Position std with poor GPS: %.2fm\n", pos_std_poor); - - // Poor GPS should have higher uncertainty (filter trusts it less) - expectTrue(pos_std_poor > pos_std_good * 1.5f, "Poor GPS increases position uncertainty"); - - // Test 3: Transition from good to poor GPS - std::printf(" Phase 3: Transition good->poor GPS\n"); - INSTestFixture fix3; - fix3.initialize(); - - // Start with good GPS - for (int i = 0; i < 150; i++) { - SimulatedSensors s = generateLevelFlightSensors(0.0f); - fix3.filter.update(0.01667, fix3.tow_ms, 50.0, 0.0, 0.0, - fix3.lat_rad, fix3.lon_rad, 1000.0, - s.gx, s.gy, s.gz, s.ax, s.ay, s.az, - s.mx, s.my, s.mz, fix3.bn, fix3.be, fix3.bd, - 3.0f, 5.0f, 0.5f); // Good - fix3.tow_ms += 16; + + const float yaw_rate = 45.0f * DEG_TO_RAD; // 90° in 2 s + for (int i = 0; i < 120; i++) { + w.heading_deg = wrap360(w.heading_deg + 45.0f * IMU_DT); + w.set_course(1.4f, w.heading_deg); + w.step(walkSensors(w.heading_deg, i, yaw_rate)); } - float before_transition = fix3.filter.getPositionNorthStd_m(); - - // Transition to poor GPS - for (int i = 0; i < 150; i++) { - SimulatedSensors s = generateLevelFlightSensors(0.0f); - fix3.filter.update(0.01667, fix3.tow_ms, 50.0, 0.0, 0.0, - fix3.lat_rad, fix3.lon_rad, 1000.0, - s.gx, s.gy, s.gz, s.ax, s.ay, s.az, - s.mx, s.my, s.mz, fix3.bn, fix3.be, fix3.bd, - 25.0f, 40.0f, 3.0f); // Poor - fix3.tow_ms += 16; + for (int i = 0; i < 180; i++) { + w.step(walkSensors(w.heading_deg, i)); } - float after_transition = fix3.filter.getPositionNorthStd_m(); - - std::printf(" Before transition: %.2fm, After: %.2fm\n", before_transition, after_transition); - expectTrue(after_transition > before_transition, "Uncertainty grows when GPS degrades"); + + expectNearDeg(w.filter.getHeading_rad(), 90.0f, 25.0f, "Heading follows a walking turn"); + expectNearDeg(w.filter.getRoll_rad(), 0.0f, 15.0f, "Roll stays near level around a corner"); + expectTrue(!w.filter.isZuptActive(), "ZUPT stays off around a walking corner"); } -static void testBarometerFusion() { - std::printf("\n=== Test: Barometer Fusion ===\n"); - - // Test 1: Barometer activates when GPS vacc is poor - std::printf(" Phase 1: Poor GPS vertical accuracy (barometer should activate)\n"); - INSTestFixture fix; - fix.initialize(); - - double start_alt = 1000.0; - double baro_alt = 1000.0; - - // Simulate flight with poor GPS vacc but good barometer - for (int i = 0; i < 600; i++) { // 10 seconds - SimulatedSensors s = generateLevelFlightSensors(0.0f); - - // Simulate slow climb (1 m/s) - double time = i * 0.01667; - double true_alt = start_alt + time * 1.0; - - // GPS altitude is noisy (poor vacc) - double gps_alt_noise = (i % 2 == 0) ? 5.0 : -5.0; // ±5m noise - double gps_alt = true_alt + gps_alt_noise; - - // Barometer is smooth and accurate (typical 2m std) - baro_alt = true_alt + 0.5 * std::sin(i * 0.1); // Small smooth error - - // Convert altitude to pressure (standard atmosphere) - float pressure_hpa = 1013.25f * std::exp(-baro_alt / 8430.0f); - - fix.filter.update(0.01667, fix.tow_ms, 50.0, 0.0, -1.0, // vd = -1 m/s (climbing) - fix.lat_rad, fix.lon_rad, gps_alt, - s.gx, s.gy, s.gz, s.ax, s.ay, s.az, - s.mx, s.my, s.mz, fix.bn, fix.be, fix.bd, - 3.0f, 25.0f, 0.5f, // Good hacc, poor vacc (triggers baro) - pressure_hpa, 1013.25f); // Barometer data - fix.tow_ms += 16; +static void testWalkingMagInterference() { + std::printf("\n=== Walking: pass a metal object ===\n"); + World w; + w.set_course(1.4f, 0.0f); + for (int i = 0; i < 120; i++) { + w.step(walkSensors(0.0f, i)); } - - // Filter altitude should be close to true altitude (smoother than noisy GPS) - double filter_alt = fix.filter.getAltitude_m(); - double expected_alt = start_alt + 10.0; // 10 seconds * 1 m/s - std::printf(" Expected: %.1fm, Filter: %.1fm\n", expected_alt, filter_alt); - expectNear((float)filter_alt, (float)expected_alt, 3.0f, "Barometer fusion smooths altitude"); - - // Test 2: Barometer should NOT activate with good GPS vacc - std::printf(" Phase 2: Good GPS vertical accuracy (barometer should NOT activate)\n"); - INSTestFixture fix2; - fix2.initialize(); - - for (int i = 0; i < 300; i++) { - SimulatedSensors s = generateLevelFlightSensors(0.0f); - float pressure_hpa = 1013.25f * std::exp(-1000.0 / 8430.0f); - - fix2.filter.update(0.01667, fix2.tow_ms, 50.0, 0.0, 0.0, - fix2.lat_rad, fix2.lon_rad, 1000.0, - s.gx, s.gy, s.gz, s.ax, s.ay, s.az, - s.mx, s.my, s.mz, fix2.bn, fix2.be, fix2.bd, - 3.0f, 5.0f, 0.5f, // Good vacc (baro won't activate) - pressure_hpa, 1013.25f); - fix2.tow_ms += 16; + float heading_before = w.filter.getHeading_rad(); + + for (int i = 0; i < 60; i++) { + SimulatedSensors s = walkSensors(0.0f, i); + s.mx *= 3.0f; + s.my *= 3.0f; + s.mz *= 3.0f; + w.step(s); + } + for (int i = 0; i < 120; i++) { + w.step(walkSensors(0.0f, i)); } - - std::printf(" Barometer doesn't interfere when GPS vacc is good\n"); - expectTrue(true, "Filter stable with good GPS vacc"); + + float delta = std::fabs(w.filter.getHeading_rad() - heading_before) * RAD_TO_DEG; + if (delta > 180.0f) delta = 360.0f - delta; + std::printf(" Heading change through 3x mag: %.2f°\n", delta); + expectTrue(delta < 20.0f, "Heading stable through mag spike"); } -static void testRestDetection() { - std::printf("\n=== Test: Rest Detection ===\n"); - - INSTestFixture fix; - fix.initialize(); - - // Phase 1: Stationary with small gyro bias - std::printf(" Phase 1: Device stationary (rest detection should activate)\n"); - const float gyro_bias = 0.02f; // 0.02 rad/s = ~1.1 deg/s - - // Remain stationary for 2 seconds (rest detection needs 1s to activate) - for (int i = 0; i < 120; i++) { // 2 seconds at 60 Hz - // Stationary: zero motion except gyro bias - float ax = 0.0f; - float ay = 0.0f; - float az = G; // Only gravity - float gx = gyro_bias + 0.001f * std::sin(i * 0.1); // Small noise - float gy = 0.001f * std::cos(i * 0.1); - float gz = 0.001f * std::sin(i * 0.15); - - SimulatedSensors s = generateLevelFlightSensors(0.0f); - s.ax = ax; - s.ay = ay; - s.az = az; - s.gx = gx; - s.gy = gy; - s.gz = gz; - - fix.filter.update(0.01667, fix.tow_ms, 0.0, 0.0, 0.0, // No velocity - fix.lat_rad, fix.lon_rad, 1000.0, - s.gx, s.gy, s.gz, s.ax, s.ay, s.az, - s.mx, s.my, s.mz, fix.bn, fix.be, fix.bd); - fix.tow_ms += 16; - - // Check rest detection after 1.5 seconds - if (i == 90) { - bool at_rest = fix.filter.isAtRest(); - std::printf(" Rest detected after 1.5s: %s\n", at_rest ? "YES" : "NO"); - expectTrue(at_rest, "Rest detection activates after sustained stillness"); - } +// ============================================================================ +// Car +// ============================================================================ + +static void testCarCruise() { + std::printf("\n=== Car: cruise at 20 m/s ===\n"); + World w; + w.alt_m = 40.0; + w.hacc = 4.0f; + w.set_course(20.0f, 0.0f); + for (int i = 0; i < 600; i++) { // 10 s + w.step(carVibeSensors(w.heading_deg, i)); } - - // Check gyro bias estimation improved - float estimated_bias_x = fix.filter.getGyroBiasX_rads(); - std::printf(" True bias: %.4f rad/s, Estimated: %.4f rad/s\n", gyro_bias, estimated_bias_x); - // Bias estimation is improved but not perfect - rest detection accelerates convergence - // The key is that rest was detected, bias estimation happens over longer timescales - expectTrue(fix.filter.isAtRest(), "Rest detection working during stationary period"); - - // Phase 2: Start moving (rest detection should deactivate) - std::printf(" Phase 2: Device starts moving (rest should deactivate)\n"); - // Need sustained motion (>0.17s) to exit rest - for (int i = 0; i < 60; i++) { // 1 second of sustained motion - SimulatedSensors s = generateLevelFlightSensors(0.0f); - s.gx = 0.15f; // Clear rotation (>1°/s variance threshold) - s.gy = 0.1f; - s.gz = 0.05f; - - fix.filter.update(0.01667, fix.tow_ms, 10.0, 0.0, 0.0, // Moving - fix.lat_rad, fix.lon_rad, 1000.0, - s.gx, s.gy, s.gz, s.ax, s.ay, s.az, - s.mx, s.my, s.mz, fix.bn, fix.be, fix.bd); - fix.tow_ms += 16; + expectNear(w.speed(), 20.0f, 2.0f, "Speed tracks 20 m/s cruise"); + expectNearDeg(w.filter.getRoll_rad(), 0.0f, 8.0f, "Roll level in a car"); + expectNearDeg(w.filter.getPitch_rad(), 0.0f, 8.0f, "Pitch level in a car"); + expectTrue(!w.filter.isZuptActive(), "ZUPT off while driving"); + expectTrue(!w.filter.isAtRest(), "Engine vibration is not rest while moving"); + expectTrue(w.filter.getHealthStatus() <= 1, "Health OK in a car"); + expectNearDeg(w.filter.getGroundTrack_rad(), 0.0f, 15.0f, "Ground track northbound in a car"); + expectNear((float)w.filter.getVelNorth_ms(), 20.0f, 2.5f, "North velocity matches car cruise"); + expectNear((float)w.filter.getVelEast_ms(), 0.0f, 2.5f, "East velocity near zero northbound"); + expectNear(w.fpa_deg(), 0.0f, 4.0f, "Vertical FPA near zero on level road"); + expectNear(w.hfpa_deg(), 0.0f, 12.0f, "Horizontal FPA near zero when track matches heading"); +} + +static void testCarLevelTurn() { + std::printf("\n=== Car: 90° turn, wheels on the ground ===\n"); + World w; + const float speed = 15.0f; + const float radius = 50.0f; + const float yaw_rate = speed / radius; // rad/s, no bank + w.set_course(speed, 0.0f); + for (int i = 0; i < 180; i++) { + w.step(carVibeSensors(w.heading_deg, i)); } - - bool still_at_rest = fix.filter.isAtRest(); - std::printf(" Rest detection after motion: %s\n", still_at_rest ? "YES" : "NO"); - // Note: Rest detection has hysteresis, may take a few cycles to fully exit - std::printf(" Rest detection correctly manages stationary/motion states\n"); - expectTrue(true, "Rest detection state management functional"); + + float max_roll = 0.0f; + int turn_iters = (int)((0.5f * (float)M_PI / yaw_rate) / IMU_DT); + for (int i = 0; i < turn_iters; i++) { + w.heading_deg = wrap360(w.heading_deg + yaw_rate * IMU_DT * RAD_TO_DEG); + w.set_course(speed, w.heading_deg); + float ay = speed * yaw_rate; // centripetal in body y + w.step(carVibeSensors(w.heading_deg, i, yaw_rate, 0.0f, ay)); + float roll = std::fabs(w.filter.getRoll_rad() * RAD_TO_DEG); + if (roll > max_roll) max_roll = roll; + } + std::printf(" Max roll during level car turn: %.2f°\n", max_roll); + expectTrue(max_roll < 15.0f, "Car turn does not produce an aircraft bank"); + + for (int i = 0; i < 180; i++) { + w.step(carVibeSensors(w.heading_deg, i)); + } + expectNearDeg(w.filter.getHeading_rad(), 90.0f, 20.0f, "Heading follows a 90° car turn"); + expectNearDeg(w.filter.getRoll_rad(), 0.0f, 10.0f, "Roll returns to level after car turn"); } -static void testSpeedBasedZupt() { - std::printf("\n=== Test: Speed-Based ZUPT ===\n"); - - INSTestFixture fix; - fix.initialize(); - - // Phase 1: Start with motion (10 m/s) - std::printf(" Phase 1: Device moving at 10 m/s (ZUPT should not activate)\n"); - for (int i = 0; i < 60; i++) { // 1 second - SimulatedSensors s = generateLevelFlightSensors(0.0f); - fix.filter.update(0.01667, fix.tow_ms, 10.0, 0.0, 0.0, // 10 m/s north - fix.lat_rad, fix.lon_rad, 1000.0, - s.gx, s.gy, s.gz, s.ax, s.ay, s.az, - s.mx, s.my, s.mz, fix.bn, fix.be, fix.bd); - fix.tow_ms += 16; +static void testCarStoplight() { + std::printf("\n=== Car: stop at a light, then pull away ===\n"); + World w; + w.set_course(12.0f, 0.0f); + for (int i = 0; i < 120; i++) { + w.step(carVibeSensors(w.heading_deg, i)); } - - bool zupt_while_moving = fix.filter.isZuptActive(); - std::printf(" ZUPT active while moving: %s\n", zupt_while_moving ? "YES" : "NO"); - expectTrue(!zupt_while_moving, "ZUPT should not activate while moving at 10 m/s"); - - // Phase 2: Slow down to stationary (GPS shows velocity decreasing) - std::printf(" Phase 2: Decelerating to stop\n"); - for (int i = 0; i < 60; i++) { // 1 second deceleration - float speed = 10.0f * (1.0f - (float)i / 60.0f); // Linear deceleration to 0 - SimulatedSensors s = generateLevelFlightSensors(0.0f); - fix.filter.update(0.01667, fix.tow_ms, speed, 0.0, 0.0, - fix.lat_rad, fix.lon_rad, 1000.0, - s.gx, s.gy, s.gz, s.ax, s.ay, s.az, - s.mx, s.my, s.mz, fix.bn, fix.be, fix.bd); - fix.tow_ms += 16; + expectTrue(!w.filter.isZuptActive(), "ZUPT off at 12 m/s"); + + for (int i = 0; i < 120; i++) { // 2 s brake + float speed = 12.0f * (1.0f - (float)i / 120.0f); + w.set_course(speed, 0.0f); + w.step(carVibeSensors(0.0f, i, 0.0f, -2.0f, 0.0f)); } - - // Phase 3: Remain stationary for 5 seconds (GPS shows ~0 m/s) - std::printf(" Phase 3: Stationary for 5 seconds (ZUPT should activate after 3s)\n"); - - // Small hand tremor simulation (device held in hand, not on desk) - // This should NOT prevent speed-based ZUPT (that's the whole point) + w.set_course(0.0f, 0.0f); bool zupt_at_2s = false; bool zupt_at_4s = false; - float vel_mag_at_4s = 0.0f; - float vel_std_at_start = 0.0f; - float vel_std_at_4s = 0.0f; - - for (int i = 0; i < 300; i++) { // 5 seconds at 60 Hz - SimulatedSensors s = generateLevelFlightSensors(0.0f); - - // Add small hand tremor (higher than variance-based rest threshold) - s.gx += 0.03f * std::sin(i * 0.2); // ~1.7 deg/s variation - s.gy += 0.02f * std::cos(i * 0.3); - s.gz += 0.01f * std::sin(i * 0.15); - s.ax += 0.8f * std::sin(i * 0.1); // ~0.8 m/s² variation (hand tremor) - s.ay += 0.6f * std::cos(i * 0.12); - - fix.filter.update(0.01667, fix.tow_ms, 0.0, 0.0, 0.0, // GPS shows 0 velocity - fix.lat_rad, fix.lon_rad, 1000.0, - s.gx, s.gy, s.gz, s.ax, s.ay, s.az, - s.mx, s.my, s.mz, fix.bn, fix.be, fix.bd); - fix.tow_ms += 16; - - // Check ZUPT status at key times - if (i == 120) { // 2 seconds (before 3s threshold) - zupt_at_2s = fix.filter.isZuptActive(); - vel_std_at_start = fix.filter.getVelocityNorthStd_ms(); - std::printf(" After 2s: ZUPT active = %s (should be NO, not enough time)\n", - zupt_at_2s ? "YES" : "NO"); - } - - if (i == 240) { // 4 seconds (after 3s threshold) - zupt_at_4s = fix.filter.isZuptActive(); - double vn = fix.filter.getVelNorth_ms(); - double ve = fix.filter.getVelEast_ms(); - double vd = fix.filter.getVelDown_ms(); - vel_mag_at_4s = std::sqrt(vn*vn + ve*ve + vd*vd); - vel_std_at_4s = fix.filter.getVelocityNorthStd_ms(); - std::printf(" After 4s: ZUPT active = %s, velocity magnitude = %.3f m/s\n", - zupt_at_4s ? "YES" : "NO", vel_mag_at_4s); - std::printf(" Velocity std: start=%.2f m/s, now=%.2f m/s\n", - vel_std_at_start, vel_std_at_4s); - } + for (int i = 0; i < 300; i++) { + w.step(carVibeSensors(0.0f, i)); + if (i == 120) zupt_at_2s = w.filter.isZuptActive(); + if (i == 240) zupt_at_4s = w.filter.isZuptActive(); } - - // Verify ZUPT behavior - expectTrue(!zupt_at_2s, "ZUPT should not activate before 3 seconds"); - expectTrue(zupt_at_4s, "ZUPT should activate after 3 seconds at low speed"); - expectTrue(vel_mag_at_4s < 0.5f, "Velocity should remain small (< 0.5 m/s) with ZUPT active"); - expectTrue(vel_std_at_4s <= vel_std_at_start * 1.5f, - "Velocity std should not grow significantly with ZUPT (may improve or stay bounded)"); - - // Verify variance-based rest did NOT activate (due to hand tremor) - bool at_rest = fix.filter.isAtRest(); - std::printf(" Variance-based rest active: %s (should be NO due to hand tremor)\n", - at_rest ? "YES" : "NO"); - std::printf(" This proves speed-based ZUPT works when handheld!\n"); - - // Phase 4: Resume motion (GPS shows 5 m/s) - std::printf(" Phase 4: Resume motion at 5 m/s (ZUPT should deactivate)\n"); - for (int i = 0; i < 60; i++) { // 1 second - SimulatedSensors s = generateLevelFlightSensors(0.0f); - fix.filter.update(0.01667, fix.tow_ms, 5.0, 0.0, 0.0, // 5 m/s north - fix.lat_rad, fix.lon_rad, 1000.0, - s.gx, s.gy, s.gz, s.ax, s.ay, s.az, - s.mx, s.my, s.mz, fix.bn, fix.be, fix.bd); - fix.tow_ms += 16; + expectTrue(!zupt_at_2s, "ZUPT not yet at 2 s stopped"); + expectTrue(zupt_at_4s, "ZUPT on after 3 s at a stoplight"); + expectTrue(w.speed_3d() < 1.0f, "Speed near zero at a stoplight"); + + w.set_course(8.0f, 0.0f); + for (int i = 0; i < 120; i++) { + w.step(carVibeSensors(0.0f, i, 0.0f, 1.5f, 0.0f)); } - - bool zupt_after_motion = fix.filter.isZuptActive(); - std::printf(" ZUPT active after resuming motion: %s\n", zupt_after_motion ? "YES" : "NO"); - expectTrue(!zupt_after_motion, "ZUPT should deactivate when speed exceeds exit threshold"); - - std::printf(" Speed-based ZUPT works for real-world stationary scenarios!\n"); + expectTrue(!w.filter.isZuptActive(), "ZUPT clears when pulling away"); } -static void testEnhancedMagRejection() { - std::printf("\n=== Test: Enhanced Magnetic Interference Rejection ===\n"); - - INSTestFixture fix; - fix.initialize(); - - // Test 1: Clean mag field (should be accepted) - std::printf(" Phase 1: Clean magnetic field (should accept)\n"); - for (int i = 0; i < 60; i++) { // 1 second - SimulatedSensors s = generateLevelFlightSensors(0.0f); - fix.filter.update(0.01667, fix.tow_ms, 50.0, 0.0, 0.0, - fix.lat_rad, fix.lon_rad, 1000.0, - s.gx, s.gy, s.gz, s.ax, s.ay, s.az, - s.mx, s.my, s.mz, fix.bn, fix.be, fix.bd); - fix.tow_ms += 16; +static void testCarUrbanGps() { + std::printf("\n=== Car: urban canyon GPS ===\n"); + World good; + good.hacc = 3.0f; + good.vacc = 5.0f; + good.sacc = 0.4f; + good.set_course(15.0f, 0.0f); + for (int i = 0; i < 300; i++) { + good.step(carVibeSensors(0.0f, i)); } - float yaw_clean = fix.filter.getYawStd_rad(); - std::printf(" Yaw std with clean mag: %.2f°\n", yaw_clean * RAD_TO_DEG); - - // Test 2: Severely distorted field (should be rejected) - std::printf(" Phase 2: Severely distorted field (magnitude gate)\n"); - INSTestFixture fix2; - fix2.initialize(); - - for (int i = 0; i < 60; i++) { - SimulatedSensors s = generateLevelFlightSensors(0.0f); - // Multiply field by 3x (should trigger magnitude gate: 0.5-2.0x) - s.mx *= 3.0f; - s.my *= 3.0f; - s.mz *= 3.0f; - - fix2.filter.update(0.01667, fix2.tow_ms, 50.0, 0.0, 0.0, - fix2.lat_rad, fix2.lon_rad, 1000.0, - s.gx, s.gy, s.gz, s.ax, s.ay, s.az, - s.mx, s.my, s.mz, fix2.bn, fix2.be, fix2.bd); - fix2.tow_ms += 16; + + World poor; + poor.hacc = 20.0f; + poor.vacc = 30.0f; + poor.sacc = 2.5f; + poor.set_course(15.0f, 0.0f); + for (int i = 0; i < 300; i++) { + poor.step(carVibeSensors(0.0f, i)); } - std::printf(" Magnitude gate rejects 3x field strength\n"); - expectTrue(true, "Filter stable despite mag interference"); - - // Test 3: Inclination distortion (should be rejected) - std::printf(" Phase 3: Inclination distortion (inclination gate)\n"); - INSTestFixture fix3; - fix3.initialize(); - + + float std_good = good.filter.getPositionNorthStd_m(); + float std_poor = poor.filter.getPositionNorthStd_m(); + std::printf(" Pos std good GPS: %.2fm poor GPS: %.2fm\n", std_good, std_poor); + expectTrue(std_poor > std_good * 1.5f, "Poor GPS increases position uncertainty"); + expectNear(poor.speed(), 15.0f, 4.0f, "Speed still usable with 20 m hacc"); + expectNearDeg(poor.filter.getRoll_rad(), 0.0f, 10.0f, "Horizon holds with poor GPS"); +} + +static void testCarAccelerate() { + std::printf("\n=== Car: acceleration ===\n"); + World w; + const float a = 2.0f; + float speed = 5.0f; + w.set_course(speed, 0.0f); for (int i = 0; i < 60; i++) { - SimulatedSensors s = generateLevelFlightSensors(0.0f); - // Rotate field to change inclination by 30° - float original_hz = s.mz; - s.mz = s.mz * std::cos(30.0f * DEG_TO_RAD) - s.mx * std::sin(30.0f * DEG_TO_RAD); - s.mx = original_hz * std::sin(30.0f * DEG_TO_RAD) + s.mx * std::cos(30.0f * DEG_TO_RAD); - - fix3.filter.update(0.01667, fix3.tow_ms, 50.0, 0.0, 0.0, - fix3.lat_rad, fix3.lon_rad, 1000.0, - s.gx, s.gy, s.gz, s.ax, s.ay, s.az, - s.mx, s.my, s.mz, fix3.bn, fix3.be, fix3.bd); - fix3.tow_ms += 16; + w.step(carVibeSensors(0.0f, i)); } - std::printf(" Inclination gate rejects 30° tilt\n"); - expectTrue(true, "Filter rejects inclination-distorted mag"); - - // Test 4: Transient interference (temporal gate) - std::printf(" Phase 4: Transient interference (temporal gate)\n"); - INSTestFixture fix4; - fix4.initialize(); - - // Start with clean field + for (int i = 0; i < 300; i++) { // 5 s → 15 m/s + speed += a * IMU_DT; + w.set_course(speed, 0.0f); + w.step(carVibeSensors(0.0f, i, 0.0f, a, 0.0f)); + } + expectNear(w.speed(), 15.0f, 2.5f, "Speed tracks a 5→15 m/s pull"); + expectNearDeg(w.filter.getRoll_rad(), 0.0f, 8.0f, "Roll stays level while accelerating"); + expectTrue(!w.filter.isZuptActive(), "ZUPT off while accelerating"); +} + +static void testCarDecelerate() { + std::printf("\n=== Car: deceleration ===\n"); + World w; + const float a = -2.0f; + float speed = 20.0f; + w.set_course(speed, 0.0f); for (int i = 0; i < 60; i++) { - SimulatedSensors s = generateLevelFlightSensors(0.0f); - fix4.filter.update(0.01667, fix4.tow_ms, 50.0, 0.0, 0.0, - fix4.lat_rad, fix4.lon_rad, 1000.0, - s.gx, s.gy, s.gz, s.ax, s.ay, s.az, - s.mx, s.my, s.mz, fix4.bn, fix4.be, fix4.bd); - fix4.tow_ms += 16; + w.step(carVibeSensors(0.0f, i)); } - - // Sudden large change (e.g., passing metal object) - for (int i = 0; i < 10; i++) { - SimulatedSensors s = generateLevelFlightSensors(0.0f); - s.mx += 15.0f; // +15 µT sudden change (triggers 10µT threshold) - s.my += 15.0f; - - fix4.filter.update(0.01667, fix4.tow_ms, 50.0, 0.0, 0.0, - fix4.lat_rad, fix4.lon_rad, 1000.0, - s.gx, s.gy, s.gz, s.ax, s.ay, s.az, - s.mx, s.my, s.mz, fix4.bn, fix4.be, fix4.bd); - fix4.tow_ms += 16; + for (int i = 0; i < 300; i++) { // 5 s → 10 m/s + speed += a * IMU_DT; + w.set_course(speed, 0.0f); + w.step(carVibeSensors(0.0f, i, 0.0f, a, 0.0f)); } - - // Return to clean field - for (int i = 0; i < 60; i++) { - SimulatedSensors s = generateLevelFlightSensors(0.0f); - fix4.filter.update(0.01667, fix4.tow_ms, 50.0, 0.0, 0.0, - fix4.lat_rad, fix4.lon_rad, 1000.0, - s.gx, s.gy, s.gz, s.ax, s.ay, s.az, - s.mx, s.my, s.mz, fix4.bn, fix4.be, fix4.bd); - fix4.tow_ms += 16; + expectNear(w.speed(), 10.0f, 2.5f, "Speed tracks a 20→10 m/s brake"); + expectNearDeg(w.filter.getRoll_rad(), 0.0f, 8.0f, "Roll stays level while braking"); + expectTrue(!w.filter.isZuptActive(), "ZUPT off while still moving after a brake"); +} + +static void testCar360() { + std::printf("\n=== Car: 360° level turn ===\n"); + World w; + const float speed = 15.0f; + const float radius = 50.0f; + const float yaw_rate = speed / radius; + w.set_course(speed, 0.0f); + for (int i = 0; i < 120; i++) { + w.step(carVibeSensors(w.heading_deg, i)); + } + float max_roll = 0.0f; + int turn_iters = (int)((2.0f * (float)M_PI / yaw_rate) / IMU_DT); + for (int i = 0; i < turn_iters; i++) { + w.heading_deg = wrap360(w.heading_deg + yaw_rate * IMU_DT * RAD_TO_DEG); + w.set_course(speed, w.heading_deg); + float ay = speed * yaw_rate; + w.step(carVibeSensors(w.heading_deg, i, yaw_rate, 0.0f, ay)); + float roll = std::fabs(w.filter.getRoll_rad() * RAD_TO_DEG); + if (roll > max_roll) max_roll = roll; } - - std::printf(" Temporal gate rejects sudden 15µT change\n"); - expectTrue(true, "Filter handles transient mag interference"); + std::printf(" Max roll during 360° car turn: %.2f° heading: %.1f°\n", + max_roll, w.filter.getHeading_rad() * RAD_TO_DEG); + expectTrue(max_roll < 15.0f, "360° car turn does not bank like an aircraft"); + expectNearDeg(w.filter.getHeading_rad(), 0.0f, 35.0f, "Heading returns after a 360° car turn"); + expectNear(w.speed(), speed, 3.0f, "Speed holds through a 360° car turn"); } -static void testSensorDelayCompensation() { - std::printf("\n=== Test: Sensor Delay Compensation ===\n"); - - // Test 1: Variable GPS update rate (5 Hz) - std::printf(" Phase 1: GPS at 5 Hz (200ms interval)\n"); - INSTestFixture fix; - fix.initialize(); - - int gps_counter = 0; - for (int i = 0; i < 300; i++) { // 5 seconds at 60 Hz IMU - SimulatedSensors s = generateLevelFlightSensors(0.0f); - - // GPS updates every 12 IMU samples (5 Hz at 60 Hz IMU) - bool gps_update = (i % 12 == 0); - - if (gps_update) { - fix.filter.update(0.01667, fix.tow_ms, 50.0, 0.0, 0.0, - fix.lat_rad, fix.lon_rad, 1000.0, - s.gx, s.gy, s.gz, s.ax, s.ay, s.az, - s.mx, s.my, s.mz, fix.bn, fix.be, fix.bd); - gps_counter++; - } else { - // No GPS update - use previous GPS data - fix.filter.update(0.01667, fix.tow_ms, 50.0, 0.0, 0.0, - fix.lat_rad, fix.lon_rad, 1000.0, - s.gx, s.gy, s.gz, s.ax, s.ay, s.az, - s.mx, s.my, s.mz, fix.bn, fix.be, fix.bd); - } - fix.tow_ms += 16; +// ============================================================================ +// Aircraft +// ============================================================================ + +static void testAircraftLevel() { + std::printf("\n=== Aircraft: straight and level ===\n"); + World w; + w.alt_m = 1000.0; + w.set_gps_hz(5); + w.hacc = 3.0f; + w.vacc = 4.0f; + w.sacc = 0.3f; + w.set_course(50.0f, 0.0f); + w.run_level(600); // 10 s + expectNearDeg(w.filter.getRoll_rad(), 0.0f, 3.0f, "Roll level in cruise"); + expectNearDeg(w.filter.getPitch_rad(), 0.0f, 3.0f, "Pitch level in cruise"); + expectNear(w.speed(), 50.0f, 2.0f, "Speed tracks 50 m/s"); + expectTrue(w.filter.getPositionNorthStd_m() < 10.0f, "Position uncertainty bounded"); + expectTrue(w.filter.getRollStd_rad() * RAD_TO_DEG < 5.0f, "Attitude uncertainty reasonable"); + expectTrue(w.filter.getHealthStatus() <= 1, "Health OK in cruise"); + expectNear(w.fpa_deg(), 0.0f, 3.0f, "Vertical FPA near zero in level cruise"); + expectNear(w.hfpa_deg(), 0.0f, 8.0f, "Horizontal FPA near zero when coordinated"); + expectNearDeg(w.filter.getGroundTrack_rad(), 0.0f, 10.0f, "Ground track northbound in cruise"); + expectNear((float)w.filter.getVelNorth_ms(), 50.0f, 2.5f, "North velocity matches airspeed"); + expectNear((float)w.filter.getVelEast_ms(), 0.0f, 2.5f, "East velocity near zero northbound"); + expectNear((float)w.filter.getVelDown_ms(), 0.0f, 1.5f, "Down velocity near zero in level flight"); + expectTrue(w.filter.getLatitude_rad() > 51.5 * DEG_TO_RAD, + "Latitude increases when flying north"); + expectTrue(std::isfinite(w.filter.getGyroBiasX_rads()) && + std::isfinite(w.filter.getAccelBiasX_mss()) && + std::isfinite(w.filter.getMagBiasX_nT()), + "Bias estimates are finite in cruise"); + expectTrue(w.filter.getVelocityNorthStd_ms() < 3.0f, "North velocity uncertainty bounded"); +} + +static void runAircraftRollIn(World &w, float target_bank, float roll_rate_deg_s, + float turn_rate_deg_s) { + float bank = 0.0f; + int n = (int)(std::fabs(target_bank) / roll_rate_deg_s / IMU_DT); + if (n < 1) n = 1; + float bank_step = target_bank / (float)n; + float airspeed = std::sqrt((float)(w.vn * w.vn + w.ve * w.ve)); + for (int i = 0; i < n; i++) { + bank += bank_step; + float partial_turn = turn_rate_deg_s * (bank / target_bank); + SimulatedSensors s; + float bank_rad = bank * DEG_TO_RAD; + float load = 1.0f / std::cos(bank_rad); + s.ax = 0.0f; + s.ay = 0.0f; + s.az = -load * G; + s.gx = bank_step / IMU_DT * DEG_TO_RAD; + s.gy = partial_turn * DEG_TO_RAD * std::sin(bank_rad); + s.gz = partial_turn * DEG_TO_RAD * std::cos(bank_rad); + w.heading_deg = wrap360(w.heading_deg + partial_turn * IMU_DT); + nedMagToBody(w.heading_deg, 0.0f, bank, &s.mx, &s.my, &s.mz); + w.set_course(airspeed, w.heading_deg); + w.step(s); } - std::printf(" GPS updates: %d (expected ~25)\n", gps_counter); - expectTrue(gps_counter >= 20 && gps_counter <= 30, "5 Hz GPS update rate handled"); - - // Test 2: Slow GPS update rate (1 Hz) - std::printf(" Phase 2: GPS at 1 Hz (1000ms interval)\n"); - INSTestFixture fix2; - fix2.initialize(); - - gps_counter = 0; - for (int i = 0; i < 300; i++) { // 5 seconds - SimulatedSensors s = generateLevelFlightSensors(0.0f); - - // GPS updates every 60 IMU samples (1 Hz at 60 Hz IMU) - bool gps_update = (i % 60 == 0); - - if (gps_update) { - fix2.filter.update(0.01667, fix2.tow_ms, 50.0, 0.0, 0.0, - fix2.lat_rad, fix2.lon_rad, 1000.0, - s.gx, s.gy, s.gz, s.ax, s.ay, s.az, - s.mx, s.my, s.mz, fix2.bn, fix2.be, fix2.bd); - gps_counter++; - } else { - fix2.filter.update(0.01667, fix2.tow_ms, 50.0, 0.0, 0.0, - fix2.lat_rad, fix2.lon_rad, 1000.0, - s.gx, s.gy, s.gz, s.ax, s.ay, s.az, - s.mx, s.my, s.mz, fix2.bn, fix2.be, fix2.bd); - } - fix2.tow_ms += 16; +} + +static void runAircraftTurn(World &w, int n, float bank_deg, float turn_rate_deg_s) { + float airspeed = std::sqrt((float)(w.vn * w.vn + w.ve * w.ve)); + for (int i = 0; i < n; i++) { + SimulatedSensors s = coordinatedTurnSensors(w.heading_deg, bank_deg, turn_rate_deg_s); + w.heading_deg = wrap360(w.heading_deg + turn_rate_deg_s * IMU_DT); + w.set_course(airspeed, w.heading_deg); + w.step(s); + } +} + +static void runAircraftRollOut(World &w, float current_bank, float roll_rate_deg_s, + float turn_rate_deg_s) { + int n = (int)(std::fabs(current_bank) / roll_rate_deg_s / IMU_DT); + if (n < 1) n = 1; + float bank_step = -current_bank / (float)n; + float bank = current_bank; + float airspeed = std::sqrt((float)(w.vn * w.vn + w.ve * w.ve)); + for (int i = 0; i < n; i++) { + bank += bank_step; + SimulatedSensors s; + float bank_rad = bank * DEG_TO_RAD; + float load = (std::fabs(bank) > 1.0f) ? (1.0f / std::cos(bank_rad)) : 1.0f; + float frac = std::fabs(bank / current_bank); + float decaying = turn_rate_deg_s * frac * DEG_TO_RAD; + s.ax = 0.0f; + s.ay = 0.0f; + s.az = -load * G; + s.gx = bank_step / IMU_DT * DEG_TO_RAD; + s.gy = decaying * std::sin(bank_rad); + s.gz = decaying * std::cos(bank_rad); + w.heading_deg = wrap360(w.heading_deg + turn_rate_deg_s * frac * IMU_DT); + nedMagToBody(w.heading_deg, 0.0f, bank, &s.mx, &s.my, &s.mz); + w.set_course(airspeed, w.heading_deg); + w.step(s); + } +} + +static void testAircraftCoordinatedTurn() { + std::printf("\n=== Aircraft: coordinated 30° turn ===\n"); + World w; + w.alt_m = 1000.0; + w.set_gps_hz(5); + w.set_course(50.0f, 0.0f); + w.run_level(120); + + const float bank = 30.0f; + const float turn_rate = 6.0f; + const float roll_rate = 15.0f; + runAircraftRollIn(w, bank, roll_rate, turn_rate); + runAircraftTurn(w, 300, bank, turn_rate); + std::printf(" Roll during turn: %.2f°\n", w.filter.getRoll_rad() * RAD_TO_DEG); + expectNearDeg(w.filter.getRoll_rad(), bank, 10.0f, "Roll tracks 30° bank"); + expectNear(w.hfpa_deg(), 0.0f, 12.0f, "Horizontal FPA near zero in a coordinated turn"); + + runAircraftRollOut(w, bank, roll_rate, turn_rate); + w.run_level(300); + std::printf(" Roll after rollout: %.2f°\n", w.filter.getRoll_rad() * RAD_TO_DEG); + expectNearDeg(w.filter.getRoll_rad(), 0.0f, 10.0f, "Roll returns to level after turn"); +} + +static void testAircraftHorizon360() { + std::printf("\n=== Aircraft: 360° sustained turn ===\n"); + World w; + w.alt_m = 1000.0; + w.set_gps_hz(5); + w.set_course(50.0f, 0.0f); + w.run_level(120); + const float bank = 30.0f; + const float turn_rate = 6.0f; + runAircraftRollIn(w, bank, 15.0f, turn_rate); + float heading_start = w.filter.getHeading_rad() * RAD_TO_DEG; + float max_err = 0.0f; + for (int seg = 0; seg < 6; seg++) { + runAircraftTurn(w, 600, bank, turn_rate); + float err = std::fabs(w.filter.getRoll_rad() * RAD_TO_DEG - bank); + if (err > max_err) max_err = err; + std::printf(" Segment %d roll error: %.2f°\n", seg + 1, err); } - std::printf(" GPS updates: %d (expected ~5)\n", gps_counter); - expectTrue(gps_counter >= 4 && gps_counter <= 6, "1 Hz GPS update rate handled"); - - // Both filters should remain stable - expectTrue(fix.filter.isHealthy() || fix.filter.getHealthStatus() == 1, "Filter stable at 5 Hz GPS"); - expectTrue(fix2.filter.isHealthy() || fix2.filter.getHealthStatus() == 1, "Filter stable at 1 Hz GPS"); + expectTrue(max_err < 30.0f, "Horizon stays within 30° over a 360° turn"); + expectNearDeg(w.filter.getHeading_rad(), heading_start, 35.0f, + "Heading returns after a 360° aircraft turn"); +} + +static void testAircraftClimb() { + std::printf("\n=== Aircraft: climb ===\n"); + World w; + w.alt_m = 1000.0; + w.set_gps_hz(5); + w.set_course(50.0f, 0.0f); + w.run_level(120); + + const float pitch = 6.0f; + const float vs = 5.0f; // m/s up + w.vd = -vs; + for (int i = 0; i < 600; i++) { // 10 s → +50 m + w.step(pitchedSensors(0.0f, pitch)); + } + std::printf(" Alt: %.1fm pitch: %.2f° vs: %.2f m/s\n", + w.filter.getAltitude_m(), w.filter.getPitch_rad() * RAD_TO_DEG, + (float)(-w.filter.getVelDown_ms())); + expectNear((float)w.filter.getAltitude_m(), 1050.0f, 12.0f, "Altitude rises ~50 m in a climb"); + expectTrue(w.filter.getPitch_rad() * RAD_TO_DEG > 2.0f, "Pitch is nose-up in a climb"); + expectTrue((-w.filter.getVelDown_ms()) > 2.0f, "Vertical speed is positive in a climb"); + expectNearDeg(w.filter.getRoll_rad(), 0.0f, 8.0f, "Wings level in a straight climb"); + // atan2(5, 50) ≈ 5.7° + expectNear(w.fpa_deg(), 5.7f, 3.5f, "Vertical FPA is nose-up in a climb"); + expectTrue(w.fpa_deg() > 2.0f, "Vertical FPA sign is climb"); +} + +static void testAircraftDescent() { + std::printf("\n=== Aircraft: descent ===\n"); + World w; + w.alt_m = 1200.0; + w.set_gps_hz(5); + w.set_course(50.0f, 0.0f); + w.run_level(120); + + const float pitch = -5.0f; + const float vs = -4.0f; // m/s down + w.vd = -vs; + for (int i = 0; i < 600; i++) { // 10 s → -40 m + w.step(pitchedSensors(0.0f, pitch)); + } + std::printf(" Alt: %.1fm pitch: %.2f° vs: %.2f m/s\n", + w.filter.getAltitude_m(), w.filter.getPitch_rad() * RAD_TO_DEG, + (float)(-w.filter.getVelDown_ms())); + expectNear((float)w.filter.getAltitude_m(), 1160.0f, 12.0f, "Altitude drops ~40 m in a descent"); + expectTrue(w.filter.getPitch_rad() * RAD_TO_DEG < -1.5f, "Pitch is nose-down in a descent"); + expectTrue((-w.filter.getVelDown_ms()) < -1.5f, "Vertical speed is negative in a descent"); + expectNearDeg(w.filter.getRoll_rad(), 0.0f, 8.0f, "Wings level in a straight descent"); + // atan2(-4, 50) ≈ -4.6° + expectNear(w.fpa_deg(), -4.6f, 3.5f, "Vertical FPA is nose-down in a descent"); + expectTrue(w.fpa_deg() < -1.5f, "Vertical FPA sign is descent"); +} + +static void testAircraftAccelerate() { + std::printf("\n=== Aircraft: acceleration ===\n"); + World w; + w.alt_m = 1000.0; + w.set_gps_hz(5); + float speed = 40.0f; + w.set_course(speed, 0.0f); + w.run_level(60); + const float a = 1.5f; + for (int i = 0; i < 300; i++) { // 5 s → 47.5 m/s + speed += a * IMU_DT; + w.set_course(speed, 0.0f); + w.step(levelSensors(0.0f, 0.0f, a, 0.0f)); + } + expectNear(w.speed(), 47.5f, 3.0f, "Speed tracks a 40→47.5 m/s acceleration"); + expectNearDeg(w.filter.getRoll_rad(), 0.0f, 6.0f, "Wings level while accelerating"); +} + +static void testAircraftDecelerate() { + std::printf("\n=== Aircraft: deceleration ===\n"); + World w; + w.alt_m = 1000.0; + w.set_gps_hz(5); + float speed = 55.0f; + w.set_course(speed, 0.0f); + w.run_level(60); + const float a = -1.5f; + for (int i = 0; i < 300; i++) { // 5 s → 47.5 m/s + speed += a * IMU_DT; + w.set_course(speed, 0.0f); + w.step(levelSensors(0.0f, 0.0f, a, 0.0f)); + } + expectNear(w.speed(), 47.5f, 3.0f, "Speed tracks a 55→47.5 m/s deceleration"); + expectNearDeg(w.filter.getRoll_rad(), 0.0f, 6.0f, "Wings level while decelerating"); +} + +static void testAircraftClimbingTurn() { + std::printf("\n=== Aircraft: climbing turn ===\n"); + World w; + w.alt_m = 1000.0; + w.set_gps_hz(5); + w.set_course(50.0f, 0.0f); + w.run_level(120); + double alt0 = w.filter.getAltitude_m(); + const float bank = 25.0f; + const float turn_rate = 6.0f; + w.vd = -3.0; // 3 m/s climb + runAircraftRollIn(w, bank, 15.0f, turn_rate); + runAircraftTurn(w, 300, bank, turn_rate); // 5 s, +15 m, ~30° heading + std::printf(" Roll: %.1f° heading: %.1f° alt: %.1fm vs: %.2f\n", + w.filter.getRoll_rad() * RAD_TO_DEG, w.filter.getHeading_rad() * RAD_TO_DEG, + w.filter.getAltitude_m(), (float)(-w.filter.getVelDown_ms())); + expectNearDeg(w.filter.getRoll_rad(), bank, 12.0f, "Roll tracks bank in a climbing turn"); + expectTrue(w.filter.getAltitude_m() > alt0 + 8.0, "Altitude increases in a climbing turn"); + expectTrue((-w.filter.getVelDown_ms()) > 1.0f, "Climbing turn has positive vertical speed"); +} + +static void testAircraftDescendingTurn() { + std::printf("\n=== Aircraft: descending turn ===\n"); + World w; + w.alt_m = 1200.0; + w.set_gps_hz(5); + w.set_course(50.0f, 0.0f); + w.run_level(120); + double alt0 = w.filter.getAltitude_m(); + const float bank = 25.0f; + const float turn_rate = 6.0f; + w.vd = 3.0; // 3 m/s descent + runAircraftRollIn(w, bank, 15.0f, turn_rate); + runAircraftTurn(w, 300, bank, turn_rate); + std::printf(" Roll: %.1f° heading: %.1f° alt: %.1fm vs: %.2f\n", + w.filter.getRoll_rad() * RAD_TO_DEG, w.filter.getHeading_rad() * RAD_TO_DEG, + w.filter.getAltitude_m(), (float)(-w.filter.getVelDown_ms())); + expectNearDeg(w.filter.getRoll_rad(), bank, 12.0f, "Roll tracks bank in a descending turn"); + expectTrue(w.filter.getAltitude_m() < alt0 - 8.0, "Altitude decreases in a descending turn"); + expectTrue((-w.filter.getVelDown_ms()) < -1.0f, "Descending turn has negative vertical speed"); +} + +static void testAircraftClimbBaro() { + std::printf("\n=== Aircraft: climb with poor GPS vertical ===\n"); + World w; + w.alt_m = 1000.0; + w.set_gps_hz(5); + w.hacc = 3.0f; + w.vacc = 25.0f; + w.sacc = 0.5f; + w.set_course(50.0f, 0.0f); + w.vd = -1.0; // NED down negative = climb + + for (int i = 0; i < 600; i++) { + double t = (double)i * IMU_DT; + double true_alt = 1000.0 + t * 1.0; + w.alt_m = true_alt; + w.baro_hpa = 1013.25f * std::exp((float)(-(true_alt + 0.5 * std::sin((float)i * 0.1f)) / 8430.0)); + SimulatedSensors s = levelSensors(0.0f); + w.step(s); + } + double expected = 1010.0; + std::printf(" Filter alt: %.1fm expected ~%.1fm\n", w.filter.getAltitude_m(), expected); + expectNear((float)w.filter.getAltitude_m(), (float)expected, 8.0f, + "Baro holds altitude in a climb when vacc is poor"); + + World good; + good.alt_m = 1000.0; + good.set_gps_hz(5); + good.hacc = 3.0f; + good.vacc = 5.0f; + good.set_course(50.0f, 0.0f); + good.baro_hpa = 1013.25f * std::exp(-1000.0f / 8430.0f); + good.run_level(300); + expectNear((float)good.filter.getAltitude_m(), 1000.0f, 3.0f, + "Baro does not pull altitude when vacc is good"); +} + +static void testAircraftGpsOutage() { + std::printf("\n=== Aircraft: 5 s GPS outage ===\n"); + World w; + w.alt_m = 1000.0; + w.set_gps_hz(5); + w.set_course(50.0f, 0.0f); + w.run_level(120); + float roll_before = w.filter.getRoll_rad(); + unsigned long frozen = w.tow_ms; + for (int i = 0; i < 300; i++) { + w.lat_rad += w.vn * (double)IMU_DT / EARTH_R; + w.lon_rad += w.ve * (double)IMU_DT / (EARTH_R * std::cos(w.lat_rad)); + SimulatedSensors s = levelSensors(0.0f); + w.filter.update((double)IMU_DT, frozen, w.gps_vn, w.gps_ve, w.gps_vd, + w.gps_lat, w.gps_lon, w.gps_alt, s.gx, s.gy, s.gz, s.ax, s.ay, s.az, + s.mx, s.my, s.mz, w.bn, w.be, w.bd, w.hacc, w.vacc, w.sacc); + } + expectNearDeg(w.filter.getRoll_rad(), roll_before * RAD_TO_DEG, 4.0f, + "Roll stable during GPS outage"); + expectTrue(w.filter.getPositionNorthStd_m() > 3.0f, "Position uncertainty grows without GPS"); +} + +static void testAircraftMagIgnoredWhenBanked() { + std::printf("\n=== Aircraft: mag ignored when banked ===\n"); + World w; + w.alt_m = 1000.0; + w.set_gps_hz(5); + w.set_course(50.0f, 0.0f); + w.run_level(60); + float heading_before = w.filter.getHeading_rad(); + for (int i = 0; i < 180; i++) { + SimulatedSensors s = coordinatedTurnSensors(0.0f, 35.0f, 0.0f); + s.mx = 0.0f; + s.my = 20.0f; + s.mz = 40.0f; + w.step(s); + } + float delta = std::fabs(w.filter.getHeading_rad() - heading_before) * RAD_TO_DEG; + if (delta > 180.0f) delta = 360.0f - delta; + std::printf(" Heading change while banked with bad mag: %.2f°\n", delta); + expectTrue(delta < 20.0f, "Banked mag error does not yank heading"); +} + +static void testAircraftCrab() { + std::printf("\n=== Aircraft: crab / horizontal FPA ===\n"); + World w; + w.alt_m = 1000.0; + w.set_gps_hz(5); + w.heading_deg = 0.0f; + w.set_velocity_track(40.0f, 15.0f); // heading north, track 15° + w.run_level(600); + std::printf(" HFPA: %.2f° track: %.2f° heading: %.2f°\n", + w.hfpa_deg(), w.track_deg(), w.filter.getHeading_rad() * RAD_TO_DEG); + expectNear(w.hfpa_deg(), 15.0f, 8.0f, "Horizontal FPA is +crab (drifting right)"); + expectTrue(w.hfpa_deg() > 5.0f, "Positive crab has positive horizontal FPA"); + expectNearDeg(w.filter.getGroundTrack_rad(), 15.0f, 10.0f, "Ground track follows velocity, not heading"); + expectNearDeg(w.filter.getHeading_rad(), 0.0f, 20.0f, "Heading stays near nose-north during crab"); + + World left; + left.alt_m = 1000.0; + left.set_gps_hz(5); + left.heading_deg = 0.0f; + left.set_velocity_track(40.0f, 345.0f); // -15° + left.run_level(600); + std::printf(" Left HFPA: %.2f°\n", left.hfpa_deg()); + expectNear(left.hfpa_deg(), -15.0f, 8.0f, "Horizontal FPA is -crab (drifting left)"); + expectTrue(left.hfpa_deg() < -5.0f, "Negative crab has negative horizontal FPA"); +} + +static void testAircraftEastbound() { + std::printf("\n=== Aircraft: eastbound track and longitude ===\n"); + World w; + w.alt_m = 1000.0; + w.set_gps_hz(5); + double lon0 = w.lon_rad; + w.set_course(50.0f, 90.0f); + w.run_level(600); + expectNearDeg(w.filter.getGroundTrack_rad(), 90.0f, 12.0f, "Ground track eastbound"); + expectNearDeg(w.filter.getHeading_rad(), 90.0f, 15.0f, "Heading eastbound"); + expectNear((float)w.filter.getVelEast_ms(), 50.0f, 3.0f, "East velocity matches airspeed"); + expectNear((float)w.filter.getVelNorth_ms(), 0.0f, 3.0f, "North velocity near zero eastbound"); + expectTrue(w.filter.getLongitude_rad() > lon0, "Longitude increases when flying east"); + expectNear(w.hfpa_deg(), 0.0f, 10.0f, "Horizontal FPA near zero when heading matches track"); } int main() { - std::printf("uNavINS Extended Kalman Filter Unit Tests\n"); + std::printf("uNavINS use-case tests\n"); std::printf("==========================================\n"); - - // Core functionality tests - testInitialization(); - testStraightAndLevelFlight(); - testCoordinatedTurn(); - testSustainedTurnHorizonDrift(); - testGPSOutage(); - testBiasEstimation(); - - // Enhancement tests - testHealthMonitoring(); - testGpsAdaptiveNoise(); - testBarometerFusion(); - testRestDetection(); - testSpeedBasedZupt(); - testEnhancedMagRejection(); - testSensorDelayCompensation(); - + + testInitFirstSampleDtZero(); + testInitRejectsBadAccel(); + testInitZeroMag(); + testInitHeadingCardinals(); + testInitIndependentInstances(); + + testStaticDesk(); + testStaticHandheld(); + testStaticPickup(); + + testWalkingStraight(); + testWalkingCorner(); + testWalkingMagInterference(); + + testCarCruise(); + testCarAccelerate(); + testCarDecelerate(); + testCarLevelTurn(); + testCar360(); + testCarStoplight(); + testCarUrbanGps(); + + testAircraftLevel(); + testAircraftAccelerate(); + testAircraftDecelerate(); + testAircraftClimb(); + testAircraftDescent(); + testAircraftCrab(); + testAircraftEastbound(); + testAircraftCoordinatedTurn(); + testAircraftClimbingTurn(); + testAircraftDescendingTurn(); + testAircraftHorizon360(); + testAircraftClimbBaro(); + testAircraftGpsOutage(); + testAircraftMagIgnoredWhenBanked(); + std::printf("\n==========================================\n"); - std::printf("uNavINS: %d passed, %d failed — %s\n", + std::printf("uNavINS: %d passed, %d failed — %s\n", g_passed, g_failed, g_failed == 0 ? "PASS ✓" : "FAIL ✗"); return g_failed == 0 ? 0 : 1; } diff --git a/ios/NativeAhrs+Emission.mm b/ios/NativeAhrs+Emission.mm index 190f033..8db7d9d 100644 --- a/ios/NativeAhrs+Emission.mm +++ b/ios/NativeAhrs+Emission.mm @@ -34,17 +34,12 @@ - (void)emitAhrsUpdateWithTimestamp:(uint64_t)timestamp { double filterHeading = normalizeHeadingDegrees(_filter->getHeading_rad() * rad2deg); // Heading: X-Plane ground truth when connected, otherwise filter output - double rotationOffset = 0.0; - if (self.rotation == AhrsRotationLeft) { - rotationOffset = 90.0; - } else if (self.rotation == AhrsRotationRight) { - rotationOffset = -90.0; - } double heading; if (self.hasXPlaneHeading) { heading = normalizeHeadingDegrees(self.latestXPlaneHeadingDeg); } else { - heading = normalizeHeadingDegrees(filterHeading + rotationOffset); + // Sensors are already transformed into aviation body axes before the filter. + heading = filterHeading; } double vel_n = _filter->getVelNorth_ms(); @@ -151,6 +146,9 @@ - (void)emitAhrsUpdateWithTimestamp:(uint64_t)timestamp { } int filterHealthStatus = _filter->getHealthStatus(); + BOOL attitudeValid = self.filterInitialized && _filter->isInitialized() && + filterHealthStatus < 3 && + std::isfinite(roll) && std::isfinite(pitch) && std::isfinite(heading); [self emitOnAhrsUpdate:@{ @"roll": @(roll), @"pitch": @(pitch), @@ -172,11 +170,13 @@ - (void)emitAhrsUpdateWithTimestamp:(uint64_t)timestamp { @"longitude": @(lon_deg), @"flightPhase": @(flightPhase), @"flightPhaseConfidence": @(flightPhaseConfidence), - @"attitudeValid": @(self.filterInitialized), + @"attitudeValid": @(attitudeValid), @"altitudeValid": @(self.hasGpsFix || self.baroCalibrated), @"positionValid": @(self.hasGpsFix), @"flightPhaseValid": @(flightPhaseValid), @"filterHealthStatus": @(filterHealthStatus), + @"atRest": @(_filter->isAtRest()), + @"zuptActive": @(_filter->isZuptActive()), }]; self.nextEmitTime = timestamp + (uint64_t)(1000000.0 / self.emitRateHz); diff --git a/ios/NativeAhrs+Location.mm b/ios/NativeAhrs+Location.mm index 3172fb1..75bfdff 100644 --- a/ios/NativeAhrs+Location.mm +++ b/ios/NativeAhrs+Location.mm @@ -112,16 +112,13 @@ - (void)locationManager:(CLLocationManager *)manager // If GPS accuracy is marginal (20-50m), be more cautious // Indoors, even "valid" GPS can have significant errors - if (latestLocation.horizontalAccuracy > 20.0f && latestLocation.horizontalAccuracy <= 50.0f) { - // GPS is marginal - reduce trust in velocity measurements - // If speed is very low but accuracy is poor, likely stationary - if (data.speed_ms < 0.2f) { - // Likely stationary but GPS is noisy - set speed to zero - data.speed_ms = 0.0f; - vel_n = 0.0f; - vel_e = 0.0f; - vel_d = 0.0f; - data.vs_ms = 0.0f; + if (latestLocation.horizontalAccuracy < 0.0 || latestLocation.horizontalAccuracy > 20.0f) { + // Indoor / poor GPS: do not finite-difference a phantom velocity + data.speed_ms = 0.0f; + data.track_deg = 0.0f; + data.vs_ms = 0.0f; + if (latestLocation.speedAccuracy < 0.0f || latestLocation.speedAccuracy > 2.0f) { + data.speedAccuracy_ms = 5.0f; } } diff --git a/ios/NativeAhrs+Recording.mm b/ios/NativeAhrs+Recording.mm index c39cd14..c44a5a8 100644 --- a/ios/NativeAhrs+Recording.mm +++ b/ios/NativeAhrs+Recording.mm @@ -561,8 +561,10 @@ - (void)processPlaybackDataPoint:(RecordingDataPoint *)point { self.lastLonRad = newLonRad; self.lastAltM = altM; self.lastGpsTimestampUs = timestamp; - // Update TOW with new GPS data self.currentTow = (unsigned long)(timestamp / 1000ULL); + [self updateMagneticDeclinationIfNeededForLatRad:self.lastLatRad + lonRad:self.lastLonRad + altM:self.lastAltM]; } } @@ -607,7 +609,7 @@ - (void)processPlaybackDataPoint:(RecordingDataPoint *)point { self.expectedMagD_nT ); - self.filterInitialized = YES; + self.filterInitialized = _filter->isInitialized(); } [self emitAhrsUpdateWithTimestamp:timestamp]; diff --git a/ios/NativeAhrs+Sensors.mm b/ios/NativeAhrs+Sensors.mm index 47f6bc0..367cc47 100644 --- a/ios/NativeAhrs+Sensors.mm +++ b/ios/NativeAhrs+Sensors.mm @@ -133,9 +133,8 @@ - (void)updateMagneticDeclinationIfNeededForLatRad:(double)latRad // Recalculate if moved 1 nautical miles since last declination calc const double MIN_DISTANCE_FOR_DECLINATION_UPDATE_M = 1852.0; BOOL needDeclinationUpdate = NO; - - if (self.lastDeclinationLatRad == 0.0 && self.lastDeclinationLonRad == 0.0) { - // First time - always calculate + + if (!self.hasDeclination) { needDeclinationUpdate = YES; } else { double distanceM = calculateDistanceMeters(self.lastDeclinationLatRad, self.lastDeclinationLonRad, latRad, lonRad); @@ -151,6 +150,7 @@ - (void)updateMagneticDeclinationIfNeededForLatRad:(double)latRad self.magneticDeclination = declination; self.lastDeclinationLatRad = latRad; self.lastDeclinationLonRad = lonRad; + self.hasDeclination = YES; // Calculate expected magnetic field for EKF magnetometer measurement float bn_nT, be_nT, bd_nT; @@ -311,7 +311,7 @@ - (void)ProcessDeviceMotionData:(CMDeviceMotion *)motion { // Store latest forward acceleration for flight phase detection self.lastBodyAccelX = (float)body_accel_x; - self.filterInitialized = YES; + self.filterInitialized = _filter->isInitialized(); [self emitAhrsUpdateWithTimestamp:timestamp_us]; } diff --git a/ios/NativeAhrs+XPlane.mm b/ios/NativeAhrs+XPlane.mm index c4da2eb..96580a4 100644 --- a/ios/NativeAhrs+XPlane.mm +++ b/ios/NativeAhrs+XPlane.mm @@ -426,7 +426,7 @@ - (void)handleXPlaneMessage:(NSString *)json { self.xplaneFirstGpsTimestampUs = timestamp; } // Update TOW with new GPS data - self.currentTow = (unsigned long)((timestamp - self.xplaneFirstGpsTimestampUs) / 1000ULL); + self.currentTow = (unsigned long)((timestamp - self.xplaneFirstGpsTimestampUs) / 1000ULL) + 1; } } @@ -561,7 +561,7 @@ - (void)handleXPlaneMessage:(NSString *)json { self.expectedMagD_nT // Expected mag field D component (nT) from WMM ); - self.filterInitialized = YES; + self.filterInitialized = _filter->isInitialized(); self.xplaneConnected = YES; self.xplaneReconnectAttempts = 0; // Reset reconnection attempts on successful connection [self emitXPlaneConnectionChanged:YES]; diff --git a/ios/NativeAhrs.h b/ios/NativeAhrs.h index 3188042..1a095a4 100755 --- a/ios/NativeAhrs.h +++ b/ios/NativeAhrs.h @@ -276,11 +276,17 @@ typedef NS_ENUM(NSInteger, AhrsRotation) { */ @property(nonatomic, assign) double magneticDeclination; +/** + * @property hasDeclination + * @brief Whether WMM declination has been computed at least once + */ +@property(nonatomic, assign) BOOL hasDeclination; + /** * @property lastDeclinationLatRad * @brief Latitude (radians) where last magnetic declination was calculated * - * Used to determine when to recalculate declination (when moved 10+ nautical + * Used to determine when to recalculate declination (when moved 1+ nautical * miles). */ @property(nonatomic, assign) double lastDeclinationLatRad; @@ -289,7 +295,7 @@ typedef NS_ENUM(NSInteger, AhrsRotation) { * @property lastDeclinationLonRad * @brief Longitude (radians) where last magnetic declination was calculated * - * Used to determine when to recalculate declination (when moved 10+ nautical + * Used to determine when to recalculate declination (when moved 1+ nautical * miles). */ @property(nonatomic, assign) double lastDeclinationLonRad; diff --git a/ios/NativeAhrs.mm b/ios/NativeAhrs.mm index c9d4da8..8432228 100755 --- a/ios/NativeAhrs.mm +++ b/ios/NativeAhrs.mm @@ -127,6 +127,7 @@ - (instancetype)init { _hasValidGroundTrack = NO; _lastDeclinationLatRad = 0.0; _lastDeclinationLonRad = 0.0; + _hasDeclination = NO; _expectedMagN_nT = std::numeric_limits::quiet_NaN(); _expectedMagE_nT = std::numeric_limits::quiet_NaN(); _expectedMagD_nT = std::numeric_limits::quiet_NaN(); @@ -358,6 +359,7 @@ - (void)invalidate { _hasGpsFix = NO; _lastGpsTimestampUs = 0; _currentTow = 0; + _hasDeclination = NO; // ===== Reset Timestamp Tracking ===== // Clear last timestamp to force recalculation of dt on next update diff --git a/package.json b/package.json index 4a36fd1..76ca6cb 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "lib", "android", "ios", - "fusion", + "fusionml", "cpp", "*.podspec", "react-native.config.js", @@ -68,28 +68,29 @@ "registry": "https://registry.npmjs.org/" }, "devDependencies": { - "@commitlint/config-conventional": "^20.4.0", + "@commitlint/config-conventional": "^20.5.3", "@eslint/compat": "^1.4.1", - "@eslint/eslintrc": "^3.3.3", - "@eslint/js": "^9.39.2", - "@react-native-community/cli": "^20.1.1", - "@react-native/babel-preset": "0.83.1", - "@react-native/eslint-config": "^0.83.1", - "@release-it/conventional-changelog": "^10.0.4", + "@eslint/eslintrc": "^3.3.7", + "@eslint/js": "^9.39.5", + "@react-native-community/cli": "^20.2.0", + "@react-native/babel-preset": "0.83.10", + "@react-native/eslint-config": "^0.83.10", + "@release-it/conventional-changelog": "^10.0.6", "@types/jest": "^29.5.14", - "@types/react": "^19.2.10", + "@types/react": "^19.2.18", "commitlint": "^19.8.1", "del-cli": "^7.0.0", - "eslint": "^9.39.2", + "eslint": "^9.39.5", "eslint-config-prettier": "^10.1.8", - "eslint-plugin-prettier": "^5.5.5", + "eslint-plugin-ft-flow": "^2.0.3", + "eslint-plugin-prettier": "^5.5.6", "jest": "^29.7.0", - "prettier": "^3.8.1", - "react": "19.1.0", - "react-native": "0.83.1", - "react-native-builder-bob": "^0.40.17", + "prettier": "^3.9.6", + "react": "19.2.8", + "react-native": "0.83.10", + "react-native-builder-bob": "^0.40.18", "release-it": "^19.2.4", - "turbo": "^2.8.1", + "turbo": "^2.10.12", "typescript": "^5.9.3" }, "peerDependencies": { diff --git a/src/AhrsLogger.ts b/src/AhrsLogger.ts index 379333f..27e0a67 100644 --- a/src/AhrsLogger.ts +++ b/src/AhrsLogger.ts @@ -17,7 +17,6 @@ class Logger { */ private logInternal(level: LogLevel, ...args: unknown[]): void { if (__DEV__) { - // eslint-disable-next-line no-console console[level](...args); } } diff --git a/src/NativeAhrs.ts b/src/NativeAhrs.ts index f901543..9afb5d3 100755 --- a/src/NativeAhrs.ts +++ b/src/NativeAhrs.ts @@ -38,7 +38,7 @@ export interface AhrsData { longitude?: number; // longitude in degrees // Flight phase - flightPhase: number; // 0=GROUND, 1=TAKEOFF, 2=CLIMB, 3=CRUISE, 4=DESCENT, 5=APPROACH, 6=LANDING + flightPhase: number; // 0=GROUND, 1=TAKEOFF_ROLL, 2=TAKEOFF, 3=CLIMB, 4=CRUISE, 5=DESCENT, 6=APPROACH, 7=LANDING, 8=LANDING_ROLL flightPhaseConfidence: number; // 0.0-1.0 confidence in current phase // Validity flags - indicate if outputs are reliable and should be used @@ -49,6 +49,9 @@ export interface AhrsData { // Filter health (0=healthy, 1=warning, 2=error, 3=critical) filterHealthStatus: number; + // Stationary aids from the EKF + atRest: boolean; // IMU variance rest (desk-still) + zuptActive: boolean; // Speed-based zero-velocity update } /** diff --git a/src/__tests__/AhrsCallbackManager.test.ts b/src/__tests__/AhrsCallbackManager.test.ts index a9a053e..73e126d 100644 --- a/src/__tests__/AhrsCallbackManager.test.ts +++ b/src/__tests__/AhrsCallbackManager.test.ts @@ -42,6 +42,9 @@ describe('AhrsCallbackManager', () => { altitudeValid: true, positionValid: true, flightPhaseValid: false, + filterHealthStatus: 0, + atRest: false, + zuptActive: false, ...overrides, }); diff --git a/src/__tests__/AhrsConfiguration.test.ts b/src/__tests__/AhrsConfiguration.test.ts index 2c275ab..94639bc 100644 --- a/src/__tests__/AhrsConfiguration.test.ts +++ b/src/__tests__/AhrsConfiguration.test.ts @@ -101,14 +101,18 @@ describe('AhrsConfiguration', () => { config.setRotation('none'); expect(NativeAhrs.setAhrsRotation).toHaveBeenCalledWith('none'); - expect(logger.log).toHaveBeenCalledWith('🔄 Setting AHRS rotation to none'); + expect(logger.log).toHaveBeenCalledWith( + '🔄 Setting AHRS rotation to none' + ); }); it('should set rotation to "left"', () => { config.setRotation('left'); expect(NativeAhrs.setAhrsRotation).toHaveBeenCalledWith('left'); - expect(logger.log).toHaveBeenCalledWith('🔄 Setting AHRS rotation to left'); + expect(logger.log).toHaveBeenCalledWith( + '🔄 Setting AHRS rotation to left' + ); }); it('should set rotation to "right"', () => { diff --git a/src/__tests__/AhrsLogger.test.ts b/src/__tests__/AhrsLogger.test.ts index 79cd6ea..4894481 100644 --- a/src/__tests__/AhrsLogger.test.ts +++ b/src/__tests__/AhrsLogger.test.ts @@ -65,7 +65,10 @@ describe('AhrsLogger', () => { it('should handle Error objects', () => { const error = new Error('test error'); logger.error('An error occurred:', error); - expect(consoleSpy.error).toHaveBeenCalledWith('An error occurred:', error); + expect(consoleSpy.error).toHaveBeenCalledWith( + 'An error occurred:', + error + ); }); it('should handle undefined and null arguments', () => { diff --git a/src/__tests__/AhrsSensor.test.ts b/src/__tests__/AhrsSensor.test.ts index 313eeb7..48c1c08 100644 --- a/src/__tests__/AhrsSensor.test.ts +++ b/src/__tests__/AhrsSensor.test.ts @@ -12,8 +12,7 @@ import NativeAhrs from '../NativeAhrs'; let storedAhrsCallback: ((data: AhrsData) => void) | null = null; let storedPlaybackCallback: ((event: PlaybackStateEvent) => void) | null = null; let storedXPlaneCallback: - | ((event: { connected: boolean; host: string }) => void) - | null = null; + ((event: { connected: boolean; host: string }) => void) | null = null; // Mock the native module with callback capture jest.mock('../NativeAhrs', () => ({ @@ -160,6 +159,9 @@ describe('AhrsSensor', () => { altitudeValid: true, positionValid: true, flightPhaseValid: false, + filterHealthStatus: 0, + atRest: false, + zuptActive: false, }; emitAhrsData(mockData); @@ -320,7 +322,6 @@ describe('AhrsSensor', () => { }); }); - describe('setQNH', () => { it('should call native setQNH', () => { Ahrs.setQNH(1013.25); diff --git a/src/__tests__/integration.test.ts b/src/__tests__/integration.test.ts index 6ec501a..e5c82af 100644 --- a/src/__tests__/integration.test.ts +++ b/src/__tests__/integration.test.ts @@ -5,8 +5,7 @@ import NativeAhrs from '../NativeAhrs'; let storedAhrsCallback: ((data: AhrsData) => void) | null = null; let storedPlaybackCallback: ((event: PlaybackStateEvent) => void) | null = null; let storedXPlaneCallback: - | ((event: { connected: boolean; host: string }) => void) - | null = null; + ((event: { connected: boolean; host: string }) => void) | null = null; // Mock the native module with callback capture jest.mock('../NativeAhrs', () => ({ @@ -111,6 +110,9 @@ describe('AHRS Integration Tests', () => { altitudeValid: true, positionValid: true, flightPhaseValid: false, + filterHealthStatus: 0, + atRest: false, + zuptActive: false, }; // Simulate native module emitting data @@ -472,6 +474,9 @@ describe('AHRS Integration Tests', () => { altitudeValid: true, positionValid: true, flightPhaseValid: true, + filterHealthStatus: 0, + atRest: false, + zuptActive: false, }; emitAhrsData(fullData); @@ -510,7 +515,11 @@ describe('AHRS Integration Tests', () => { it('should handle getRecordingFiles', async () => { const mockFiles = [ { filename: 'flight1.json.gz', size: 1024, date: Date.now() }, - { filename: 'flight2.json.gz', size: 2048, date: Date.now() - 86400000 }, + { + filename: 'flight2.json.gz', + size: 2048, + date: Date.now() - 86400000, + }, ]; (NativeAhrs.getRecordingFiles as jest.Mock).mockResolvedValue(mockFiles); diff --git a/turbo.json b/turbo.json index 405897e..f71a01b 100644 --- a/turbo.json +++ b/turbo.json @@ -1,6 +1,6 @@ { "$schema": "https://turbo.build/schema.json", - "pipeline": { + "tasks": { "build:android": { "env": ["ORG_GRADLE_PROJECT_newArchEnabled"], "inputs": [ @@ -23,6 +23,7 @@ "package.json", "*.podspec", "ios", + "fusionml", "src/*.ts", "src/*.tsx", "example/package.json", diff --git a/yarn.lock b/yarn.lock index ccb5b3f..265e79d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -31,7 +31,7 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.24.7, @babel/code-frame@npm:^7.27.1": +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.27.1": version: 7.27.1 resolution: "@babel/code-frame@npm:7.27.1" dependencies: @@ -53,6 +53,17 @@ __metadata: languageName: node linkType: hard +"@babel/code-frame@npm:^7.29.0, @babel/code-frame@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/code-frame@npm:7.29.7" + dependencies: + "@babel/helper-validator-identifier": ^7.29.7 + js-tokens: ^4.0.0 + picocolors: ^1.1.1 + checksum: 21b12fe2356e36f6cc3cd8a3721f878bfeea80ce38356979a0518b47b3aafdcc0bd263da75ccc9d51c64d40b1b6df00e768ce2446acb0b7cbec0ae8f905663ad + languageName: node + linkType: hard + "@babel/compat-data@npm:^7.27.2, @babel/compat-data@npm:^7.27.7, @babel/compat-data@npm:^7.28.0": version: 7.28.0 resolution: "@babel/compat-data@npm:7.28.0" @@ -67,6 +78,13 @@ __metadata: languageName: node linkType: hard +"@babel/compat-data@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/compat-data@npm:7.29.7" + checksum: 4424f8fb72f61657c8e811bdf7e5c69af212a15fe362711162b2e00b82f0e0588fb8259ecd9a70c5490562bf51d408b0cb92f277ead71a2390ddddfe7283b35d + languageName: node + linkType: hard + "@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.23.9, @babel/core@npm:^7.25.2": version: 7.28.3 resolution: "@babel/core@npm:7.28.3" @@ -90,7 +108,7 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:^7.24.4, @babel/core@npm:^7.28.6": +"@babel/core@npm:^7.24.4": version: 7.28.6 resolution: "@babel/core@npm:7.28.6" dependencies: @@ -113,6 +131,29 @@ __metadata: languageName: node linkType: hard +"@babel/core@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/core@npm:7.29.7" + dependencies: + "@babel/code-frame": ^7.29.7 + "@babel/generator": ^7.29.7 + "@babel/helper-compilation-targets": ^7.29.7 + "@babel/helper-module-transforms": ^7.29.7 + "@babel/helpers": ^7.29.7 + "@babel/parser": ^7.29.7 + "@babel/template": ^7.29.7 + "@babel/traverse": ^7.29.7 + "@babel/types": ^7.29.7 + "@jridgewell/remapping": ^2.3.5 + convert-source-map: ^2.0.0 + debug: ^4.1.0 + gensync: ^1.0.0-beta.2 + json5: ^2.2.3 + semver: ^6.3.1 + checksum: 95149e98ffde5b9d903459c284fbcf1f9ad8b3a833d69fdfe1aa7185821a102af1925324fe2892caf4b643b151c1dc948510d1e25a5e3c6c6c6f17f6712eb38e + languageName: node + linkType: hard + "@babel/eslint-parser@npm:^7.25.1": version: 7.28.0 resolution: "@babel/eslint-parser@npm:7.28.0" @@ -127,7 +168,7 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.25.0, @babel/generator@npm:^7.28.3, @babel/generator@npm:^7.7.2": +"@babel/generator@npm:^7.28.3, @babel/generator@npm:^7.7.2": version: 7.28.3 resolution: "@babel/generator@npm:7.28.3" dependencies: @@ -140,29 +181,29 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/generator@npm:7.28.5" +"@babel/generator@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/generator@npm:7.28.6" dependencies: - "@babel/parser": ^7.28.5 - "@babel/types": ^7.28.5 + "@babel/parser": ^7.28.6 + "@babel/types": ^7.28.6 "@jridgewell/gen-mapping": ^0.3.12 "@jridgewell/trace-mapping": ^0.3.28 jsesc: ^3.0.2 - checksum: 3e86fa0197bb33394a85a73dbbca92bb1b3f250a30294c7e327359c0978ad90f36f3d71c7f2965a3fc349cfa82becc8f87e7421c75796c8bc48dd9010dd866d1 + checksum: 74f62f140e301c8c21652f7db3bc275008708272c0395f178ba6953297af50c4ea484874a44b3f292d242ce8a977fd3f31d9d3a3501c3aaca9cd46e3b1cded01 languageName: node linkType: hard -"@babel/generator@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/generator@npm:7.28.6" +"@babel/generator@npm:^7.29.1, @babel/generator@npm:^7.29.7, @babel/generator@npm:^7.29.8": + version: 7.29.8 + resolution: "@babel/generator@npm:7.29.8" dependencies: - "@babel/parser": ^7.28.6 - "@babel/types": ^7.28.6 + "@babel/parser": ^7.29.8 + "@babel/types": ^7.29.8 "@jridgewell/gen-mapping": ^0.3.12 "@jridgewell/trace-mapping": ^0.3.28 jsesc: ^3.0.2 - checksum: 74f62f140e301c8c21652f7db3bc275008708272c0395f178ba6953297af50c4ea484874a44b3f292d242ce8a977fd3f31d9d3a3501c3aaca9cd46e3b1cded01 + checksum: e3d80ad2ce45ca974cb14e084350d590aa62e840d5028bb7771e910b1727b6646afcc9815560e3c6e3526f115b71b18a7f7139ada76a80287d5bd25b84c17e8d languageName: node linkType: hard @@ -175,6 +216,15 @@ __metadata: languageName: node linkType: hard +"@babel/helper-annotate-as-pure@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-annotate-as-pure@npm:7.29.7" + dependencies: + "@babel/types": ^7.29.7 + checksum: acd9e128de634a5144b5d622357d018fa616de45f64c74e42007c048dd15d0a0be213f4d5a2bf02307bdaddf053791b87900a99d183de828c08dc3b556329009 + languageName: node + linkType: hard + "@babel/helper-compilation-targets@npm:^7.27.1, @babel/helper-compilation-targets@npm:^7.27.2": version: 7.27.2 resolution: "@babel/helper-compilation-targets@npm:7.27.2" @@ -201,6 +251,19 @@ __metadata: languageName: node linkType: hard +"@babel/helper-compilation-targets@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-compilation-targets@npm:7.29.7" + dependencies: + "@babel/compat-data": ^7.29.7 + "@babel/helper-validator-option": ^7.29.7 + browserslist: ^4.24.0 + lru-cache: ^5.1.1 + semver: ^6.3.1 + checksum: f60a943937f4eba0e671aa28551cb45569fd081c1e30a52ede167860475dc0417f3dbdf2a0fa3f086965595c7070aa76308da60cc0319860de05db4ed2a431f7 + languageName: node + linkType: hard + "@babel/helper-create-class-features-plugin@npm:^7.27.1, @babel/helper-create-class-features-plugin@npm:^7.28.3": version: 7.28.3 resolution: "@babel/helper-create-class-features-plugin@npm:7.28.3" @@ -218,20 +281,20 @@ __metadata: languageName: node linkType: hard -"@babel/helper-create-class-features-plugin@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/helper-create-class-features-plugin@npm:7.28.6" +"@babel/helper-create-class-features-plugin@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-create-class-features-plugin@npm:7.29.7" dependencies: - "@babel/helper-annotate-as-pure": ^7.27.3 - "@babel/helper-member-expression-to-functions": ^7.28.5 - "@babel/helper-optimise-call-expression": ^7.27.1 - "@babel/helper-replace-supers": ^7.28.6 - "@babel/helper-skip-transparent-expression-wrappers": ^7.27.1 - "@babel/traverse": ^7.28.6 + "@babel/helper-annotate-as-pure": ^7.29.7 + "@babel/helper-member-expression-to-functions": ^7.29.7 + "@babel/helper-optimise-call-expression": ^7.29.7 + "@babel/helper-replace-supers": ^7.29.7 + "@babel/helper-skip-transparent-expression-wrappers": ^7.29.7 + "@babel/traverse": ^7.29.7 semver: ^6.3.1 peerDependencies: "@babel/core": ^7.0.0 - checksum: f886ab302a83f8e410384aa635806b22374897fd9e3387c737ab9d91d1214bf9f7e57ae92619bd25dea63c9c0a49b25b44eb807873332e0eb9549219adc73639 + checksum: c954e4bfe423a277cdcbad64344637cf696ddcd80085fce5f284b02a0c700af0d2d7b61468a06d9e296e948de60ece138921b65564aec023a9d9594f5d9fe18d languageName: node linkType: hard @@ -248,16 +311,16 @@ __metadata: languageName: node linkType: hard -"@babel/helper-create-regexp-features-plugin@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/helper-create-regexp-features-plugin@npm:7.28.5" +"@babel/helper-create-regexp-features-plugin@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-create-regexp-features-plugin@npm:7.29.7" dependencies: - "@babel/helper-annotate-as-pure": ^7.27.3 + "@babel/helper-annotate-as-pure": ^7.29.7 regexpu-core: ^6.3.1 semver: ^6.3.1 peerDependencies: "@babel/core": ^7.0.0 - checksum: de202103e6ff8cd8da0d62eb269fcceb29857f3fa16173f0ff38188fd514e9ad4901aef1d590ff8ba25381644b42eaf70ad9ba91fda59fe7aa6a5e694cdde267 + checksum: 702a34db6c064a2c26675b717b3af88b8acaeb5341e2285792a873a67a16ec4cbe987ba72e055db198b2e03ead05600d8c9c0c1e7436708e95865bbfb3516026 languageName: node linkType: hard @@ -276,6 +339,21 @@ __metadata: languageName: node linkType: hard +"@babel/helper-define-polyfill-provider@npm:^0.6.8": + version: 0.6.8 + resolution: "@babel/helper-define-polyfill-provider@npm:0.6.8" + dependencies: + "@babel/helper-compilation-targets": ^7.28.6 + "@babel/helper-plugin-utils": ^7.28.6 + debug: ^4.4.3 + lodash.debounce: ^4.0.8 + resolve: ^1.22.11 + peerDependencies: + "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 + checksum: 39fef64ade79253836320c7826895d948ab5e8e21479cf29f5d6bb5284126693ca537b6ace9d9b7b515a8be66bd4a8a7d7687f9b25b7574a52dae7790fcd3a4e + languageName: node + linkType: hard + "@babel/helper-globals@npm:^7.28.0": version: 7.28.0 resolution: "@babel/helper-globals@npm:7.28.0" @@ -283,6 +361,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-globals@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-globals@npm:7.29.7" + checksum: 6deaf9846a415c7f110ac153e8c3d81e3543c9b685aa9fd9a59b4235f402e2b760129d3df49466daea9162f0cf73ff98a0ec7f180448a3449ca14ae5e1117b42 + languageName: node + linkType: hard + "@babel/helper-member-expression-to-functions@npm:^7.27.1": version: 7.27.1 resolution: "@babel/helper-member-expression-to-functions@npm:7.27.1" @@ -293,13 +378,13 @@ __metadata: languageName: node linkType: hard -"@babel/helper-member-expression-to-functions@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/helper-member-expression-to-functions@npm:7.28.5" +"@babel/helper-member-expression-to-functions@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-member-expression-to-functions@npm:7.29.7" dependencies: - "@babel/traverse": ^7.28.5 - "@babel/types": ^7.28.5 - checksum: 447d385233bae2eea713df1785f819b5a5ca272950740da123c42d23f491045120f0fbbb5609c091f7a9bbd40f289a442846dde0cb1bf0c59440fa093690cf7c + "@babel/traverse": ^7.29.7 + "@babel/types": ^7.29.7 + checksum: 79d5f095b4bafadff3d1ec316d9f17ec85940fece957db62dd523b08e142da73c53180d1bce2fdc4d523e3889bb01b39bea70ad968b6e2f4dbdc66ebd1055b8a languageName: node linkType: hard @@ -323,6 +408,16 @@ __metadata: languageName: node linkType: hard +"@babel/helper-module-imports@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-module-imports@npm:7.29.7" + dependencies: + "@babel/traverse": ^7.29.7 + "@babel/types": ^7.29.7 + checksum: ad5a768fc9c162620b7f5b7645c6c2efee1e6f9df432bb79651888661a7e4cd91dac7192f3ddcfd6de4778a089e9fb30fafae768156aa73a07eeca425f64e849 + languageName: node + linkType: hard + "@babel/helper-module-transforms@npm:^7.27.1, @babel/helper-module-transforms@npm:^7.28.3": version: 7.28.3 resolution: "@babel/helper-module-transforms@npm:7.28.3" @@ -349,6 +444,19 @@ __metadata: languageName: node linkType: hard +"@babel/helper-module-transforms@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-module-transforms@npm:7.29.7" + dependencies: + "@babel/helper-module-imports": ^7.29.7 + "@babel/helper-validator-identifier": ^7.29.7 + "@babel/traverse": ^7.29.7 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 484f6d02975d304f680d44e331d4b832d67e51917483985eab7b853664452b5a627366e7a9d421200ec772a123213a591e28b40636566afeac189411ba3f45a8 + languageName: node + linkType: hard + "@babel/helper-optimise-call-expression@npm:^7.27.1": version: 7.27.1 resolution: "@babel/helper-optimise-call-expression@npm:7.27.1" @@ -358,6 +466,15 @@ __metadata: languageName: node linkType: hard +"@babel/helper-optimise-call-expression@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-optimise-call-expression@npm:7.29.7" + dependencies: + "@babel/types": ^7.29.7 + checksum: 6b477e01b403fd48349336cb1d94722bff4fa54af2841b5fa950c557b796f4ecc14724052252ed1362ccfc23d1c09c54dc03e182fea59d3dc5bd69f8c626ba25 + languageName: node + linkType: hard + "@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.27.1, @babel/helper-plugin-utils@npm:^7.8.0": version: 7.27.1 resolution: "@babel/helper-plugin-utils@npm:7.27.1" @@ -372,6 +489,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-plugin-utils@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-plugin-utils@npm:7.29.7" + checksum: b0a183abcc6670afa4861425fa428217d8ebadce062d5b43117919e8715f820080fd63bbfcf0e43c6e0e7d21a96b21f635c46dda80bdb0ce7e8a762ebee1d8d9 + languageName: node + linkType: hard + "@babel/helper-remap-async-to-generator@npm:^7.27.1": version: 7.27.1 resolution: "@babel/helper-remap-async-to-generator@npm:7.27.1" @@ -385,6 +509,19 @@ __metadata: languageName: node linkType: hard +"@babel/helper-remap-async-to-generator@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-remap-async-to-generator@npm:7.29.7" + dependencies: + "@babel/helper-annotate-as-pure": ^7.29.7 + "@babel/helper-wrap-function": ^7.29.7 + "@babel/traverse": ^7.29.7 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 98338ad6e34ebb4be2dc23f8d9199d28d6d8ac6a2ce8b90fe9efdf3595b39748321528d9f2540ec0586a6e45f7c84f5f623fbf980c5efa7fa9ba7ce837ea4b20 + languageName: node + linkType: hard + "@babel/helper-replace-supers@npm:^7.27.1": version: 7.27.1 resolution: "@babel/helper-replace-supers@npm:7.27.1" @@ -398,16 +535,16 @@ __metadata: languageName: node linkType: hard -"@babel/helper-replace-supers@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/helper-replace-supers@npm:7.28.6" +"@babel/helper-replace-supers@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-replace-supers@npm:7.29.7" dependencies: - "@babel/helper-member-expression-to-functions": ^7.28.5 - "@babel/helper-optimise-call-expression": ^7.27.1 - "@babel/traverse": ^7.28.6 + "@babel/helper-member-expression-to-functions": ^7.29.7 + "@babel/helper-optimise-call-expression": ^7.29.7 + "@babel/traverse": ^7.29.7 peerDependencies: "@babel/core": ^7.0.0 - checksum: aa6530a52010883b6be88465e3b9e789509786a40203650a23a51c315f7442b196e5925fb8e2d66d1e3dc2c604cdc817bd8c5c170dbb322ab5ebc7486fd8a022 + checksum: f7eb9a6b035d9d45250c880eb09605f95998998e828ec90759ed45764fe0abeee583419969de8b8dee551163dff914c9fc6ace90c1d819c56c23146f8df525db languageName: node linkType: hard @@ -421,6 +558,16 @@ __metadata: languageName: node linkType: hard +"@babel/helper-skip-transparent-expression-wrappers@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.29.7" + dependencies: + "@babel/traverse": ^7.29.7 + "@babel/types": ^7.29.7 + checksum: a5800bfcdca6cef7f6fe33ac02a0f05ff33da9746f97806553f249733f7ba8400290a17f3831d7faa5d91656f254ab749931f53c8a29f301d958d7dd00499637 + languageName: node + linkType: hard + "@babel/helper-string-parser@npm:^7.27.1": version: 7.27.1 resolution: "@babel/helper-string-parser@npm:7.27.1" @@ -428,6 +575,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-string-parser@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-string-parser@npm:7.29.7" + checksum: 4c229d2c2296b6c94439e87ecddf3a93cee3ffd2d4cee0b4c28079275bed3de4e02cd943e4cb06036d1d9407549c4e3423006b61a46215c482fce902ee02bc0b + languageName: node + linkType: hard + "@babel/helper-validator-identifier@npm:^7.27.1": version: 7.27.1 resolution: "@babel/helper-validator-identifier@npm:7.27.1" @@ -442,6 +596,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-identifier@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-validator-identifier@npm:7.29.7" + checksum: cc7779e96fe9c9478c96ca4bf04fc338b95b501aa5abe78178307dea282d4a5dc23d443efb12dde8e8c5636d03dd00e443532e6ed7f15fa7977349af1f87ba4d + languageName: node + linkType: hard + "@babel/helper-validator-option@npm:^7.27.1": version: 7.27.1 resolution: "@babel/helper-validator-option@npm:7.27.1" @@ -449,6 +610,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-option@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-validator-option@npm:7.29.7" + checksum: aeb6aa966f59300d3cc2fea7c68e1dfd7ad011fc10e535c8e2b2de3094b27c859428dc7220f16420350f8b1cde99da120b673be04bcb0c2f37b56258c96bed58 + languageName: node + linkType: hard + "@babel/helper-wrap-function@npm:^7.27.1": version: 7.28.3 resolution: "@babel/helper-wrap-function@npm:7.28.3" @@ -460,6 +628,17 @@ __metadata: languageName: node linkType: hard +"@babel/helper-wrap-function@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helper-wrap-function@npm:7.29.7" + dependencies: + "@babel/template": ^7.29.7 + "@babel/traverse": ^7.29.7 + "@babel/types": ^7.29.7 + checksum: 2e6dfca94a10a3672a6b0ff337c80f27d7c66f3ea110969e833529a2d5bfbb5e53ab40abf6fad4657b8f810fcab2e3d56998f8da89bfe82a58267c5e8bc06e9a + languageName: node + linkType: hard + "@babel/helpers@npm:^7.28.3": version: 7.28.3 resolution: "@babel/helpers@npm:7.28.3" @@ -480,6 +659,16 @@ __metadata: languageName: node linkType: hard +"@babel/helpers@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/helpers@npm:7.29.7" + dependencies: + "@babel/template": ^7.29.7 + "@babel/types": ^7.29.7 + checksum: b5c4ed0ce5983c5599cd01b3948444b77ba2fa47bf6282a82afdcbe45eb8cc5b7986194e3920ef2760f533c6a775504e6b00a66960c0a3bc52909920b8433908 + languageName: node + linkType: hard + "@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.25.3, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.28.3": version: 7.28.3 resolution: "@babel/parser@npm:7.28.3" @@ -502,14 +691,14 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/parser@npm:7.28.5" +"@babel/parser@npm:^7.29.0, @babel/parser@npm:^7.29.7, @babel/parser@npm:^7.29.8": + version: 7.29.8 + resolution: "@babel/parser@npm:7.29.8" dependencies: - "@babel/types": ^7.28.5 + "@babel/types": ^7.29.8 bin: parser: ./bin/babel-parser.js - checksum: 5c2456e3f26c70d4a3ce1a220b529a91a2df26c54a2894fd0dea2342699ea1067ffdda9f0715eeab61da46ff546fd5661bc70be6d8d11977cbe21f5f0478819a + checksum: c8bbea3c87b8593e78e8901841b068cc004ece24bf3ee44e11b4a3e04f10dab513125ca290c2cc04ba981399b662dbebb0a675016417d600e99272e86d59b375 languageName: node linkType: hard @@ -525,15 +714,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.28.5" +"@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.29.7" dependencies: - "@babel/helper-plugin-utils": ^7.27.1 - "@babel/traverse": ^7.28.5 + "@babel/helper-plugin-utils": ^7.29.7 + "@babel/traverse": ^7.29.7 peerDependencies: "@babel/core": ^7.0.0 - checksum: 749b40a963d5633f554cad0336245cb6c1c1393c70a3fddcf302d86a1a42b35efdd2ed62056b88db66f3900887ae1cee9a3eeec89799c22e0cf65059f0dfd142 + checksum: 83cca77d175f3e66460a004648c8a645da880d5a9db96b03abd433e05bc4357aec0418d17b05fbfecfa679438b8eb8d8adc71b8f4db86cfaf6e6b0373b39a05e languageName: node linkType: hard @@ -548,6 +737,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-bugfix-safari-class-field-initializer-scope@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-bugfix-safari-class-field-initializer-scope@npm:7.29.7" + dependencies: + "@babel/helper-plugin-utils": ^7.29.7 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: ad06de66ccfb19f0f04e6124d144f3fef72fa5191861b1d04bc32cab87ce43958810d9632eac5881ef991a78b33e68588e3d90e76a63d917bd5a7ff4c96618f8 + languageName: node + linkType: hard + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.27.1" @@ -559,6 +759,29 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.29.7" + dependencies: + "@babel/helper-plugin-utils": ^7.29.7 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 2189a2a648948107c59ad3bf028e5b71a85b28c840facfead769a33c5f63ae4ec0f147e6a2e664a91a506d4405f5a8972d2ca628f3b64d03edd7620d770761fb + languageName: node + linkType: hard + +"@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@npm:7.29.7" + dependencies: + "@babel/helper-plugin-utils": ^7.29.7 + "@babel/helper-skip-transparent-expression-wrappers": ^7.29.7 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: a9bd13c2600bdfcb5b35590e210bcb30f009fda9bd1556567757ea4fcb8ca247750331d6d10774d68127cae4e529bc79abd86a28fe5e2a1cc2cc00e75964ac52 + languageName: node + linkType: hard + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:7.27.1" @@ -572,6 +795,19 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:7.29.7" + dependencies: + "@babel/helper-plugin-utils": ^7.29.7 + "@babel/helper-skip-transparent-expression-wrappers": ^7.29.7 + "@babel/plugin-transform-optional-chaining": ^7.29.7 + peerDependencies: + "@babel/core": ^7.13.0 + checksum: 76a494ec4f52a127b0208c4574a6da36f6ff5f30484306921762db549fa7d9d9183c837759eb68c0c9e5a56013aa247e6e02e02263384d2a103240353ae0ceb6 + languageName: node + linkType: hard + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:^7.28.3": version: 7.28.3 resolution: "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:7.28.3" @@ -584,15 +820,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:7.28.6" +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:7.29.7" dependencies: - "@babel/helper-plugin-utils": ^7.28.6 - "@babel/traverse": ^7.28.6 + "@babel/helper-plugin-utils": ^7.29.7 + "@babel/traverse": ^7.29.7 peerDependencies: "@babel/core": ^7.0.0 - checksum: f1341f829f809c8685d839669953a478f8a40d1d53f4f5e1972bf39ff4e1ece148319340292d6e0c3641157268b435cbb99b3ac2f3cefe9fca9e81b8f62d6d71 + checksum: 7a58b4b32f4c04b86160dc7900612904e2b88d42c8b89d59a89d3c343f320ad096d73fb453a2e8bf8e97c9d4af3563043bc6d1e3def53c819812cb03f3f873c8 languageName: node linkType: hard @@ -704,14 +940,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-import-assertions@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-syntax-import-assertions@npm:7.28.6" +"@babel/plugin-syntax-import-assertions@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-syntax-import-assertions@npm:7.29.7" dependencies: - "@babel/helper-plugin-utils": ^7.28.6 + "@babel/helper-plugin-utils": ^7.29.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 25017235e1e2c4ed892aa327a3fa10f4209cc618c6dd7806fc40c07d8d7d24a39743d3d5568b8d1c8f416cffe03c174e78874ded513c9338b07a7ab1dcbab050 + checksum: a7f24858e7e833c2ee25779a355b5eff46e4bc93c98b06bda7ea0fd12faf99c4954e0f71074485512045920432790e0269aa562dd4d2085c3f8660ed1cfde8a1 languageName: node linkType: hard @@ -726,14 +962,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-import-attributes@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-syntax-import-attributes@npm:7.28.6" +"@babel/plugin-syntax-import-attributes@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-syntax-import-attributes@npm:7.29.7" dependencies: - "@babel/helper-plugin-utils": ^7.28.6 + "@babel/helper-plugin-utils": ^7.29.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 6c8c6a5988dbb9799d6027360d1a5ba64faabf551f2ef11ba4eade0c62253b5c85d44ddc8eb643c74b9acb2bcaa664a950bd5de9a5d4aef291c4f2a48223bb4b + checksum: 9f47345d09aae16b7ab52ecaf541cde3e3ae1e57e3eb2d4088e062b29dfbd67db55d42d529840557583d66121e2a98788df7a455401cc6d635c8b7700a02efc9 languageName: node linkType: hard @@ -892,6 +1128,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-arrow-functions@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-arrow-functions@npm:7.29.7" + dependencies: + "@babel/helper-plugin-utils": ^7.29.7 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 0037fd7563c7c91cddb8ce104e270bc260190d29c7a297df65e4306471010b4343366de13fab4602cf8ee6c672d3b313b34a01b62f27a333cad16908c83368d8 + languageName: node + linkType: hard + "@babel/plugin-transform-async-generator-functions@npm:^7.25.4, @babel/plugin-transform-async-generator-functions@npm:^7.28.0": version: 7.28.0 resolution: "@babel/plugin-transform-async-generator-functions@npm:7.28.0" @@ -905,16 +1152,16 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-async-generator-functions@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-async-generator-functions@npm:7.28.6" +"@babel/plugin-transform-async-generator-functions@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-async-generator-functions@npm:7.29.7" dependencies: - "@babel/helper-plugin-utils": ^7.28.6 - "@babel/helper-remap-async-to-generator": ^7.27.1 - "@babel/traverse": ^7.28.6 + "@babel/helper-plugin-utils": ^7.29.7 + "@babel/helper-remap-async-to-generator": ^7.29.7 + "@babel/traverse": ^7.29.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 0c9e362039c7b0d6620845021b8f576908063a4bfd5857feba59d6097204d405c693d9a6f21b6f5cd846a2722cabd898a1e680dc976abc3e0c4b6edae623854e + checksum: d8239f43b4051b12cd7a5581367bd2b13ac26235da39d79f182999879f010124341ac500f480140b0961a62ec451a0a963b421c509f9c1a306c313f10fc60451 languageName: node linkType: hard @@ -931,16 +1178,16 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-async-to-generator@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-async-to-generator@npm:7.28.6" +"@babel/plugin-transform-async-to-generator@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-async-to-generator@npm:7.29.7" dependencies: - "@babel/helper-module-imports": ^7.28.6 - "@babel/helper-plugin-utils": ^7.28.6 - "@babel/helper-remap-async-to-generator": ^7.27.1 + "@babel/helper-module-imports": ^7.29.7 + "@babel/helper-plugin-utils": ^7.29.7 + "@babel/helper-remap-async-to-generator": ^7.29.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: bca5774263ec01dd2bf71c74bbaf7baa183bf03576636b7826c3346be70c8c8cb15cff549112f2983c36885131a0afde6c443591278c281f733ee17f455aa9b1 + checksum: e24db9c4c69121daab25883f9a96e6849fa664c78c6bbcfb77fe0a0c6ab29b81ba39e8497985367463d3a88deac3b5bbe15dd1c5d0e5dd492cfccf8efdd27452 languageName: node linkType: hard @@ -955,6 +1202,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-block-scoped-functions@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-block-scoped-functions@npm:7.29.7" + dependencies: + "@babel/helper-plugin-utils": ^7.29.7 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 24f9712ade98061cd22088709b86b8e3e19ea416dbe5a69ad504fc3f8f2179af5bdeb32fcb9c24fc861bef515e41ae5ef72cd909e0e42bb0cf15838c4e737149 + languageName: node + linkType: hard + "@babel/plugin-transform-block-scoping@npm:^7.25.0, @babel/plugin-transform-block-scoping@npm:^7.28.0": version: 7.28.0 resolution: "@babel/plugin-transform-block-scoping@npm:7.28.0" @@ -966,14 +1224,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-block-scoping@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-block-scoping@npm:7.28.6" +"@babel/plugin-transform-block-scoping@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-block-scoping@npm:7.29.7" dependencies: - "@babel/helper-plugin-utils": ^7.28.6 + "@babel/helper-plugin-utils": ^7.29.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: cb4f71ac4fc7b32c2e3cc167eb9e7a1a11562127d702e3b5093567750e9a4eb11a29ae5a917f62741bf9d5792bfe3022cbcdcc7bb927ddb6f627b6749a38c118 + checksum: a17c02f8dfcaf2c26c0c323aaa8e3a1616f2de3ea0a4947e16789bb64cb0f177deff388eb1390c100c82d7e6fecd4d583646bb9305fb3df3ca824b1bdbebdc8e languageName: node linkType: hard @@ -989,15 +1247,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-class-properties@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-class-properties@npm:7.28.6" +"@babel/plugin-transform-class-properties@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-class-properties@npm:7.29.7" dependencies: - "@babel/helper-create-class-features-plugin": ^7.28.6 - "@babel/helper-plugin-utils": ^7.28.6 + "@babel/helper-create-class-features-plugin": ^7.29.7 + "@babel/helper-plugin-utils": ^7.29.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 200f30d44b36a768fa3a8cf690db9e333996af2ad14d9fa1b4c91a427ed9302907873b219b4ce87517ca1014a810eb2e929a6a66be68473f72b546fc64d04fbc + checksum: 0cc5e7a882e29eead360f02ef79f6b2ec3b3813213b1513d8fdaa931d1d1361fccc92fbacc9b399e42495953d9d6fc722f283b5f3aa272fe016a0b5fe1e6a130 languageName: node linkType: hard @@ -1013,15 +1271,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-class-static-block@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-class-static-block@npm:7.28.6" +"@babel/plugin-transform-class-static-block@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-class-static-block@npm:7.29.7" dependencies: - "@babel/helper-create-class-features-plugin": ^7.28.6 - "@babel/helper-plugin-utils": ^7.28.6 + "@babel/helper-create-class-features-plugin": ^7.29.7 + "@babel/helper-plugin-utils": ^7.29.7 peerDependencies: "@babel/core": ^7.12.0 - checksum: 3db326156f73a0c0d1e2ea4d73e082b9ace2f6a9c965db1c2e51f3a186751b8b91bafb184d05e046bf970b50ecfde1f74862dd895f9a5ea0fad328369d74cfc4 + checksum: fe94eb94d8417de753a26f06a3c50780cdfc3f07e10bcd09dde2fe61dcd9a2714b83b1b2d36733328a3ad09b02e84fa06197f757644ffbcca1673e87c64ecb76 languageName: node linkType: hard @@ -1041,19 +1299,19 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-classes@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-classes@npm:7.28.6" +"@babel/plugin-transform-classes@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-classes@npm:7.29.7" dependencies: - "@babel/helper-annotate-as-pure": ^7.27.3 - "@babel/helper-compilation-targets": ^7.28.6 - "@babel/helper-globals": ^7.28.0 - "@babel/helper-plugin-utils": ^7.28.6 - "@babel/helper-replace-supers": ^7.28.6 - "@babel/traverse": ^7.28.6 + "@babel/helper-annotate-as-pure": ^7.29.7 + "@babel/helper-compilation-targets": ^7.29.7 + "@babel/helper-globals": ^7.29.7 + "@babel/helper-plugin-utils": ^7.29.7 + "@babel/helper-replace-supers": ^7.29.7 + "@babel/traverse": ^7.29.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: bddeefbfd1966272e5da6a0844d68369a0f43c286816c8b379dfd576cf835b8bc652089ef337b0334ff3ae6c9652d56d8332b78a7d29176534265c39856e4822 + checksum: cc45ff5b5b063339131cd701266af90506db8640e56494de0c78f882da6317f057951bf6507c5b48a0278cf5dad21691baf2af05e24b8c26b0725d677088edbf languageName: node linkType: hard @@ -1069,15 +1327,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-computed-properties@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-computed-properties@npm:7.28.6" +"@babel/plugin-transform-computed-properties@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-computed-properties@npm:7.29.7" dependencies: - "@babel/helper-plugin-utils": ^7.28.6 - "@babel/template": ^7.28.6 + "@babel/helper-plugin-utils": ^7.29.7 + "@babel/template": ^7.29.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: fd1fcc55003a2584c7461bf214ae9e9fce370ad09339319e99e29e5e55a8a3bd485d10805b3d69636a738208761b3a5b0dafdd023534396be45a36409082b014 + checksum: ad945a48e6826c3b34f825e780ab33bf91e18c7924dd263e44bef8d8a6350636305b22af8f00793d3767482004651f4bfb9fed0c92f05c01b99ae80d79956e67 languageName: node linkType: hard @@ -1093,15 +1351,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-destructuring@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/plugin-transform-destructuring@npm:7.28.5" +"@babel/plugin-transform-destructuring@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-destructuring@npm:7.29.7" dependencies: - "@babel/helper-plugin-utils": ^7.27.1 - "@babel/traverse": ^7.28.5 + "@babel/helper-plugin-utils": ^7.29.7 + "@babel/traverse": ^7.29.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 74a06e55e715cfda0fdd8be53d2655d64dfdc28dffaede329d42548fd5b1449ad26a4ce43a24c3fd277b96f8b2010c7b3915afa8297911cda740cc5cc3a81f38 + checksum: 5f56c030beec2ae1640909eb5213fc655f0062046b28699d049cc8b00fb7d9aa1c5ba1dc1c7ee41c8bae97be778066f1f9ea2ecd8fff872a1e8f10fd3addf18c languageName: node linkType: hard @@ -1117,15 +1375,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-dotall-regex@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-dotall-regex@npm:7.28.6" +"@babel/plugin-transform-dotall-regex@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-dotall-regex@npm:7.29.7" dependencies: - "@babel/helper-create-regexp-features-plugin": ^7.28.5 - "@babel/helper-plugin-utils": ^7.28.6 + "@babel/helper-create-regexp-features-plugin": ^7.29.7 + "@babel/helper-plugin-utils": ^7.29.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 866ffbbdee77fa955063b37c75593db8dbbe46b1ebb64cc788ea437e3a9aa41cb7b9afcee617c678a32b6705baa0892ec8e5d4b8af3bbb0ab1b254514ccdbd37 + checksum: 537df0fb915a420df715a2f4da16ab6c08ce2370521edef9a8a59af23a533314109f6abd836c5e127c8cea4a46bc4a05692670cf7ad64868c286075fa2d7848c languageName: node linkType: hard @@ -1140,6 +1398,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-duplicate-keys@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-duplicate-keys@npm:7.29.7" + dependencies: + "@babel/helper-plugin-utils": ^7.29.7 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 39bf312a798792e361d2afa9e900eb7d7fd853239a776912bafc5bb3799886374d54a0882d21517296086abdd3e5458405f6f3a7da5dcb493bc86c5a32576bab + languageName: node + linkType: hard + "@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:7.27.1" @@ -1152,15 +1421,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:7.28.6" +"@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:7.29.7" dependencies: - "@babel/helper-create-regexp-features-plugin": ^7.28.5 - "@babel/helper-plugin-utils": ^7.28.6 + "@babel/helper-create-regexp-features-plugin": ^7.29.7 + "@babel/helper-plugin-utils": ^7.29.7 peerDependencies: "@babel/core": ^7.0.0 - checksum: 3f2e2b85199adfdc3297983412c2ecdacc0004bc5ac3263d29909219b8c5afa2ca49e3b6efc11ce67034d5780eef27882a94873444cf27d841d7fa7f01d7dcff + checksum: fa7fcdbede10dbc06fe4f861e90286f7f599d3f3c43e69445e63c3f48422fd34db0b0dd9bbebccd1dbd04a50fca4ab22fd82d28e7bc8fe9b6d5790c9e694d380 languageName: node linkType: hard @@ -1175,6 +1444,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-dynamic-import@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-dynamic-import@npm:7.29.7" + dependencies: + "@babel/helper-plugin-utils": ^7.29.7 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 58c035e6b9103c225f3d181671d9b3dec140351c3ecf12bc3a66b8653be41161f20135ada00a703244c2bfc9dd57b62fc54f77f2f6fe43df6c598358f377e6fa + languageName: node + linkType: hard + "@babel/plugin-transform-explicit-resource-management@npm:^7.28.0": version: 7.28.0 resolution: "@babel/plugin-transform-explicit-resource-management@npm:7.28.0" @@ -1187,15 +1467,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-explicit-resource-management@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-explicit-resource-management@npm:7.28.6" +"@babel/plugin-transform-explicit-resource-management@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-explicit-resource-management@npm:7.29.7" dependencies: - "@babel/helper-plugin-utils": ^7.28.6 - "@babel/plugin-transform-destructuring": ^7.28.5 + "@babel/helper-plugin-utils": ^7.29.7 + "@babel/plugin-transform-destructuring": ^7.29.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: be65403694d360793b1b626ac0dfa7c120cfe4dd1c95a81a30b6e7426dc317643e60a486d642e318a4d3d9a7193e72fdb36e2ec140c25c773dcb9c3b1e2854ef + checksum: 28ff30f46d97b91ba5f57ae63499a489fa1ec3dcc427ec851c2aacb34106b573c58eba4be000bc4c28223ba767cfadc008cdfa9f833ed12996e2f014e0a06373 languageName: node linkType: hard @@ -1210,14 +1490,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-exponentiation-operator@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-exponentiation-operator@npm:7.28.6" +"@babel/plugin-transform-exponentiation-operator@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-exponentiation-operator@npm:7.29.7" dependencies: - "@babel/helper-plugin-utils": ^7.28.6 + "@babel/helper-plugin-utils": ^7.29.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: b232152499370435c7cd4bf3321f58e189150e35ca3722ea16533d33434b97294df1342f5499671ec48e62b71c34cdea0ca8cf317ad12594a10f6fc670315e62 + checksum: bf0366d77d318d4b6eae6880217e3fdfcb6e5f7913f658583de9537fd4fca1f05f9ac4083d8f946a84eaefec068cbba948be90f4a39484c85bc69082def3162d languageName: node linkType: hard @@ -1232,6 +1512,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-export-namespace-from@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-export-namespace-from@npm:7.29.7" + dependencies: + "@babel/helper-plugin-utils": ^7.29.7 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: d157d62b144d1626b801e557dcede914db33e78f3f4230f487e5709c21efd40648f7f3a47a44a7fce694ad9cd117d2ac3ed796da0102275fd02b4fdb317d906b + languageName: node + linkType: hard + "@babel/plugin-transform-flow-strip-types@npm:^7.25.2, @babel/plugin-transform-flow-strip-types@npm:^7.26.5": version: 7.27.1 resolution: "@babel/plugin-transform-flow-strip-types@npm:7.27.1" @@ -1256,6 +1547,18 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-for-of@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-for-of@npm:7.29.7" + dependencies: + "@babel/helper-plugin-utils": ^7.29.7 + "@babel/helper-skip-transparent-expression-wrappers": ^7.29.7 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 7641d5eb4ef268df2f7d8736691a565646f42c55fdc2c86a65ed027276415a19ac6cafab2fc386621894c5d3202baacc9f222424bda37fa2ab9a20d7fec921f9 + languageName: node + linkType: hard + "@babel/plugin-transform-function-name@npm:^7.25.1, @babel/plugin-transform-function-name@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-function-name@npm:7.27.1" @@ -1269,6 +1572,19 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-function-name@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-function-name@npm:7.29.7" + dependencies: + "@babel/helper-compilation-targets": ^7.29.7 + "@babel/helper-plugin-utils": ^7.29.7 + "@babel/traverse": ^7.29.7 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 87a6329442ef8085fbc160e659f64562f0f5b63be65403b8bbb8e18c67dd7d03b82fb2e1371fdde734e2f05b13a72f88ed8d8cb03807150063954b9604d082cf + languageName: node + linkType: hard + "@babel/plugin-transform-json-strings@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-json-strings@npm:7.27.1" @@ -1280,14 +1596,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-json-strings@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-json-strings@npm:7.28.6" +"@babel/plugin-transform-json-strings@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-json-strings@npm:7.29.7" dependencies: - "@babel/helper-plugin-utils": ^7.28.6 + "@babel/helper-plugin-utils": ^7.29.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 69d82a1a0a72ed6e6f7969e09cf330516599d79b2b4e680e9dd3c57616a8c6af049b5103456e370ab56642815e80e46ed88bb81e9e059304a85c5fe0bf137c29 + checksum: 3af97e144e0d986522f01803ffa0f90741530d1610952ac6a92511c356ecbf5616092ab1d21401d25bc7cb8ac90d73c2c7ff35e41766df2708c29d7e588cfa7f languageName: node linkType: hard @@ -1302,6 +1618,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-literals@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-literals@npm:7.29.7" + dependencies: + "@babel/helper-plugin-utils": ^7.29.7 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: aadb2b3fe85186c274a07d5486aeef9496ce374e534fbc7b54f77985c75513422d9acec4c532f67b027e939644d93a69c00505b8909e259184c3ee5c5c62c46b + languageName: node + linkType: hard + "@babel/plugin-transform-logical-assignment-operators@npm:^7.24.7, @babel/plugin-transform-logical-assignment-operators@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-logical-assignment-operators@npm:7.27.1" @@ -1313,14 +1640,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-logical-assignment-operators@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-logical-assignment-operators@npm:7.28.6" +"@babel/plugin-transform-logical-assignment-operators@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-logical-assignment-operators@npm:7.29.7" dependencies: - "@babel/helper-plugin-utils": ^7.28.6 + "@babel/helper-plugin-utils": ^7.29.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 36095d5d1cfc680e95298b5389a16016da800ae3379b130dabf557e94652c47b06610407e9fa44aaa03e9b0a5aa7b4b93348123985d44a45e369bf5f3497d149 + checksum: 374d83dfbb2de5e339d2966487c0f0d766cd67422addbd0172104ff2788b60317858172a7ab2cb6ee7d5bffad72ce07120fec009a2ef3b35551013fce4908686 languageName: node linkType: hard @@ -1335,6 +1662,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-member-expression-literals@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-member-expression-literals@npm:7.29.7" + dependencies: + "@babel/helper-plugin-utils": ^7.29.7 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 698cbc9500e8caea1d36b48248112d60e023cea9d0772ac1ecf1639b38b662d9352043747dbe617fe1f6f17709bc17601534f7bf2f22c791fb86f7e2ab43e39f + languageName: node + linkType: hard + "@babel/plugin-transform-modules-amd@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-modules-amd@npm:7.27.1" @@ -1347,6 +1685,18 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-modules-amd@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-modules-amd@npm:7.29.7" + dependencies: + "@babel/helper-module-transforms": ^7.29.7 + "@babel/helper-plugin-utils": ^7.29.7 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 6fce7d329230e3be0389b1135b6ffa1224c3f060294409137c5bb7f26d81ac47b6d91651deaef93b17f5e6f99e5ca7edb80b6b855b562b65ad00b3d7f717da40 + languageName: node + linkType: hard + "@babel/plugin-transform-modules-commonjs@npm:^7.24.8, @babel/plugin-transform-modules-commonjs@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-modules-commonjs@npm:7.27.1" @@ -1359,15 +1709,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-modules-commonjs@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-modules-commonjs@npm:7.28.6" +"@babel/plugin-transform-modules-commonjs@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-modules-commonjs@npm:7.29.7" dependencies: - "@babel/helper-module-transforms": ^7.28.6 - "@babel/helper-plugin-utils": ^7.28.6 + "@babel/helper-module-transforms": ^7.29.7 + "@babel/helper-plugin-utils": ^7.29.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: b48cab26fda72894c7002a9c783befbc8a643d827c52bdcc5adf83e418ca93224a15aaf7ed2d1e6284627be55913696cfa2119242686cfa77a473bf79314df26 + checksum: e9d8102deca4c2f06507a8d071ca0e161f01cec0e108151142edb39995bd830e312d55d21fac0ceb390ac79a1fee5fb768b719413b8fc5adda5f06ecd8845cd6 languageName: node linkType: hard @@ -1385,17 +1735,17 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-modules-systemjs@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/plugin-transform-modules-systemjs@npm:7.28.5" +"@babel/plugin-transform-modules-systemjs@npm:^7.29.7": + version: 7.29.8 + resolution: "@babel/plugin-transform-modules-systemjs@npm:7.29.8" dependencies: - "@babel/helper-module-transforms": ^7.28.3 - "@babel/helper-plugin-utils": ^7.27.1 - "@babel/helper-validator-identifier": ^7.28.5 - "@babel/traverse": ^7.28.5 + "@babel/helper-module-transforms": ^7.29.7 + "@babel/helper-plugin-utils": ^7.29.7 + "@babel/helper-validator-identifier": ^7.29.7 + "@babel/traverse": ^7.29.8 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 646748dcf968c107fedfbff38aa37f7a9ebf2ccdf51fd9f578c6cd323371db36bbc5fe0d995544db168f39be9bca32a85fbf3bfff4742d2bed22e21c2847fa46 + checksum: c0b1048a02c82d8aaa76ceb8b957ffe3e10a0d62896bb80efbdc10fd418636cf32769fc937cb75d69b8affd314d54f49d5da770658d182a280501d96bc97eaa4 languageName: node linkType: hard @@ -1411,6 +1761,18 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-modules-umd@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-modules-umd@npm:7.29.7" + dependencies: + "@babel/helper-module-transforms": ^7.29.7 + "@babel/helper-plugin-utils": ^7.29.7 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 589f5c30a906587d039e2f3bf8267652be272d93d3056667b479f450318c91e55d1674631239ac25e0e9591f4abb7ff225bb0fc8af58e5bfb703d3385ec02082 + languageName: node + linkType: hard + "@babel/plugin-transform-named-capturing-groups-regex@npm:^7.24.7, @babel/plugin-transform-named-capturing-groups-regex@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-named-capturing-groups-regex@npm:7.27.1" @@ -1423,6 +1785,18 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-named-capturing-groups-regex@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-named-capturing-groups-regex@npm:7.29.7" + dependencies: + "@babel/helper-create-regexp-features-plugin": ^7.29.7 + "@babel/helper-plugin-utils": ^7.29.7 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 3a481be133e9ca5d25570b5ed62daae323a51663bacf30fed0d1980e912047ebd34d6326533182db625fc0bbd086d1a23ed68ebb6108e7a68a8650c228afc084 + languageName: node + linkType: hard + "@babel/plugin-transform-new-target@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-new-target@npm:7.27.1" @@ -1434,6 +1808,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-new-target@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-new-target@npm:7.29.7" + dependencies: + "@babel/helper-plugin-utils": ^7.29.7 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 35b4c295348d17c9c00cf772663a6e705fd48f5a9a5378d7b548ab4cb71d5f183ca9446d2a86c77f16b037cfe64c621c00e93219aefab43bfd39d8dd2c8b56bb + languageName: node + linkType: hard + "@babel/plugin-transform-nullish-coalescing-operator@npm:^7.24.7, @babel/plugin-transform-nullish-coalescing-operator@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.27.1" @@ -1445,14 +1830,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.28.6" +"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.29.7" dependencies: - "@babel/helper-plugin-utils": ^7.28.6 + "@babel/helper-plugin-utils": ^7.29.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 1cdd3ca48a8fffa13dbb9949748d3dd2183cf24110cd55d702da4549205611fc12978b49886be809ec1929ff6304ac4eecc747a33dca2484f9dc655928ab5a89 + checksum: f486e737ddcec3a88e2e0dc004c28e15156dd255f3b2d944903f3d75666257c96ee7ebf57d80d2cf42eda2bee497db8134a26d657ddc3defcc34b9583ecbd119 languageName: node linkType: hard @@ -1467,14 +1852,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-numeric-separator@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-numeric-separator@npm:7.28.6" +"@babel/plugin-transform-numeric-separator@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-numeric-separator@npm:7.29.7" dependencies: - "@babel/helper-plugin-utils": ^7.28.6 + "@babel/helper-plugin-utils": ^7.29.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 4b5ca60e481e22f0842761a3badca17376a230b5a7e5482338604eb95836c2d0c9c9bde53bdc5c2de1c6a12ae6c12de7464d098bf74b0943f85905ca358f0b68 + checksum: 6177df9e1a8a190bf4a360f8cbcfa614b70ededba61201bd0a38929770b6d00a8a54a19f8fda82cf60688d9bab938427a9fe5dae0a9e8cd8ed79c88a3b2dbcd7 languageName: node linkType: hard @@ -1493,18 +1878,18 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-object-rest-spread@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-object-rest-spread@npm:7.28.6" +"@babel/plugin-transform-object-rest-spread@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-object-rest-spread@npm:7.29.7" dependencies: - "@babel/helper-compilation-targets": ^7.28.6 - "@babel/helper-plugin-utils": ^7.28.6 - "@babel/plugin-transform-destructuring": ^7.28.5 - "@babel/plugin-transform-parameters": ^7.27.7 - "@babel/traverse": ^7.28.6 + "@babel/helper-compilation-targets": ^7.29.7 + "@babel/helper-plugin-utils": ^7.29.7 + "@babel/plugin-transform-destructuring": ^7.29.7 + "@babel/plugin-transform-parameters": ^7.29.7 + "@babel/traverse": ^7.29.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: ab85b1321f86db91aba22ad9d8e6ab65448c983214998012229f5302468527d27b908ad6b14755991c317e35d2f54ec8459a2a094a755999651fe0ac9bd2e9a6 + checksum: e09bcc8800cf374962ab98bce5ccceb900266278a3a1e4a68391abec440fdf7371c8059f9091f407a1b167f3acf624c34c9b92188674fdcb7797e3f02509216b languageName: node linkType: hard @@ -1520,6 +1905,18 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-object-super@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-object-super@npm:7.29.7" + dependencies: + "@babel/helper-plugin-utils": ^7.29.7 + "@babel/helper-replace-supers": ^7.29.7 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 65ed8719563572c4917f19b32c476c9a9062fccf89bfd44a418bb734cd866034d66049499cace58e2bb4589da779983f534078bd989333f36268b9da08d4b33c + languageName: node + linkType: hard + "@babel/plugin-transform-optional-catch-binding@npm:^7.24.7, @babel/plugin-transform-optional-catch-binding@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-optional-catch-binding@npm:7.27.1" @@ -1531,14 +1928,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-optional-catch-binding@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-optional-catch-binding@npm:7.28.6" +"@babel/plugin-transform-optional-catch-binding@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-optional-catch-binding@npm:7.29.7" dependencies: - "@babel/helper-plugin-utils": ^7.28.6 + "@babel/helper-plugin-utils": ^7.29.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: ee24a17defec056eb9ef01824d7e4a1f65d531af6b4b79acfd0bcb95ce0b47926e80c61897f36f8c01ce733b069c9acdb1c9ce5ec07a729d0dbf9e8d859fe992 + checksum: 4b6e41e1dc5dbd02cfe0b96214130cca5fd3bd879551fc82188bb3d9a2782af9bab50f2140af9ff946a8ee23b9478ee42810641fb99aa3e033884d7c6103d138 languageName: node linkType: hard @@ -1554,15 +1951,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-optional-chaining@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-optional-chaining@npm:7.28.6" +"@babel/plugin-transform-optional-chaining@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-optional-chaining@npm:7.29.7" dependencies: - "@babel/helper-plugin-utils": ^7.28.6 - "@babel/helper-skip-transparent-expression-wrappers": ^7.27.1 + "@babel/helper-plugin-utils": ^7.29.7 + "@babel/helper-skip-transparent-expression-wrappers": ^7.29.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: a40dbe709671a436bb69e14524805e10af81b44c422e4fc5dc905cb91adb92d650c9d266c3c2c0da0d410dea89ce784995d4118b7ab6a7544f4923e61590b386 + checksum: 6255d259bb0143f7938278ebb382aba22311c260919d3f08476509c76335a111b479b3ad7363e86de064f87af85ca4d7f03f08195f0646c525ea70fb587c0a2d languageName: node linkType: hard @@ -1577,6 +1974,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-parameters@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-parameters@npm:7.29.7" + dependencies: + "@babel/helper-plugin-utils": ^7.29.7 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 615cdd72dc2d08051e6d4e7f0539661e40029257a047ce15c5da5f4394a372a53f6f6d6aece72ad15ccf0590e448c9a742f8576564321c9dcc16262ec6197c9b + languageName: node + linkType: hard + "@babel/plugin-transform-private-methods@npm:^7.24.7, @babel/plugin-transform-private-methods@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-private-methods@npm:7.27.1" @@ -1589,15 +1997,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-private-methods@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-private-methods@npm:7.28.6" +"@babel/plugin-transform-private-methods@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-private-methods@npm:7.29.7" dependencies: - "@babel/helper-create-class-features-plugin": ^7.28.6 - "@babel/helper-plugin-utils": ^7.28.6 + "@babel/helper-create-class-features-plugin": ^7.29.7 + "@babel/helper-plugin-utils": ^7.29.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: b80179b28f6a165674d0b0d6c6349b13a01dd282b18f56933423c0a33c23fc0626c8f011f859fc20737d021fe966eb8474a5233e4596401482e9ee7fb00e2aa2 + checksum: 5055af2b86a95acbf6bd1a14256edf439ba4f214707f6aa9f6a29d1168c882e5709853c4ff225f55575f88d3a58effbed952d25c5cd70f93785106541f992cdd languageName: node linkType: hard @@ -1614,16 +2022,16 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-private-property-in-object@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-private-property-in-object@npm:7.28.6" +"@babel/plugin-transform-private-property-in-object@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-private-property-in-object@npm:7.29.7" dependencies: - "@babel/helper-annotate-as-pure": ^7.27.3 - "@babel/helper-create-class-features-plugin": ^7.28.6 - "@babel/helper-plugin-utils": ^7.28.6 + "@babel/helper-annotate-as-pure": ^7.29.7 + "@babel/helper-create-class-features-plugin": ^7.29.7 + "@babel/helper-plugin-utils": ^7.29.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 32a935e44872e90607851be5bc2cd3365f29c0e0e3853ef3e2b6a7da4d08c647379bf2f2dc4f14a9064d7d72e2cf75da85e55baeeec1ffc25cf6088fe24422f7 + checksum: 068ff9e35e103b269c707d16f50384646295a6613c5abfdebea24f0430bb18ea4ef40a299e1dcc915bb22f65e2217c3428c20bcd4a3fe5f8ac60ec212835646d languageName: node linkType: hard @@ -1638,6 +2046,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-property-literals@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-property-literals@npm:7.29.7" + dependencies: + "@babel/helper-plugin-utils": ^7.29.7 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 7e7a557df8feb50cd6913158c6d12df0e8a9da58e3f73e7cbfc08af399055b03e77a22b12c73d5bf6bb49239617d6189ccb6021ab03f0225bc54f9c74a880b62 + languageName: node + linkType: hard + "@babel/plugin-transform-react-display-name@npm:^7.24.7, @babel/plugin-transform-react-display-name@npm:^7.27.1": version: 7.28.0 resolution: "@babel/plugin-transform-react-display-name@npm:7.28.0" @@ -1720,14 +2139,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-regenerator@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-regenerator@npm:7.28.6" +"@babel/plugin-transform-regenerator@npm:^7.29.7": + version: 7.29.8 + resolution: "@babel/plugin-transform-regenerator@npm:7.29.8" dependencies: - "@babel/helper-plugin-utils": ^7.28.6 + "@babel/helper-plugin-utils": ^7.29.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 1c1e3149a14e2cb695483f69f4ec18d1b820b23fe3b766a1e2efdbc2af0ed8acea6ea9438e8bc1496aab51e598a824428cec28431f1c1ea21d9599b46bf4aa24 + checksum: 18ce75d379e276764d1283002d0b7fffc02f487269ddc425c19d059f3f6e0a9f57ad4bdd20c1ddf99b9441f12357a1189c6927c1b19348e10aa5b7216edd9936 languageName: node linkType: hard @@ -1743,15 +2162,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-regexp-modifiers@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-regexp-modifiers@npm:7.28.6" +"@babel/plugin-transform-regexp-modifiers@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-regexp-modifiers@npm:7.29.7" dependencies: - "@babel/helper-create-regexp-features-plugin": ^7.28.5 - "@babel/helper-plugin-utils": ^7.28.6 + "@babel/helper-create-regexp-features-plugin": ^7.29.7 + "@babel/helper-plugin-utils": ^7.29.7 peerDependencies: "@babel/core": ^7.0.0 - checksum: 5aacc570034c085afa0165137bb9a04cd4299b86eb9092933a96dcc1132c8f591d9d534419988f5f762b2f70d43a3c719a6b8fa05fdd3b2b1820d01cf85500da + checksum: bfbc6b898ace99b8412eb6e902b90856188c692e69672d42d48c5e22a5bf9b0d15d35424fda8501bfb06ba0504acc5f1b9e13eacaba232aa58af74472d6068dc languageName: node linkType: hard @@ -1766,6 +2185,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-reserved-words@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-reserved-words@npm:7.29.7" + dependencies: + "@babel/helper-plugin-utils": ^7.29.7 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: ffd7f86ddce36c6ded2dd9218f81be8cbae35b1ed01100ac0a98623bd0a76c059188b70953ab5accae8f8430451e8ed4779d533ac5e35c523f5004a981208b7c + languageName: node + linkType: hard + "@babel/plugin-transform-runtime@npm:^7.24.7": version: 7.28.3 resolution: "@babel/plugin-transform-runtime@npm:7.28.3" @@ -1793,6 +2223,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-shorthand-properties@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-shorthand-properties@npm:7.29.7" + dependencies: + "@babel/helper-plugin-utils": ^7.29.7 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: c57ef27853f334a6147da9aa00f8a8f4c3a1c217eb2efa73cba2e118edda10754fa23cec2c0c7f7408279ad28fef92c1f663dfec137a7503813331569c3e02f9 + languageName: node + linkType: hard + "@babel/plugin-transform-spread@npm:^7.24.7, @babel/plugin-transform-spread@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-spread@npm:7.27.1" @@ -1805,15 +2246,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-spread@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-spread@npm:7.28.6" +"@babel/plugin-transform-spread@npm:^7.29.7": + version: 7.29.8 + resolution: "@babel/plugin-transform-spread@npm:7.29.8" dependencies: - "@babel/helper-plugin-utils": ^7.28.6 - "@babel/helper-skip-transparent-expression-wrappers": ^7.27.1 + "@babel/helper-plugin-utils": ^7.29.7 + "@babel/helper-skip-transparent-expression-wrappers": ^7.29.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: e4782578904df68f7d2b3e865f20701c71d6aba0027c4794c1dc08a2f805a12892a078dab483714552398a689ad4ff6786cdf4e088b073452aee7db67e37a09c + checksum: 7f5951150b5c92b1e5e244e270bf680bcfaaaf4651c89c6658da489ab188c9b71273ebd4478659ad5175342e513f7197eba8a35241c82a5162f5897b6d35f881 languageName: node linkType: hard @@ -1828,6 +2269,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-sticky-regex@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-sticky-regex@npm:7.29.7" + dependencies: + "@babel/helper-plugin-utils": ^7.29.7 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 16b570c0270a59c2a29b2118c00e463882e9dda49b51a17dde3ae6b2886995d49f4bf2161a4c743ad1bca39d2aeb3ac963400e095682e105c7f751e6a2156c28 + languageName: node + linkType: hard + "@babel/plugin-transform-strict-mode@npm:^7.24.7": version: 7.27.1 resolution: "@babel/plugin-transform-strict-mode@npm:7.27.1" @@ -1850,6 +2302,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-template-literals@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-template-literals@npm:7.29.7" + dependencies: + "@babel/helper-plugin-utils": ^7.29.7 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: d1014dab020f0f802089de17bba82d929eda6ac87fde5f58fb9763885b8d645ce63fc1df97055c06a12d95a8788334a93b559fcaf1da6d7777a191e5f9c5646e + languageName: node + linkType: hard + "@babel/plugin-transform-typeof-symbol@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-typeof-symbol@npm:7.27.1" @@ -1861,6 +2324,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-typeof-symbol@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-typeof-symbol@npm:7.29.7" + dependencies: + "@babel/helper-plugin-utils": ^7.29.7 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 90c2308f97c4e3773b7d2010b962ce6122bd9e1163f0caf7bb45819342f547a5a41d36c73d9a828ad89ce2072e2613ea867d28ad59eb440d9e22196438437d02 + languageName: node + linkType: hard + "@babel/plugin-transform-typescript@npm:^7.25.2, @babel/plugin-transform-typescript@npm:^7.27.1": version: 7.28.0 resolution: "@babel/plugin-transform-typescript@npm:7.28.0" @@ -1887,6 +2361,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-unicode-escapes@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-unicode-escapes@npm:7.29.7" + dependencies: + "@babel/helper-plugin-utils": ^7.29.7 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: cf337bf93a2e76aa82acb60d8a8a567225744cf7f42ffa4dde4c177aef54250f4067db38b8ab2c26f20cf2a589822242891ba8b91739a705f618bf6a4eeca7ff + languageName: node + linkType: hard + "@babel/plugin-transform-unicode-property-regex@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-unicode-property-regex@npm:7.27.1" @@ -1899,15 +2384,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-unicode-property-regex@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-unicode-property-regex@npm:7.28.6" +"@babel/plugin-transform-unicode-property-regex@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-unicode-property-regex@npm:7.29.7" dependencies: - "@babel/helper-create-regexp-features-plugin": ^7.28.5 - "@babel/helper-plugin-utils": ^7.28.6 + "@babel/helper-create-regexp-features-plugin": ^7.29.7 + "@babel/helper-plugin-utils": ^7.29.7 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: d14e8c51aa73f592575c1543400fd67d96df6410d75c9dc10dd640fd7eecb37366a2f2368bbdd7529842532eda4af181c921bda95146c6d373c64ea59c6e9991 + checksum: 03ee0b5d27eee08ba71bc09e919eb648094123985b7adc7dc875e4172100596a47ab68337abf6814cbd3140c6552cf1044135eb0ec1ec78345e1270e5e6aabba languageName: node linkType: hard @@ -1923,6 +2408,18 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-unicode-regex@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-unicode-regex@npm:7.29.7" + dependencies: + "@babel/helper-create-regexp-features-plugin": ^7.29.7 + "@babel/helper-plugin-utils": ^7.29.7 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 1ade0672ae5bbbf2ec1ea0a8de1b5d804ae414283215620097ab21cf7f05dae8916f5b0548a18c6f080ec17135018f5edd2d38f8fa9ca052af570cab5c712786 + languageName: node + linkType: hard + "@babel/plugin-transform-unicode-sets-regex@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-unicode-sets-regex@npm:7.27.1" @@ -1935,15 +2432,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-unicode-sets-regex@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/plugin-transform-unicode-sets-regex@npm:7.28.6" +"@babel/plugin-transform-unicode-sets-regex@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/plugin-transform-unicode-sets-regex@npm:7.29.7" dependencies: - "@babel/helper-create-regexp-features-plugin": ^7.28.5 - "@babel/helper-plugin-utils": ^7.28.6 + "@babel/helper-create-regexp-features-plugin": ^7.29.7 + "@babel/helper-plugin-utils": ^7.29.7 peerDependencies: "@babel/core": ^7.0.0 - checksum: 423971fe2eef9d18782b1c30f5f42613ee510e5b9c08760c5538a0997b36c34495acce261e0e37a27831f81330359230bd1f33c2e1822de70241002b45b7d68e + checksum: 680c22e2a63bffbf6798b0c2f599b06beb7ea4d29ee37a56c9192014143d396c479b12783d9c343e107fa491aeeb65b1ca774fd76825ffb306eef7fb5e7b4b2c languageName: node linkType: hard @@ -2027,83 +2524,84 @@ __metadata: languageName: node linkType: hard -"@babel/preset-env@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/preset-env@npm:7.28.6" +"@babel/preset-env@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/preset-env@npm:7.29.7" dependencies: - "@babel/compat-data": ^7.28.6 - "@babel/helper-compilation-targets": ^7.28.6 - "@babel/helper-plugin-utils": ^7.28.6 - "@babel/helper-validator-option": ^7.27.1 - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": ^7.28.5 - "@babel/plugin-bugfix-safari-class-field-initializer-scope": ^7.27.1 - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": ^7.27.1 - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": ^7.27.1 - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": ^7.28.6 + "@babel/compat-data": ^7.29.7 + "@babel/helper-compilation-targets": ^7.29.7 + "@babel/helper-plugin-utils": ^7.29.7 + "@babel/helper-validator-option": ^7.29.7 + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": ^7.29.7 + "@babel/plugin-bugfix-safari-class-field-initializer-scope": ^7.29.7 + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": ^7.29.7 + "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": ^7.29.7 + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": ^7.29.7 + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": ^7.29.7 "@babel/plugin-proposal-private-property-in-object": 7.21.0-placeholder-for-preset-env.2 - "@babel/plugin-syntax-import-assertions": ^7.28.6 - "@babel/plugin-syntax-import-attributes": ^7.28.6 + "@babel/plugin-syntax-import-assertions": ^7.29.7 + "@babel/plugin-syntax-import-attributes": ^7.29.7 "@babel/plugin-syntax-unicode-sets-regex": ^7.18.6 - "@babel/plugin-transform-arrow-functions": ^7.27.1 - "@babel/plugin-transform-async-generator-functions": ^7.28.6 - "@babel/plugin-transform-async-to-generator": ^7.28.6 - "@babel/plugin-transform-block-scoped-functions": ^7.27.1 - "@babel/plugin-transform-block-scoping": ^7.28.6 - "@babel/plugin-transform-class-properties": ^7.28.6 - "@babel/plugin-transform-class-static-block": ^7.28.6 - "@babel/plugin-transform-classes": ^7.28.6 - "@babel/plugin-transform-computed-properties": ^7.28.6 - "@babel/plugin-transform-destructuring": ^7.28.5 - "@babel/plugin-transform-dotall-regex": ^7.28.6 - "@babel/plugin-transform-duplicate-keys": ^7.27.1 - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": ^7.28.6 - "@babel/plugin-transform-dynamic-import": ^7.27.1 - "@babel/plugin-transform-explicit-resource-management": ^7.28.6 - "@babel/plugin-transform-exponentiation-operator": ^7.28.6 - "@babel/plugin-transform-export-namespace-from": ^7.27.1 - "@babel/plugin-transform-for-of": ^7.27.1 - "@babel/plugin-transform-function-name": ^7.27.1 - "@babel/plugin-transform-json-strings": ^7.28.6 - "@babel/plugin-transform-literals": ^7.27.1 - "@babel/plugin-transform-logical-assignment-operators": ^7.28.6 - "@babel/plugin-transform-member-expression-literals": ^7.27.1 - "@babel/plugin-transform-modules-amd": ^7.27.1 - "@babel/plugin-transform-modules-commonjs": ^7.28.6 - "@babel/plugin-transform-modules-systemjs": ^7.28.5 - "@babel/plugin-transform-modules-umd": ^7.27.1 - "@babel/plugin-transform-named-capturing-groups-regex": ^7.27.1 - "@babel/plugin-transform-new-target": ^7.27.1 - "@babel/plugin-transform-nullish-coalescing-operator": ^7.28.6 - "@babel/plugin-transform-numeric-separator": ^7.28.6 - "@babel/plugin-transform-object-rest-spread": ^7.28.6 - "@babel/plugin-transform-object-super": ^7.27.1 - "@babel/plugin-transform-optional-catch-binding": ^7.28.6 - "@babel/plugin-transform-optional-chaining": ^7.28.6 - "@babel/plugin-transform-parameters": ^7.27.7 - "@babel/plugin-transform-private-methods": ^7.28.6 - "@babel/plugin-transform-private-property-in-object": ^7.28.6 - "@babel/plugin-transform-property-literals": ^7.27.1 - "@babel/plugin-transform-regenerator": ^7.28.6 - "@babel/plugin-transform-regexp-modifiers": ^7.28.6 - "@babel/plugin-transform-reserved-words": ^7.27.1 - "@babel/plugin-transform-shorthand-properties": ^7.27.1 - "@babel/plugin-transform-spread": ^7.28.6 - "@babel/plugin-transform-sticky-regex": ^7.27.1 - "@babel/plugin-transform-template-literals": ^7.27.1 - "@babel/plugin-transform-typeof-symbol": ^7.27.1 - "@babel/plugin-transform-unicode-escapes": ^7.27.1 - "@babel/plugin-transform-unicode-property-regex": ^7.28.6 - "@babel/plugin-transform-unicode-regex": ^7.27.1 - "@babel/plugin-transform-unicode-sets-regex": ^7.28.6 + "@babel/plugin-transform-arrow-functions": ^7.29.7 + "@babel/plugin-transform-async-generator-functions": ^7.29.7 + "@babel/plugin-transform-async-to-generator": ^7.29.7 + "@babel/plugin-transform-block-scoped-functions": ^7.29.7 + "@babel/plugin-transform-block-scoping": ^7.29.7 + "@babel/plugin-transform-class-properties": ^7.29.7 + "@babel/plugin-transform-class-static-block": ^7.29.7 + "@babel/plugin-transform-classes": ^7.29.7 + "@babel/plugin-transform-computed-properties": ^7.29.7 + "@babel/plugin-transform-destructuring": ^7.29.7 + "@babel/plugin-transform-dotall-regex": ^7.29.7 + "@babel/plugin-transform-duplicate-keys": ^7.29.7 + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": ^7.29.7 + "@babel/plugin-transform-dynamic-import": ^7.29.7 + "@babel/plugin-transform-explicit-resource-management": ^7.29.7 + "@babel/plugin-transform-exponentiation-operator": ^7.29.7 + "@babel/plugin-transform-export-namespace-from": ^7.29.7 + "@babel/plugin-transform-for-of": ^7.29.7 + "@babel/plugin-transform-function-name": ^7.29.7 + "@babel/plugin-transform-json-strings": ^7.29.7 + "@babel/plugin-transform-literals": ^7.29.7 + "@babel/plugin-transform-logical-assignment-operators": ^7.29.7 + "@babel/plugin-transform-member-expression-literals": ^7.29.7 + "@babel/plugin-transform-modules-amd": ^7.29.7 + "@babel/plugin-transform-modules-commonjs": ^7.29.7 + "@babel/plugin-transform-modules-systemjs": ^7.29.7 + "@babel/plugin-transform-modules-umd": ^7.29.7 + "@babel/plugin-transform-named-capturing-groups-regex": ^7.29.7 + "@babel/plugin-transform-new-target": ^7.29.7 + "@babel/plugin-transform-nullish-coalescing-operator": ^7.29.7 + "@babel/plugin-transform-numeric-separator": ^7.29.7 + "@babel/plugin-transform-object-rest-spread": ^7.29.7 + "@babel/plugin-transform-object-super": ^7.29.7 + "@babel/plugin-transform-optional-catch-binding": ^7.29.7 + "@babel/plugin-transform-optional-chaining": ^7.29.7 + "@babel/plugin-transform-parameters": ^7.29.7 + "@babel/plugin-transform-private-methods": ^7.29.7 + "@babel/plugin-transform-private-property-in-object": ^7.29.7 + "@babel/plugin-transform-property-literals": ^7.29.7 + "@babel/plugin-transform-regenerator": ^7.29.7 + "@babel/plugin-transform-regexp-modifiers": ^7.29.7 + "@babel/plugin-transform-reserved-words": ^7.29.7 + "@babel/plugin-transform-shorthand-properties": ^7.29.7 + "@babel/plugin-transform-spread": ^7.29.7 + "@babel/plugin-transform-sticky-regex": ^7.29.7 + "@babel/plugin-transform-template-literals": ^7.29.7 + "@babel/plugin-transform-typeof-symbol": ^7.29.7 + "@babel/plugin-transform-unicode-escapes": ^7.29.7 + "@babel/plugin-transform-unicode-property-regex": ^7.29.7 + "@babel/plugin-transform-unicode-regex": ^7.29.7 + "@babel/plugin-transform-unicode-sets-regex": ^7.29.7 "@babel/preset-modules": 0.1.6-no-external-plugins - babel-plugin-polyfill-corejs2: ^0.4.14 - babel-plugin-polyfill-corejs3: ^0.13.0 - babel-plugin-polyfill-regenerator: ^0.6.5 - core-js-compat: ^3.43.0 + babel-plugin-polyfill-corejs2: ^0.4.15 + babel-plugin-polyfill-corejs3: ^0.14.0 + babel-plugin-polyfill-regenerator: ^0.6.6 + core-js-compat: ^3.48.0 semver: ^6.3.1 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 097576541fd5db64acf1c6ae12c6d6eb12e75b099c8728898c99724a655c930071f00757734f011baac8d0681bf8096e22c6cbf4fd7b06fc662e5fbb8619fb4b + checksum: 36deae2ea4329e8490fc4507e6747e07c4cff9df1f1397aa99fae16e03a5195f9c7a3494f571ddfb5002bbc0e18832a8cc711831d686896312fa127823f7fedc languageName: node linkType: hard @@ -2158,10 +2656,10 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.28.6": - version: 7.28.6 - resolution: "@babel/runtime@npm:7.28.6" - checksum: 42d8a868c2fc2e9a77927945a6daa7ec03c7ea49e611e0d15442933cdabb12f20e3a6849c729259076c10a4247adec229331d1f94c2d0073ea0979d7853e29fd +"@babel/runtime@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/runtime@npm:7.29.7" + checksum: bc311855c6dbf5356030979584ca0f8b6cee39fe058175b02e85a73e246fc76ae30248b0d40e70c2652101faeb43279468ed2d086a670673eb9783c1fee1df2b languageName: node linkType: hard @@ -2187,7 +2685,18 @@ __metadata: languageName: node linkType: hard -"@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3, @babel/traverse@npm:^7.25.3, @babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.0, @babel/traverse@npm:^7.28.3": +"@babel/template@npm:^7.29.7": + version: 7.29.7 + resolution: "@babel/template@npm:7.29.7" + dependencies: + "@babel/code-frame": ^7.29.7 + "@babel/parser": ^7.29.7 + "@babel/types": ^7.29.7 + checksum: 521eb6a1fd4735074ca8dac0d70810860a80edf3bf78105851571993cd13701a2041987e7398ccc9376eb6235ea1258bf494ccaccf3b67fa98dbe954154a2e93 + languageName: node + linkType: hard + +"@babel/traverse@npm:^7.25.3, @babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.0, @babel/traverse@npm:^7.28.3": version: 7.28.3 resolution: "@babel/traverse@npm:7.28.3" dependencies: @@ -2202,21 +2711,6 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/traverse@npm:7.28.5" - dependencies: - "@babel/code-frame": ^7.27.1 - "@babel/generator": ^7.28.5 - "@babel/helper-globals": ^7.28.0 - "@babel/parser": ^7.28.5 - "@babel/template": ^7.27.2 - "@babel/types": ^7.28.5 - debug: ^4.3.1 - checksum: e028ee9654f44be7c2a2df268455cee72d5c424c9ae536785f8f7c8680356f7b977c77ad76909d07eeed09ff1e125ce01cf783011f66b56c838791a85fa6af04 - languageName: node - linkType: hard - "@babel/traverse@npm:^7.28.6": version: 7.28.6 resolution: "@babel/traverse@npm:7.28.6" @@ -2232,23 +2726,28 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.25.2, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.2, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": - version: 7.28.2 - resolution: "@babel/types@npm:7.28.2" +"@babel/traverse@npm:^7.29.0, @babel/traverse@npm:^7.29.7, @babel/traverse@npm:^7.29.8": + version: 7.29.8 + resolution: "@babel/traverse@npm:7.29.8" dependencies: - "@babel/helper-string-parser": ^7.27.1 - "@babel/helper-validator-identifier": ^7.27.1 - checksum: 2218f0996d5fbadc4e3428c4c38f4ed403f0e2634e3089beba2c89783268c0c1d796a23e65f9f1ff8547b9061ae1a67691c76dc27d0b457e5fa9f2dd4e022e49 + "@babel/code-frame": ^7.29.7 + "@babel/generator": ^7.29.8 + "@babel/helper-globals": ^7.29.7 + "@babel/parser": ^7.29.8 + "@babel/template": ^7.29.7 + "@babel/types": ^7.29.8 + debug: ^4.3.1 + checksum: 7c33eb59db5c67411305ed1d29fe50dbde74b220f4500840951d776fbcc3fbddca09f57302e8961d2533871ef4719ec0f2f05969870263b180d6ff4217f7acee languageName: node linkType: hard -"@babel/types@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/types@npm:7.28.5" +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.2, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": + version: 7.28.2 + resolution: "@babel/types@npm:7.28.2" dependencies: "@babel/helper-string-parser": ^7.27.1 - "@babel/helper-validator-identifier": ^7.28.5 - checksum: 5bc266af9e55ff92f9ddf33d83a42c9de1a87f9579d0ed62ef94a741a081692dd410a4fbbab18d514b83e135083ff05bc0e37003834801c9514b9d8ad748070d + "@babel/helper-validator-identifier": ^7.27.1 + checksum: 2218f0996d5fbadc4e3428c4c38f4ed403f0e2634e3089beba2c89783268c0c1d796a23e65f9f1ff8547b9061ae1a67691c76dc27d0b457e5fa9f2dd4e022e49 languageName: node linkType: hard @@ -2262,6 +2761,16 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.29.0, @babel/types@npm:^7.29.7, @babel/types@npm:^7.29.8": + version: 7.29.8 + resolution: "@babel/types@npm:7.29.8" + dependencies: + "@babel/helper-string-parser": ^7.29.7 + "@babel/helper-validator-identifier": ^7.29.7 + checksum: 9ac45d1fc702bce8e51f74566c85211f4a5d06b6f921c297e07f05742ac0eb9660d4380f96ac18d882415e4e1172e92a3fc7a20b29afd2277b713efc5b3c7cf6 + languageName: node + linkType: hard + "@bcoe/v8-coverage@npm:^0.2.3": version: 0.2.3 resolution: "@bcoe/v8-coverage@npm:0.2.3" @@ -2286,13 +2795,13 @@ __metadata: languageName: node linkType: hard -"@commitlint/config-conventional@npm:^20.4.0": - version: 20.4.0 - resolution: "@commitlint/config-conventional@npm:20.4.0" +"@commitlint/config-conventional@npm:^20.5.3": + version: 20.5.3 + resolution: "@commitlint/config-conventional@npm:20.5.3" dependencies: - "@commitlint/types": ^20.4.0 - conventional-changelog-conventionalcommits: ^9.1.0 - checksum: 8d8fb2ac205ec50c8979978e05fdc96250afa7850f50e44264b7b8dbf57384eb148000c7f276bf60f993f010fbdcb036f7e0dc1ce7fb0fc60b2f2293889c3f12 + "@commitlint/types": ^20.5.0 + conventional-changelog-conventionalcommits: ^9.2.0 + checksum: ed0883aaf4eba950885b3cc50bf7ab24e13579611fda39766415b279aebb6be5f62f79d19d7f66fceb65dbd9cd2ebb158443233f5586bd249b75e025ff89612d languageName: node linkType: hard @@ -2460,13 +2969,13 @@ __metadata: languageName: node linkType: hard -"@commitlint/types@npm:^20.4.0": - version: 20.4.0 - resolution: "@commitlint/types@npm:20.4.0" +"@commitlint/types@npm:^20.5.0": + version: 20.5.0 + resolution: "@commitlint/types@npm:20.5.0" dependencies: - conventional-commits-parser: ^6.2.1 + conventional-commits-parser: ^6.3.0 picocolors: ^1.1.1 - checksum: aff0e6d33eb3191fad3c20d7d46e282ae3dacca52a24f372ac880f0231c859a26726d25565246ea34b67aa9ac1db55f12ef3d635bd6fe9e68cb34fa0a7c5fe47 + checksum: 4ba62acd297549991fb3d126d2f97039d07ce52d5d96d41347ebebec9ac14223aac78dd23e8ac74d388fa042c7b0bc026c43662978b2a60dfdd11158fd7907ef languageName: node linkType: hard @@ -2489,6 +2998,25 @@ __metadata: languageName: node linkType: hard +"@conventional-changelog/git-client@npm:^2.6.0, @conventional-changelog/git-client@npm:^2.7.0": + version: 2.7.0 + resolution: "@conventional-changelog/git-client@npm:2.7.0" + dependencies: + "@simple-libs/child-process-utils": ^1.0.0 + "@simple-libs/stream-utils": ^1.2.0 + semver: ^7.5.2 + peerDependencies: + conventional-commits-filter: ^5.0.0 + conventional-commits-parser: ^6.4.0 + peerDependenciesMeta: + conventional-commits-filter: + optional: true + conventional-commits-parser: + optional: true + checksum: 557ddc544549286d6a62db660d487eff55b973246b63be188f08b5fe48b08b673a8d50724f59f9012384aa7d7dbaa2c10044052a8253398e70db7e71ff3961d4 + languageName: node + linkType: hard + "@eslint-community/eslint-utils@npm:^4.7.0, @eslint-community/eslint-utils@npm:^4.8.0": version: 4.9.0 resolution: "@eslint-community/eslint-utils@npm:4.9.0" @@ -2521,14 +3049,14 @@ __metadata: languageName: node linkType: hard -"@eslint/config-array@npm:^0.21.1": - version: 0.21.1 - resolution: "@eslint/config-array@npm:0.21.1" +"@eslint/config-array@npm:^0.21.2": + version: 0.21.2 + resolution: "@eslint/config-array@npm:0.21.2" dependencies: "@eslint/object-schema": ^2.1.7 debug: ^4.3.1 - minimatch: ^3.1.2 - checksum: fc5b57803b059f7c1f62950ef83baf045a01887fc00551f9e87ac119246fcc6d71c854a7f678accc79cbf829ed010e8135c755a154b0f54b129c538950cd7e6a + minimatch: ^3.1.5 + checksum: f3d6ba56d6a3dfc5400575011fb4ae5ac189c96b6ca4920adb6da2d084f9eaa28583fa0aa55e123c42baa2bd31f85228ee35a05c8a395b58fb8d976e16482697 languageName: node linkType: hard @@ -2550,44 +3078,27 @@ __metadata: languageName: node linkType: hard -"@eslint/eslintrc@npm:^3.3.1": - version: 3.3.1 - resolution: "@eslint/eslintrc@npm:3.3.1" - dependencies: - ajv: ^6.12.4 - debug: ^4.3.2 - espree: ^10.0.1 - globals: ^14.0.0 - ignore: ^5.2.0 - import-fresh: ^3.2.1 - js-yaml: ^4.1.0 - minimatch: ^3.1.2 - strip-json-comments: ^3.1.1 - checksum: 8241f998f0857abf5a615072273b90b1244d75c1c45d217c6a8eb444c6e12bbb5506b4879c14fb262eb72b7d8e3d2f0542da2db1a7f414a12496ebb790fb4d62 - languageName: node - linkType: hard - -"@eslint/eslintrc@npm:^3.3.3": - version: 3.3.3 - resolution: "@eslint/eslintrc@npm:3.3.3" +"@eslint/eslintrc@npm:^3.3.6, @eslint/eslintrc@npm:^3.3.7": + version: 3.3.7 + resolution: "@eslint/eslintrc@npm:3.3.7" dependencies: - ajv: ^6.12.4 + ajv: ^6.14.0 debug: ^4.3.2 espree: ^10.0.1 globals: ^14.0.0 ignore: ^5.2.0 import-fresh: ^3.2.1 - js-yaml: ^4.1.1 - minimatch: ^3.1.2 + js-yaml: ^4.3.2 + minimatch: ^3.1.5 strip-json-comments: ^3.1.1 - checksum: d1e16e47f1bb29af32defa597eaf84ac0ff8c06760c0a5f4933c604cd9d931d48c89bed96252222f22abac231898a53bc41385a5e6129257f0060b5ec431bdb2 + checksum: 3529073f3f78242b70d9434dd71680391a21cf0c514cd334d32b04ecd787c4f0e65d4ca9be2529166c76242d99504cc84289310d8b1e2e02cdfc34e7e1580b20 languageName: node linkType: hard -"@eslint/js@npm:9.39.2, @eslint/js@npm:^9.39.2": - version: 9.39.2 - resolution: "@eslint/js@npm:9.39.2" - checksum: 362aa447266fa5717e762b2b252f177345cb0d7b2954113db9773b3a28898f7cbbc807e07f8078995e6da3f62791f7c5fa2c03517b7170a8e76613cf7fd83c92 +"@eslint/js@npm:9.39.5, @eslint/js@npm:^9.39.5": + version: 9.39.5 + resolution: "@eslint/js@npm:9.39.5" + checksum: fb012ffa4be0e1eb3fea59e4df6394a2abc054c800554596a682e7086aec4dc3c39801b3a4c2f1aaeba097ab09c17d2f02bcdd47fdcd0120dc94af6e5c3b00c4 languageName: node linkType: hard @@ -3261,6 +3772,13 @@ __metadata: languageName: node linkType: hard +"@nodable/entities@npm:^3.0.0": + version: 3.0.0 + resolution: "@nodable/entities@npm:3.0.0" + checksum: ac37bc86140f8e0074a4595598b553aecd607889abdf996f24cdb37e220973c8d233a56af79fe1b6b35319512ca986c442018eb22e7a6c3c6a51352d77718b92 + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -3457,10 +3975,10 @@ __metadata: languageName: node linkType: hard -"@pkgr/core@npm:^0.2.9": - version: 0.2.9 - resolution: "@pkgr/core@npm:0.2.9" - checksum: bb2fb86977d63f836f8f5b09015d74e6af6488f7a411dcd2bfdca79d76b5a681a9112f41c45bdf88a9069f049718efc6f3900d7f1de66a2ec966068308ae517f +"@pkgr/core@npm:^0.3.6": + version: 0.3.6 + resolution: "@pkgr/core@npm:0.3.6" + checksum: 29082aa13d36f13fc41cdc64cb1feb36b630de0d6ebde84e2ff68e3d7a7f1dce4462cca91f76176c46e50bbca6b1e7f1fd9cf907af12d5d70da83bc981ca4ccf languageName: node linkType: hard @@ -3475,65 +3993,65 @@ __metadata: languageName: node linkType: hard -"@react-native-community/cli-clean@npm:20.1.1": - version: 20.1.1 - resolution: "@react-native-community/cli-clean@npm:20.1.1" +"@react-native-community/cli-clean@npm:20.2.0": + version: 20.2.0 + resolution: "@react-native-community/cli-clean@npm:20.2.0" dependencies: - "@react-native-community/cli-tools": 20.1.1 + "@react-native-community/cli-tools": 20.2.0 execa: ^5.0.0 fast-glob: ^3.3.2 picocolors: ^1.1.1 - checksum: f082a56e04fd0b626e2d863831acd58f47866f6a3d235e45059fb8697a75c0484d06ba4bb3116198fc1e773cf19fc30526148ad45c3d0c771eb9f66346957405 + checksum: fb460bb18ab257ab8e5f80e3a42448459342f615dfb975bbdac030ed79ecf0ac1c84b130ed0f86cae7165a3ea9f13dd0e811d19f255f63d481655f0913c097bb languageName: node linkType: hard -"@react-native-community/cli-config-android@npm:20.1.1": - version: 20.1.1 - resolution: "@react-native-community/cli-config-android@npm:20.1.1" +"@react-native-community/cli-config-android@npm:20.2.0": + version: 20.2.0 + resolution: "@react-native-community/cli-config-android@npm:20.2.0" dependencies: - "@react-native-community/cli-tools": 20.1.1 + "@react-native-community/cli-tools": 20.2.0 fast-glob: ^3.3.2 - fast-xml-parser: ^4.4.1 + fast-xml-parser: ^5.3.6 picocolors: ^1.1.1 - checksum: bf94b3e833eaf64c661503f836748160f4d763a5fb0c931bba74fdc836335ba210b030e556bea8cf15f1880ca9c483f1834fdfb04edb2e63036c5211c3db2627 + checksum: f85f9b6481e3a2c8c5819e99afe162779decd3199a650559cf8fb0ec1fd3f3b95172021aaf066e6ec497c5807d86ea14c494c93df63442151aafe7d01275a3a7 languageName: node linkType: hard -"@react-native-community/cli-config-apple@npm:20.1.1": - version: 20.1.1 - resolution: "@react-native-community/cli-config-apple@npm:20.1.1" +"@react-native-community/cli-config-apple@npm:20.2.0": + version: 20.2.0 + resolution: "@react-native-community/cli-config-apple@npm:20.2.0" dependencies: - "@react-native-community/cli-tools": 20.1.1 + "@react-native-community/cli-tools": 20.2.0 execa: ^5.0.0 fast-glob: ^3.3.2 picocolors: ^1.1.1 - checksum: a01736ce164e6f08404b1d23732a82f4f38148682ef86268095680719aaa7d08eedfa7ff606aa7a095bf0762e36978e24192e4f9881016a56b162711195bbe4b + checksum: f610f36a9847c10160a9e6575c38b4e38c8cf548091ceb56eb4c211a85db0efbb31c983a32a4fc0cbdcb108ea6f997038df6d08035534814ae51f0ef2de0fd7b languageName: node linkType: hard -"@react-native-community/cli-config@npm:20.1.1": - version: 20.1.1 - resolution: "@react-native-community/cli-config@npm:20.1.1" +"@react-native-community/cli-config@npm:20.2.0": + version: 20.2.0 + resolution: "@react-native-community/cli-config@npm:20.2.0" dependencies: - "@react-native-community/cli-tools": 20.1.1 + "@react-native-community/cli-tools": 20.2.0 cosmiconfig: ^9.0.0 deepmerge: ^4.3.0 fast-glob: ^3.3.2 joi: ^17.2.1 picocolors: ^1.1.1 - checksum: 3ff7b882e4ec05886e98ec95048bdfce34bf5f4db36e822502001a84d2388976d11f6b23f4aaa859ea1f5c8a025385ece62f23443ff5de46de74d3bd65f31ce9 + checksum: d0acbbc63aec3abf0dd022aa05c64e84a0629d594c283c8ee976341a54b953efa82980a828842471d2a317cef95b32fa171e1294a5bc5039eecf82cec2a0f872 languageName: node linkType: hard -"@react-native-community/cli-doctor@npm:20.1.1": - version: 20.1.1 - resolution: "@react-native-community/cli-doctor@npm:20.1.1" +"@react-native-community/cli-doctor@npm:20.2.0": + version: 20.2.0 + resolution: "@react-native-community/cli-doctor@npm:20.2.0" dependencies: - "@react-native-community/cli-config": 20.1.1 - "@react-native-community/cli-platform-android": 20.1.1 - "@react-native-community/cli-platform-apple": 20.1.1 - "@react-native-community/cli-platform-ios": 20.1.1 - "@react-native-community/cli-tools": 20.1.1 + "@react-native-community/cli-config": 20.2.0 + "@react-native-community/cli-platform-android": 20.2.0 + "@react-native-community/cli-platform-apple": 20.2.0 + "@react-native-community/cli-platform-ios": 20.2.0 + "@react-native-community/cli-tools": 20.2.0 command-exists: ^1.2.8 deepmerge: ^4.3.0 envinfo: ^7.13.0 @@ -3544,51 +4062,51 @@ __metadata: semver: ^7.5.2 wcwidth: ^1.0.1 yaml: ^2.2.1 - checksum: 87f45d4afc5ad838a34d06c6762fafbb4d6af3dea3bbcaeabcef01c7aafaf7b1a2cdb99e6733cdd446e9340f52931b9c6a89ed9c135588646fc13d88fc8fa4b9 + checksum: 065c85149b05954e032622cb6be2a97f2cea0b776d87017b0d631df42f449b42e80469f2e2392ee039b9d63afb3f49477c8b3a263162d4294b145eb967f71c6d languageName: node linkType: hard -"@react-native-community/cli-platform-android@npm:20.1.1": - version: 20.1.1 - resolution: "@react-native-community/cli-platform-android@npm:20.1.1" +"@react-native-community/cli-platform-android@npm:20.2.0": + version: 20.2.0 + resolution: "@react-native-community/cli-platform-android@npm:20.2.0" dependencies: - "@react-native-community/cli-config-android": 20.1.1 - "@react-native-community/cli-tools": 20.1.1 + "@react-native-community/cli-config-android": 20.2.0 + "@react-native-community/cli-tools": 20.2.0 execa: ^5.0.0 logkitty: ^0.7.1 picocolors: ^1.1.1 - checksum: 80d7316a0fe275e69d7b1f1eb7d237933f791ffcff0dc6d57e2e40335459e3f5b2a5b68387bf70016195b7b2bd4d81f024c4c69de609b64581b23384af82cd35 + checksum: 0051a73be6173e7349f9ec90766004959db15e7d4851a6d1445878571a59e644cfd1bdbca69d22391f31269815e6f12c8365868fb5182f92e88bff570d2bc981 languageName: node linkType: hard -"@react-native-community/cli-platform-apple@npm:20.1.1": - version: 20.1.1 - resolution: "@react-native-community/cli-platform-apple@npm:20.1.1" +"@react-native-community/cli-platform-apple@npm:20.2.0": + version: 20.2.0 + resolution: "@react-native-community/cli-platform-apple@npm:20.2.0" dependencies: - "@react-native-community/cli-config-apple": 20.1.1 - "@react-native-community/cli-tools": 20.1.1 + "@react-native-community/cli-config-apple": 20.2.0 + "@react-native-community/cli-tools": 20.2.0 execa: ^5.0.0 - fast-xml-parser: ^4.4.1 + fast-xml-parser: ^5.3.6 picocolors: ^1.1.1 - checksum: bfafacf2466ffe79ea491e82c5a85d5ad7bde2df29b2cedb1c3489fdb14ab1a553a6d738382e3c32ec0f4f74239d2f07a9758fa45500d889ecf16dd6dd327255 + checksum: 4d3461ca3a48781d6fe352648cb22cd02fe58084da0309cf9c1cc508310665af5e4dd85e5e3988d19895c37f1fb0ededb54426078e3c16db1acb50d6b9f15422 languageName: node linkType: hard -"@react-native-community/cli-platform-ios@npm:20.1.1": - version: 20.1.1 - resolution: "@react-native-community/cli-platform-ios@npm:20.1.1" +"@react-native-community/cli-platform-ios@npm:20.2.0": + version: 20.2.0 + resolution: "@react-native-community/cli-platform-ios@npm:20.2.0" dependencies: - "@react-native-community/cli-platform-apple": 20.1.1 - checksum: a2ec3bf30048824f48143bc8b994b7bd0deec706898493b19366649da4bf0c1a426db0f73059c4e7ec79c25dd1463ccba0215b84c11d9ebcfce438b0a6d678a6 + "@react-native-community/cli-platform-apple": 20.2.0 + checksum: 5ccec2a92842043bdabfa3464226571dc1c9bf8e73dac2adbd3dea98a1d9f6e75d2dcb5b4fee9ea48c2acf6921013ba70f9b751a7255715f59ce9e0d66e1a4fc languageName: node linkType: hard -"@react-native-community/cli-server-api@npm:20.1.1": - version: 20.1.1 - resolution: "@react-native-community/cli-server-api@npm:20.1.1" +"@react-native-community/cli-server-api@npm:20.2.0": + version: 20.2.0 + resolution: "@react-native-community/cli-server-api@npm:20.2.0" dependencies: - "@react-native-community/cli-tools": 20.1.1 - body-parser: ^1.20.3 + "@react-native-community/cli-tools": 20.2.0 + body-parser: ^2.2.2 compression: ^1.7.1 connect: ^3.6.5 errorhandler: ^1.5.1 @@ -3596,15 +4114,14 @@ __metadata: open: ^6.2.0 pretty-format: ^29.7.0 serve-static: ^1.13.1 - strict-url-sanitise: 0.0.1 ws: ^6.2.3 - checksum: 0a8d926bd58f1d749abc43ca2d949fc4178da9e2fe3b0ecb26b690842cfa770389f44943a4b784b23b5b1944e79f8d3a3c4ceb58ec996efe0889e09eaaca8ab5 + checksum: d5089cb09c171ca4ce6d59501c931bb42d3283840a6c7b28ee767bf74bc594c6019cbe7e8def2de7c752130d48beef990f05b16b22815b15c767f795a415f461 languageName: node linkType: hard -"@react-native-community/cli-tools@npm:20.1.1": - version: 20.1.1 - resolution: "@react-native-community/cli-tools@npm:20.1.1" +"@react-native-community/cli-tools@npm:20.2.0": + version: 20.2.0 + resolution: "@react-native-community/cli-tools@npm:20.2.0" dependencies: "@vscode/sudo-prompt": ^9.0.0 appdirsjs: ^1.2.4 @@ -3616,29 +4133,29 @@ __metadata: picocolors: ^1.1.1 prompts: ^2.4.2 semver: ^7.5.2 - checksum: bf192d0acf8d1431492e4ee31c0f992b2179471d9fa7e0dc35cd46bf2e8bec376a4d30201a22f1e2790ac7cf3ce7e5e04fc2c181fa61465added2bcb9150ea6b + checksum: 15afb79d88bb078e95631fc25964b554202b0ff0faf607c7af21e22882ffff926c36dbfc76250f1793747a9809872645869c0b00273bc02afbc667c0221855d4 languageName: node linkType: hard -"@react-native-community/cli-types@npm:20.1.1": - version: 20.1.1 - resolution: "@react-native-community/cli-types@npm:20.1.1" +"@react-native-community/cli-types@npm:20.2.0": + version: 20.2.0 + resolution: "@react-native-community/cli-types@npm:20.2.0" dependencies: joi: ^17.2.1 - checksum: 0e03906f4636b35935eccabb47311748ceb5dc9328db16c42c5b7752aa21b822e0ddbf5dee91297310fe92db7cee19cfc312877bb0dd0c3b37cd183dcf127dda + checksum: 218fb539c8fb33bf18afe67500ca0a2d929e4e9b4ad33041101bf5f718abf8814249fae704fa60c9838dd2cfa8b3f97cf71dd35c5440055a6edffced67f50042 languageName: node linkType: hard -"@react-native-community/cli@npm:20.1.1, @react-native-community/cli@npm:^20.1.1": - version: 20.1.1 - resolution: "@react-native-community/cli@npm:20.1.1" +"@react-native-community/cli@npm:20.2.0, @react-native-community/cli@npm:^20.2.0": + version: 20.2.0 + resolution: "@react-native-community/cli@npm:20.2.0" dependencies: - "@react-native-community/cli-clean": 20.1.1 - "@react-native-community/cli-config": 20.1.1 - "@react-native-community/cli-doctor": 20.1.1 - "@react-native-community/cli-server-api": 20.1.1 - "@react-native-community/cli-tools": 20.1.1 - "@react-native-community/cli-types": 20.1.1 + "@react-native-community/cli-clean": 20.2.0 + "@react-native-community/cli-config": 20.2.0 + "@react-native-community/cli-doctor": 20.2.0 + "@react-native-community/cli-server-api": 20.2.0 + "@react-native-community/cli-tools": 20.2.0 + "@react-native-community/cli-types": 20.2.0 commander: ^9.4.1 deepmerge: ^4.3.0 execa: ^5.0.0 @@ -3650,37 +4167,37 @@ __metadata: semver: ^7.5.2 bin: rnc-cli: build/bin.js - checksum: cbbc72e3940a44f766677e5305d1d6990720f6421c0b7a20fd08b22ea71cc2e6a5ef2d39b5c85a2973e119b64d54ffd4a838d028f0019c0c3675c33172737a34 + checksum: c9b5796c3d9c920f419be2fced9ac2deaf476cffc44cfa4459401272cb33b1ca033db221dada1cafda627330566eba1a67cd23cb6854dd132253ad70af62552c languageName: node linkType: hard -"@react-native-community/slider@npm:^5.1.2": - version: 5.1.2 - resolution: "@react-native-community/slider@npm:5.1.2" - checksum: 49ce66bb7c87e26d17a75043814ca29b61b417ab694cb54af0a555470e5e2770efbe37e253c836973d0fa688ba2b1e86f0915f8ce78d3c4b8a882429034dc66d +"@react-native-community/slider@npm:^5.2.1": + version: 5.2.1 + resolution: "@react-native-community/slider@npm:5.2.1" + checksum: 966ce08eeaaf280f199dee0f5943894cc4d40e870afd765b5b04ed26b6c3d5aa2e9ea0600de66d41f682cb7ee7afae55a81bae6832b8bb92ab9c11f0354f3aa9 languageName: node linkType: hard -"@react-native/assets-registry@npm:0.83.1": - version: 0.83.1 - resolution: "@react-native/assets-registry@npm:0.83.1" - checksum: ec788b086fb1be0813d47660c34cdd758eb54dada0e9e1a2e8b55d888adab3bd9e6431742d645317f94033522805fc2c7902aa9de567d7c77d37b9619d927cd5 +"@react-native/assets-registry@npm:0.83.10": + version: 0.83.10 + resolution: "@react-native/assets-registry@npm:0.83.10" + checksum: b97f4b2ada4548b058f31fe6c20f0341031bcde864812e46e4cf8a5e6b535af077114e9976ce4eb62cc9f22349ee0b6f1bc69ef0d6481f43504c4415735d8733 languageName: node linkType: hard -"@react-native/babel-plugin-codegen@npm:0.83.1": - version: 0.83.1 - resolution: "@react-native/babel-plugin-codegen@npm:0.83.1" +"@react-native/babel-plugin-codegen@npm:0.83.10": + version: 0.83.10 + resolution: "@react-native/babel-plugin-codegen@npm:0.83.10" dependencies: "@babel/traverse": ^7.25.3 - "@react-native/codegen": 0.83.1 - checksum: 39e44ed3576b823434de4acea4d6fc1312da867d1dce9522d2634bce42f3bf061e94e0887b060825aa739d696b7324fda276469b4be4f8be86eea7d648b6dc55 + "@react-native/codegen": 0.83.10 + checksum: c904a8be807b7cf74caf04d303213fa2a0ff106bb805a33ece62aed30ca85587db747249c9929a487fd4cbdc944b6f8c8347e3e16f0794ab34ced6ec96a20cf7 languageName: node linkType: hard -"@react-native/babel-preset@npm:0.83.1": - version: 0.83.1 - resolution: "@react-native/babel-preset@npm:0.83.1" +"@react-native/babel-preset@npm:0.83.10": + version: 0.83.10 + resolution: "@react-native/babel-preset@npm:0.83.10" dependencies: "@babel/core": ^7.25.2 "@babel/plugin-proposal-export-default-from": ^7.24.7 @@ -3723,19 +4240,19 @@ __metadata: "@babel/plugin-transform-typescript": ^7.25.2 "@babel/plugin-transform-unicode-regex": ^7.24.7 "@babel/template": ^7.25.0 - "@react-native/babel-plugin-codegen": 0.83.1 + "@react-native/babel-plugin-codegen": 0.83.10 babel-plugin-syntax-hermes-parser: 0.32.0 babel-plugin-transform-flow-enums: ^0.0.2 react-refresh: ^0.14.0 peerDependencies: "@babel/core": "*" - checksum: fbff4569a44063d779de784e0e87871d1069438f30a909520f1902e04c72df4d36682b582dc834def8c71b06c4ac4f3481f61751c4b23cb720cf514d13f12b23 + checksum: 3fb284091a8696c3b596a2e445f3700e5697052a561f3c01574934512fcc4513c5d720afdab4b8e31024c01923b97d8e3c5adf473ea0423b89a1d6c2364e8bdb languageName: node linkType: hard -"@react-native/codegen@npm:0.83.1": - version: 0.83.1 - resolution: "@react-native/codegen@npm:0.83.1" +"@react-native/codegen@npm:0.83.10": + version: 0.83.10 + resolution: "@react-native/codegen@npm:0.83.10" dependencies: "@babel/core": ^7.25.2 "@babel/parser": ^7.25.3 @@ -3746,20 +4263,20 @@ __metadata: yargs: ^17.6.2 peerDependencies: "@babel/core": "*" - checksum: 49c7e79b81d2595df33617b29aea981716ac36d92083301977c896a8299d1e1ce86054a804c85e1411a3732fd4e1b71e6e9edf53830b577ec5a9dd9120ca45a0 + checksum: a315616c1b50333a3412aa8ccd94988d4b62be123bd6f6877a9cdf8d5d44a64b4505e0caf2f5665bdbf97efd3821752217491b877f2a3de6f560b3f00cd74107 languageName: node linkType: hard -"@react-native/community-cli-plugin@npm:0.83.1": - version: 0.83.1 - resolution: "@react-native/community-cli-plugin@npm:0.83.1" +"@react-native/community-cli-plugin@npm:0.83.10": + version: 0.83.10 + resolution: "@react-native/community-cli-plugin@npm:0.83.10" dependencies: - "@react-native/dev-middleware": 0.83.1 + "@react-native/dev-middleware": 0.83.10 debug: ^4.4.0 invariant: ^2.2.4 - metro: ^0.83.3 - metro-config: ^0.83.3 - metro-core: ^0.83.3 + metro: ^0.83.6 + metro-config: ^0.83.6 + metro-core: ^0.83.6 semver: ^7.1.3 peerDependencies: "@react-native-community/cli": "*" @@ -3769,34 +4286,34 @@ __metadata: optional: true "@react-native/metro-config": optional: true - checksum: 75d2a9e4de37bb4eb59d787e31c12e4e36db363b765d6ceaae68ab1f4c7cad021f9f8358eeef4c795949172d6af94f4d93081f98e4110a39d14868cecfde75bd + checksum: d3ab23c82467dac519d6208c4f7fc50e9ed3daea1ec36c488807962aac3ae84033cde15c2c023828786a2b53aa6c507c53cef0677e57f37cb4a949ff95b7f1ab languageName: node linkType: hard -"@react-native/debugger-frontend@npm:0.83.1": - version: 0.83.1 - resolution: "@react-native/debugger-frontend@npm:0.83.1" - checksum: 6eb15797a5a136a99443e9d8ee1da14a22cc3fdf629272811018a046d2d5abc0c9f60ccc41d7f95c5e04fbd361b4cdae924f79b81f7a11bdb119e15a072c08f7 +"@react-native/debugger-frontend@npm:0.83.10": + version: 0.83.10 + resolution: "@react-native/debugger-frontend@npm:0.83.10" + checksum: 6765839a21b6086455e6f27677f9b44e192e62cb54de19138d4ea99d1844889a53e9a0899a7d89fb23714c35b40bae0ee2ee8a6bd77644adfb3eb6809b8777b5 languageName: node linkType: hard -"@react-native/debugger-shell@npm:0.83.1": - version: 0.83.1 - resolution: "@react-native/debugger-shell@npm:0.83.1" +"@react-native/debugger-shell@npm:0.83.10": + version: 0.83.10 + resolution: "@react-native/debugger-shell@npm:0.83.10" dependencies: cross-spawn: ^7.0.6 fb-dotslash: 0.5.8 - checksum: 22f45aeb7f3f9f93c7e9615b66bf158e7f3764d5c31e4aea80b85ffef28369d82a2e6208c7dca80e0ceeadf3fa17616f4c90b8fdbab41826a8c72d4ff194309b + checksum: b8b3f82c0dfeae22dfece16ed11da8e0cb250876205e40d5f0fdf0f25cf9a34c9dc33b541861e11c7f9f7a3758a0adbd65f9996f1fc9f8480eae7f8447443054 languageName: node linkType: hard -"@react-native/dev-middleware@npm:0.83.1": - version: 0.83.1 - resolution: "@react-native/dev-middleware@npm:0.83.1" +"@react-native/dev-middleware@npm:0.83.10": + version: 0.83.10 + resolution: "@react-native/dev-middleware@npm:0.83.10" dependencies: "@isaacs/ttlcache": ^1.4.1 - "@react-native/debugger-frontend": 0.83.1 - "@react-native/debugger-shell": 0.83.1 + "@react-native/debugger-frontend": 0.83.10 + "@react-native/debugger-shell": 0.83.10 chrome-launcher: ^0.15.2 chromium-edge-launcher: ^0.2.0 connect: ^3.6.5 @@ -3806,17 +4323,17 @@ __metadata: open: ^7.0.3 serve-static: ^1.16.2 ws: ^7.5.10 - checksum: d8439119cd99a8db0649b97a1f459222f49bb9425e1248d1466e4f7f4a104915d1e6ccc11403a5a0f3aa810eea3aa836f921ff11f44c4d3a06769d96083beb86 + checksum: 1bbf2fa74191af2c7120b256d2ce1512a5ee74fcf4d5922b99686764efa87cd3522df36d4e3ec94b9821fc4330201f132a8108fd1026a041227cea6397bb4ed9 languageName: node linkType: hard -"@react-native/eslint-config@npm:^0.83.1": - version: 0.83.1 - resolution: "@react-native/eslint-config@npm:0.83.1" +"@react-native/eslint-config@npm:^0.83.10": + version: 0.83.10 + resolution: "@react-native/eslint-config@npm:0.83.10" dependencies: "@babel/core": ^7.25.2 "@babel/eslint-parser": ^7.25.1 - "@react-native/eslint-plugin": 0.83.1 + "@react-native/eslint-plugin": 0.83.10 "@typescript-eslint/eslint-plugin": ^8.36.0 "@typescript-eslint/parser": ^8.36.0 eslint-config-prettier: ^8.5.0 @@ -3829,74 +4346,74 @@ __metadata: peerDependencies: eslint: ">=8" prettier: ">=2" - checksum: c35bba5ecbbd51de7dad44e293a597758004f4225b2ccb11cd15f43a0fd77b1102c8897f8683e77b3f2165bd21bdbfbe1f4d36dfb1526e09d21b51a88cff93de + checksum: 8c479738d7a0e163b7a27297ca2efa1300d5275a335c458032fc9004cdd44249ef134d4feaba07270faa9c3053725106ac082a6921f96bbf96f30af7c104ede1 languageName: node linkType: hard -"@react-native/eslint-plugin@npm:0.83.1": - version: 0.83.1 - resolution: "@react-native/eslint-plugin@npm:0.83.1" - checksum: 7d051f2e8d6cd397cea33c543967888597da303ff2ee9550318627889e90f937f7808945fcce12eccefd0dbc7ba65ccba1ce630c1d2f94110ddd2afa13cca675 +"@react-native/eslint-plugin@npm:0.83.10": + version: 0.83.10 + resolution: "@react-native/eslint-plugin@npm:0.83.10" + checksum: 95d8399918c3ac941f4de0606dc4e9455246fc0b1e1e8aeac52dfdd7a58920e31ee11ecaf5fcbed36cbe43e6a84cb49a5fd73abb2c3b69ba669fa88080a1868e languageName: node linkType: hard -"@react-native/gradle-plugin@npm:0.83.1": - version: 0.83.1 - resolution: "@react-native/gradle-plugin@npm:0.83.1" - checksum: dcf126b36fc46d06d2c8e5482a63566aca36273c3b2da79c67e158ea82f25445775456077afc1fbaf0c198d3307aa94bda814d177c31a149fc1ee06ab0614105 +"@react-native/gradle-plugin@npm:0.83.10": + version: 0.83.10 + resolution: "@react-native/gradle-plugin@npm:0.83.10" + checksum: 6e6029e48e90fca81383c8c3c7c9ddb06a9aa2cacba8978eb16c76f58a9c966ff20c286aced71b2cc20fcaa43227bb27e8fc8bc29ca84e35202a2caa1e8a29f4 languageName: node linkType: hard -"@react-native/js-polyfills@npm:0.83.1": - version: 0.83.1 - resolution: "@react-native/js-polyfills@npm:0.83.1" - checksum: 1c3fbceac6371252d6e54f9e76b852bfaec7a7472455f9856467dd73a87b8445eda03fb38fc65bc9abd76606e6e52041c754db41f2a23c74dbf5e052e9af129a +"@react-native/js-polyfills@npm:0.83.10": + version: 0.83.10 + resolution: "@react-native/js-polyfills@npm:0.83.10" + checksum: 619252b9b318b0fdb8a9a629de304914daac6e7831aabd6800513700f411ea206fad39fce17a287409c4b2370dccdcc2d192aa990098a448dbc4dce87e33192c languageName: node linkType: hard -"@react-native/metro-babel-transformer@npm:0.83.1": - version: 0.83.1 - resolution: "@react-native/metro-babel-transformer@npm:0.83.1" +"@react-native/metro-babel-transformer@npm:0.83.10": + version: 0.83.10 + resolution: "@react-native/metro-babel-transformer@npm:0.83.10" dependencies: "@babel/core": ^7.25.2 - "@react-native/babel-preset": 0.83.1 + "@react-native/babel-preset": 0.83.10 hermes-parser: 0.32.0 nullthrows: ^1.1.1 peerDependencies: "@babel/core": "*" - checksum: c5b9fff540ae9d8df789823f6119e42415374079745bdd187be32ea919e0e7862f839e7311e102e0d3d3c5583c078021d4c3bb7a91374d306ef8ce4fe70a3793 + checksum: 70ce70ac8426861fa860c1ccd2d540995cd3f61c4e6f88632f230dde7c508b395124bef3addf7f8465e083fb3b10f5b67e40e632535024356671d8b3a218b9e1 languageName: node linkType: hard -"@react-native/metro-config@npm:0.83.1": - version: 0.83.1 - resolution: "@react-native/metro-config@npm:0.83.1" +"@react-native/metro-config@npm:0.83.10": + version: 0.83.10 + resolution: "@react-native/metro-config@npm:0.83.10" dependencies: - "@react-native/js-polyfills": 0.83.1 - "@react-native/metro-babel-transformer": 0.83.1 - metro-config: ^0.83.3 - metro-runtime: ^0.83.3 - checksum: dd5c4962e33aeecbb71fc70199de8bd7cf808d2e92b310eac2337e497683b92af68f388f206298988425f84c509768ea99a35802bcde405a07f32676107d691b + "@react-native/js-polyfills": 0.83.10 + "@react-native/metro-babel-transformer": 0.83.10 + metro-config: ^0.83.6 + metro-runtime: ^0.83.6 + checksum: 4f0f3c1a14f56a6081b6a9425948e8fd323f47f804c13bad18d214dea749bba9740ceb2d7c92a0ce1f618e33015135e5241530ce8e0f29ad169c11b935b006c7 languageName: node linkType: hard -"@react-native/normalize-colors@npm:0.83.1": - version: 0.83.1 - resolution: "@react-native/normalize-colors@npm:0.83.1" - checksum: dd87c889218522affe58059d424404cee28f168bc3641f015ee2620c55b3e29930d279eed6916f866c166bb53d425cd160ccfaab546a6123b6c74e9931eac5d1 +"@react-native/normalize-colors@npm:0.83.10": + version: 0.83.10 + resolution: "@react-native/normalize-colors@npm:0.83.10" + checksum: f41ff05f8abf39068cc1e207627fdff858a5996bf6324730b3f06e44ed16d1da9fde64666a7951be281cc64db379cef37a8d868ceecdc12dc7d395629b34dc8e languageName: node linkType: hard -"@react-native/typescript-config@npm:0.83.1": - version: 0.83.1 - resolution: "@react-native/typescript-config@npm:0.83.1" - checksum: fb0d4716fb0fc01cf50cb872ed417d6387e64d25a283dffa22053db3986550b0adb57c09d2e6c6fa3114b13bf366da06e2cc466e77c4aeb71106448bfd166847 +"@react-native/typescript-config@npm:0.83.10": + version: 0.83.10 + resolution: "@react-native/typescript-config@npm:0.83.10" + checksum: 3c18baf2f96f60d5164a7a2b5881638c2b38531e774710f66adba7db44d27a4e33525538f80838f56ae5c6459aa3114d277c5071de7eead39f9c5ae38322296e languageName: node linkType: hard -"@react-native/virtualized-lists@npm:0.83.1": - version: 0.83.1 - resolution: "@react-native/virtualized-lists@npm:0.83.1" +"@react-native/virtualized-lists@npm:0.83.10": + version: 0.83.10 + resolution: "@react-native/virtualized-lists@npm:0.83.10" dependencies: invariant: ^2.2.4 nullthrows: ^1.1.1 @@ -3907,32 +4424,36 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 35205e505c53ff95c71434c82d02d11a454c28d603189b84c83207fa121874d3c6e5a0b0605495fbaa6eef797a71aa42df8d1780e2e2c64ee1e6b2548a815e27 + checksum: de6348e3ac022e01b1d560b4383d76a219bbefa86ddb67b8f369f5787bfdef2df6d2090c0f9de700debb30a2e3e4f2e0c9fa274615b72c6dd72d4c996189ad7a languageName: node linkType: hard -"@release-it/conventional-changelog@npm:^10.0.4": - version: 10.0.4 - resolution: "@release-it/conventional-changelog@npm:10.0.4" +"@release-it/conventional-changelog@npm:^10.0.6": + version: 10.0.6 + resolution: "@release-it/conventional-changelog@npm:10.0.6" dependencies: - "@conventional-changelog/git-client": ^2.5.1 + "@conventional-changelog/git-client": ^2.6.0 concat-stream: ^2.0.0 - conventional-changelog: ^7.1.1 - conventional-changelog-angular: ^8.1.0 - conventional-changelog-conventionalcommits: ^9.1.0 + conventional-changelog: ^7.2.0 + conventional-changelog-angular: ^8.3.0 + conventional-changelog-conventionalcommits: ^9.3.0 conventional-recommended-bump: ^11.2.0 - semver: ^7.7.3 + semver: ^7.7.4 peerDependencies: release-it: ^18.0.0 || ^19.0.0 - checksum: 16fe278af2000d2a018fd71f1505a734d4aef6f9e79030d31103d25f46f842ee46ebf28b7daf8c9d96e1175c90f1cd8fa4888735d0eeb3ceac62745414e44f41 + checksum: cde63539d8796dde7d9671ce95c981ba62c98312386c6fc72851b3fc61e89091ac72bcbeec9b9d2b8bd234aa8db70fbd97517e31aad5f39dd64e24c8e6600460 languageName: node linkType: hard -"@shopify/react-native-skia@npm:^2.4.16": - version: 2.4.16 - resolution: "@shopify/react-native-skia@npm:2.4.16" +"@shopify/react-native-skia@npm:2.9.1": + version: 2.9.1 + resolution: "@shopify/react-native-skia@npm:2.9.1" dependencies: - canvaskit-wasm: 0.40.0 + canvaskit-wasm: 0.41.0 + react-native-skia-android: 150.0.0 + react-native-skia-apple-ios: 150.0.0 + react-native-skia-apple-macos: 150.0.0 + react-native-skia-apple-tvos: 150.0.0 react-reconciler: 0.31.0 peerDependencies: react: ">=19.0" @@ -3945,7 +4466,7 @@ __metadata: optional: true bin: setup-skia-web: scripts/setup-canvaskit.js - checksum: 9960767c1ccb602ab96bf4f3c31202e2aa8b55d41f476066c5f63d38b4934ca9c2b3d92a1080628b9da1d3676814a223b8164965bfe097df2f084b3f89e839d6 + checksum: 2bbdb425b561632a316aa1f460907870817ce55aebe6c147998d3e962bcc4cd10528dcf34ed17b868ed623ca58b7b1f9f464f14678efa7fdcf8a2c5b2a5156e6 languageName: node linkType: hard @@ -3982,6 +4503,13 @@ __metadata: languageName: node linkType: hard +"@simple-libs/hosted-git-info@npm:^1.0.2": + version: 1.0.2 + resolution: "@simple-libs/hosted-git-info@npm:1.0.2" + checksum: c5d3c4e743611124b3d5be12ec5d536cb9f16ce699e1d911b0e05b96d9c7b4f1afd2606e1baaf0889c843a111dd2f8648fd162eb0d09e20fddf9f13b0e7d2090 + languageName: node + linkType: hard + "@simple-libs/stream-utils@npm:^1.1.0": version: 1.1.0 resolution: "@simple-libs/stream-utils@npm:1.1.0" @@ -3991,6 +4519,13 @@ __metadata: languageName: node linkType: hard +"@simple-libs/stream-utils@npm:^1.2.0": + version: 1.2.0 + resolution: "@simple-libs/stream-utils@npm:1.2.0" + checksum: 80a2602f0e96515cab1f4ab054dccd0ee570b0a0b1722189d29fe2625e96a63b83c87486259268101b8b15a77a129aaca22bf480cf111e0910650af0820d26ee + languageName: node + linkType: hard + "@sinclair/typebox@npm:^0.27.8": version: 0.27.8 resolution: "@sinclair/typebox@npm:0.27.8" @@ -4030,6 +4565,48 @@ __metadata: languageName: node linkType: hard +"@turbo/darwin-64@npm:2.10.12": + version: 2.10.12 + resolution: "@turbo/darwin-64@npm:2.10.12" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@turbo/darwin-arm64@npm:2.10.12": + version: 2.10.12 + resolution: "@turbo/darwin-arm64@npm:2.10.12" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@turbo/linux-64@npm:2.10.12": + version: 2.10.12 + resolution: "@turbo/linux-64@npm:2.10.12" + conditions: (os=android | os=linux) & cpu=x64 + languageName: node + linkType: hard + +"@turbo/linux-arm64@npm:2.10.12": + version: 2.10.12 + resolution: "@turbo/linux-arm64@npm:2.10.12" + conditions: (os=android | os=linux) & cpu=arm64 + languageName: node + linkType: hard + +"@turbo/windows-64@npm:2.10.12": + version: 2.10.12 + resolution: "@turbo/windows-64@npm:2.10.12" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@turbo/windows-arm64@npm:2.10.12": + version: 2.10.12 + resolution: "@turbo/windows-arm64@npm:2.10.12" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@types/babel__core@npm:^7.1.14": version: 7.20.5 resolution: "@types/babel__core@npm:7.20.5" @@ -4170,12 +4747,12 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:^19.2.10": - version: 19.2.10 - resolution: "@types/react@npm:19.2.10" +"@types/react@npm:^19.2.18": + version: 19.2.18 + resolution: "@types/react@npm:19.2.18" dependencies: csstype: ^3.2.2 - checksum: d47df647fe594299b078a0ae0ef5bebd16969c19065986814e3cd38b1f062c7f2ae82ec90a0965fd1fb34139972a64fb87fca86f2ad88e547d718f2d3c0f68e3 + checksum: 01e42509fd2dbe05652bbe50f69883c79f6c5b24e5f306307e1f95eaf7eddf1cdd2646a1e056c6088d2c4403d5e2ab72226e31001c6564630529901514872fb1 languageName: node linkType: hard @@ -4381,7 +4958,17 @@ __metadata: languageName: node linkType: hard -"accepts@npm:^1.3.7, accepts@npm:~1.3.7": +"accepts@npm:^2.0.0": + version: 2.0.0 + resolution: "accepts@npm:2.0.0" + dependencies: + mime-types: ^3.0.0 + negotiator: ^1.0.0 + checksum: 49fe6c050cb6f6ff4e771b4d88324fca4d3127865f2473872e818dca127d809ba3aa8fdfc7acb51dd3c5bade7311ca6b8cfff7015ea6db2f7eb9c8444d223a4f + languageName: node + linkType: hard + +"accepts@npm:~1.3.7": version: 1.3.8 resolution: "accepts@npm:1.3.8" dependencies: @@ -4426,15 +5013,15 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^6.12.4": - version: 6.12.6 - resolution: "ajv@npm:6.12.6" +"ajv@npm:^6.14.0": + version: 6.15.0 + resolution: "ajv@npm:6.15.0" dependencies: fast-deep-equal: ^3.1.1 fast-json-stable-stringify: ^2.0.0 json-schema-traverse: ^0.4.1 uri-js: ^4.2.2 - checksum: 874972efe5c4202ab0a68379481fbd3d1b5d0a7bd6d3cc21d40d3536ebff3352a2a1fabb632d4fd2cc7fe4cbdcd5ed6782084c9bbf7f32a1536d18f9da5007d4 + checksum: a8e0308f1b44c3dfd1143911353be51bf8aedc2f2bcd595061755ad241c8450a10e4b657af8ba764c5ec9ae2162010f21d5e0d43763e20d782a8171da99b967a languageName: node linkType: hard @@ -4540,6 +5127,13 @@ __metadata: languageName: node linkType: hard +"anynum@npm:^1.0.1": + version: 1.0.1 + resolution: "anynum@npm:1.0.1" + checksum: 42b4b624ec5b6ba3cf2ba457568a66f48adfe59c7ea5dae3931aec1d5292f1549ee436d54ab1e205beb7c7c7eed136daa03c878e8d4575cd6b5e712df2f84146 + languageName: node + linkType: hard + "appdirsjs@npm:^1.2.4": version: 1.2.7 resolution: "appdirsjs@npm:1.2.7" @@ -4789,6 +5383,19 @@ __metadata: languageName: node linkType: hard +"babel-plugin-polyfill-corejs2@npm:^0.4.15": + version: 0.4.17 + resolution: "babel-plugin-polyfill-corejs2@npm:0.4.17" + dependencies: + "@babel/compat-data": ^7.28.6 + "@babel/helper-define-polyfill-provider": ^0.6.8 + semver: ^6.3.1 + peerDependencies: + "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 + checksum: 945f80f413706831b665322690c655f3782ca6fd8c1fbcccaf449d976ebe6151677fb9331442c72e85eae9a05d5e6633be4e15f75d3e788762d825d31f2964ce + languageName: node + linkType: hard + "babel-plugin-polyfill-corejs3@npm:^0.13.0": version: 0.13.0 resolution: "babel-plugin-polyfill-corejs3@npm:0.13.0" @@ -4801,6 +5408,18 @@ __metadata: languageName: node linkType: hard +"babel-plugin-polyfill-corejs3@npm:^0.14.0": + version: 0.14.2 + resolution: "babel-plugin-polyfill-corejs3@npm:0.14.2" + dependencies: + "@babel/helper-define-polyfill-provider": ^0.6.8 + core-js-compat: ^3.48.0 + peerDependencies: + "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 + checksum: 4bcaf4da658aaeb7a6534e6b65a6a45539c5f53bec596fefd0b44eebd249e5db8bbf239a421ceaff5933a0a7eee11e45791e4f4e04886cdf47bb1d4b1a8015aa + languageName: node + linkType: hard + "babel-plugin-polyfill-regenerator@npm:^0.6.5": version: 0.6.5 resolution: "babel-plugin-polyfill-regenerator@npm:0.6.5" @@ -4812,6 +5431,17 @@ __metadata: languageName: node linkType: hard +"babel-plugin-polyfill-regenerator@npm:^0.6.6": + version: 0.6.8 + resolution: "babel-plugin-polyfill-regenerator@npm:0.6.8" + dependencies: + "@babel/helper-define-polyfill-provider": ^0.6.8 + peerDependencies: + "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 + checksum: 974464353d6f974e97673385aff616a913c0b76039eab8c5317a2d07c661e080f3dcc213e86f3eae40010172a27ab793cda7a290a8a899716f9a22df9b1d92d2 + languageName: node + linkType: hard + "babel-plugin-syntax-hermes-parser@npm:0.32.0": version: 0.32.0 resolution: "babel-plugin-syntax-hermes-parser@npm:0.32.0" @@ -4890,6 +5520,15 @@ __metadata: languageName: node linkType: hard +"baseline-browser-mapping@npm:^2.11.20": + version: 2.11.21 + resolution: "baseline-browser-mapping@npm:2.11.21" + bin: + baseline-browser-mapping: dist/cli.cjs + checksum: 686840029c0130aa93e103d9fa8a47dc888e81b08a00f6a02a085b7c4568d188a250e7da6bd3b18b6e4cf424ef35061f3827551442d07dda386fb75c544e2e29 + languageName: node + linkType: hard + "basic-ftp@npm:^5.0.2": version: 5.0.5 resolution: "basic-ftp@npm:5.0.5" @@ -4915,23 +5554,20 @@ __metadata: languageName: node linkType: hard -"body-parser@npm:^1.20.3": - version: 1.20.3 - resolution: "body-parser@npm:1.20.3" +"body-parser@npm:^2.2.2": + version: 2.3.0 + resolution: "body-parser@npm:2.3.0" dependencies: - bytes: 3.1.2 - content-type: ~1.0.5 - debug: 2.6.9 - depd: 2.0.0 - destroy: 1.2.0 - http-errors: 2.0.0 - iconv-lite: 0.4.24 - on-finished: 2.4.1 - qs: 6.13.0 - raw-body: 2.5.2 - type-is: ~1.6.18 - unpipe: 1.0.0 - checksum: 1a35c59a6be8d852b00946330141c4f142c6af0f970faa87f10ad74f1ee7118078056706a05ae3093c54dabca9cd3770fa62a170a85801da1a4324f04381167d + bytes: ^3.1.2 + content-type: ^2.0.0 + debug: ^4.4.3 + http-errors: ^2.0.1 + iconv-lite: ^0.7.2 + on-finished: ^2.4.1 + qs: ^6.15.2 + raw-body: ^3.0.2 + type-is: ^2.1.0 + checksum: 44d681aaa2d6ffe3efb4f6c098e7c4f7beae0d158580f7abea1cd487fc05b485488815f5c6fc71aa05975541c00042b4becdb485c441ddd63ce9c71b63b7ea4d languageName: node linkType: hard @@ -4977,6 +5613,21 @@ __metadata: languageName: node linkType: hard +"browserslist@npm:^4.28.7": + version: 4.28.9 + resolution: "browserslist@npm:4.28.9" + dependencies: + baseline-browser-mapping: ^2.11.20 + caniuse-lite: ^1.0.30001810 + electron-to-chromium: ^1.5.420 + node-releases: ^2.0.54 + update-browserslist-db: ^1.3.2 + bin: + browserslist: cli.js + checksum: 598ea7c768c8d5044ba65b2ac9b235ed6851815dec0abb8fc1ee59c97a1bcaf8ab4aa38f0ec4265d4a5dac15763f6d0043a1c0f062317973d5d4d7b26c9fca40 + languageName: node + linkType: hard + "bser@npm:2.1.1": version: 2.1.1 resolution: "bser@npm:2.1.1" @@ -5012,7 +5663,7 @@ __metadata: languageName: node linkType: hard -"bytes@npm:3.1.2": +"bytes@npm:3.1.2, bytes@npm:^3.1.2, bytes@npm:~3.1.2": version: 3.1.2 resolution: "bytes@npm:3.1.2" checksum: e4bcd3948d289c5127591fbedf10c0b639ccbf00243504e4e127374a15c3bc8eed0d28d4aaab08ff6f1cf2abc0cce6ba3085ed32f4f90e82a5683ce0014e1b6e @@ -5124,12 +5775,19 @@ __metadata: languageName: node linkType: hard -"canvaskit-wasm@npm:0.40.0": - version: 0.40.0 - resolution: "canvaskit-wasm@npm:0.40.0" +"caniuse-lite@npm:^1.0.30001810": + version: 1.0.30001810 + resolution: "caniuse-lite@npm:1.0.30001810" + checksum: 4ff2dae8df59eb5f5ae71c4803666cf581459a9432c7cf016afd85f4f110774fb9c6469a62ff69a73ed7be5a7eae27988656c3acf56bceed7b0f24358426a5a8 + languageName: node + linkType: hard + +"canvaskit-wasm@npm:0.41.0": + version: 0.41.0 + resolution: "canvaskit-wasm@npm:0.41.0" dependencies: "@webgpu/types": 0.1.21 - checksum: 983e6b2a32babcbf0a8c29412fee57305d55b7e9603d5ba1e4c68f082fb649433cd1eb8cfddf17dda6dc041ef2c77eb9d9c7f0cba852f6f75f4e78809547d9c5 + checksum: 4204de4bea15f5dd4a1695aab0ff0ab5a103a55e51ac33aaadcad5a436e5ba0574eaa59f552346a64b6d25b7dfbfd332e440225675ad3ae7b517881e255a4b07 languageName: node linkType: hard @@ -5499,10 +6157,10 @@ __metadata: languageName: node linkType: hard -"content-type@npm:~1.0.5": - version: 1.0.5 - resolution: "content-type@npm:1.0.5" - checksum: 566271e0a251642254cde0f845f9dd4f9856e52d988f4eb0d0dcffbb7a1f8ec98de7a5215fc628f3bce30fe2fb6fd2bc064b562d721658c59b544e2d34ea2766 +"content-type@npm:^2.0.0": + version: 2.1.0 + resolution: "content-type@npm:2.1.0" + checksum: d49ee84ba26900bfd57c95913be9baffdc2b51c4c8cc9bcdfa815d29aa7af6dce547d04007a356519da4b45a75a21fedbaee244aaef84a55e32900fcb79bce0b languageName: node linkType: hard @@ -5515,21 +6173,21 @@ __metadata: languageName: node linkType: hard -"conventional-changelog-angular@npm:^8.1.0": - version: 8.1.0 - resolution: "conventional-changelog-angular@npm:8.1.0" +"conventional-changelog-angular@npm:^8.3.0": + version: 8.3.1 + resolution: "conventional-changelog-angular@npm:8.3.1" dependencies: compare-func: ^2.0.0 - checksum: 1f14b235ab09b74e658353aa8ce559ec99de34f6c3e54923fee327291373baa720143a3172d12ae17cc3d9634b34a7bee57559c211354c9557d33743e5245f75 + checksum: 1c40ef7831034debf026652452903f32308f49b1453d5dce1c6501489210207df68659e73dfd0f3a3cbed4532e42667cd19f5ce7d036fb91b681bc85d2f8bbe3 languageName: node linkType: hard -"conventional-changelog-conventionalcommits@npm:^9.1.0": - version: 9.1.0 - resolution: "conventional-changelog-conventionalcommits@npm:9.1.0" +"conventional-changelog-conventionalcommits@npm:^9.2.0, conventional-changelog-conventionalcommits@npm:^9.3.0": + version: 9.3.1 + resolution: "conventional-changelog-conventionalcommits@npm:9.3.1" dependencies: compare-func: ^2.0.0 - checksum: 9f40dc7c8b96cb44d07b835f19ea76506b151be1f17558d57c7f266c5f76d7c64d1d4c3b39f16ba5a8a1934b76835235f00e2f1d25baa1f3ddf01b52a162bd44 + checksum: 6b581bf8e1fca7e404333cc04568140825495b652e559cc83dd56e40864a1178a2864001d0354de6b75c575739b9e7f328bb99339472c855c165990952a565b8 languageName: node linkType: hard @@ -5540,35 +6198,37 @@ __metadata: languageName: node linkType: hard -"conventional-changelog-writer@npm:^8.2.0": - version: 8.2.0 - resolution: "conventional-changelog-writer@npm:8.2.0" +"conventional-changelog-writer@npm:^8.4.0": + version: 8.4.0 + resolution: "conventional-changelog-writer@npm:8.4.0" dependencies: + "@simple-libs/stream-utils": ^1.2.0 conventional-commits-filter: ^5.0.0 handlebars: ^4.7.7 meow: ^13.0.0 semver: ^7.5.2 bin: conventional-changelog-writer: dist/cli/index.js - checksum: 0a7b62fdc06dbe3e8f0feff2c51295ebc03d8046db73111b3c6a595472885551adf9ef2eeb741c43794466e58c1f23a055160c8aef08cacfe769b86ea2b7c611 + checksum: c537aa18fab4358fe30e90315e1c961c2429cfa9e50ad61fba178839242a98b56a1fb37846ea744a0691dae8a2b9566334be55a2a385f741274abd6351409a3d languageName: node linkType: hard -"conventional-changelog@npm:^7.1.1": - version: 7.1.1 - resolution: "conventional-changelog@npm:7.1.1" +"conventional-changelog@npm:^7.2.0": + version: 7.2.1 + resolution: "conventional-changelog@npm:7.2.1" dependencies: - "@conventional-changelog/git-client": ^2.5.1 + "@conventional-changelog/git-client": ^2.7.0 + "@simple-libs/hosted-git-info": ^1.0.2 "@types/normalize-package-data": ^2.4.4 conventional-changelog-preset-loader: ^5.0.0 - conventional-changelog-writer: ^8.2.0 - conventional-commits-parser: ^6.2.0 + conventional-changelog-writer: ^8.4.0 + conventional-commits-parser: ^6.4.0 fd-package-json: ^2.0.0 meow: ^13.0.0 normalize-package-data: ^7.0.0 bin: conventional-changelog: dist/cli/index.js - checksum: 44b66b24092c384b2d1af22b3521ddf0e1f9caf973fd3de07323c2d67fbcfb7f566b7597a5b06406b478a5294ed7972ab37c6dd70ab61fe1d025fdb907b28fd2 + checksum: a6ff14125fd6c84980168aaaf7dc8e10d03f7e564cd0886ca1f385bb5899ccfdf78d0089f06388cacbae7b83c54856b1424e895bd0b44ef2bc88db19bbeced8a languageName: node linkType: hard @@ -5593,7 +6253,7 @@ __metadata: languageName: node linkType: hard -"conventional-commits-parser@npm:^6.1.0, conventional-commits-parser@npm:^6.2.0, conventional-commits-parser@npm:^6.2.1": +"conventional-commits-parser@npm:^6.1.0": version: 6.2.1 resolution: "conventional-commits-parser@npm:6.2.1" dependencies: @@ -5604,6 +6264,18 @@ __metadata: languageName: node linkType: hard +"conventional-commits-parser@npm:^6.3.0, conventional-commits-parser@npm:^6.4.0": + version: 6.4.0 + resolution: "conventional-commits-parser@npm:6.4.0" + dependencies: + "@simple-libs/stream-utils": ^1.2.0 + meow: ^13.0.0 + bin: + conventional-commits-parser: dist/cli/index.js + checksum: c7e1f3075e5af116b1e9cd0e29368c7a4b3d877351d6d211797b7b0c71368b33ef98504b26d66f819697777505c46c3a0f074eb7d2f1d00bae51e532f491911f + languageName: node + linkType: hard + "conventional-recommended-bump@npm:^11.2.0": version: 11.2.0 resolution: "conventional-recommended-bump@npm:11.2.0" @@ -5635,6 +6307,15 @@ __metadata: languageName: node linkType: hard +"core-js-compat@npm:^3.48.0": + version: 3.50.0 + resolution: "core-js-compat@npm:3.50.0" + dependencies: + browserslist: ^4.28.7 + checksum: dc74d7cb879da8b1a10de54804f2d4b7bc143caa330c843aa2f42e5ca6e302dc968518182ed25724a864127b158bee245abd1a146632f872011ac24f5fd82be5 + languageName: node + linkType: hard + "cosmiconfig-typescript-loader@npm:^6.1.0": version: 6.1.0 resolution: "cosmiconfig-typescript-loader@npm:6.1.0" @@ -5775,6 +6456,18 @@ __metadata: languageName: node linkType: hard +"debug@npm:^4.4.3": + version: 4.4.3 + resolution: "debug@npm:4.4.3" + dependencies: + ms: ^2.1.3 + peerDependenciesMeta: + supports-color: + optional: true + checksum: 4805abd570e601acdca85b6aa3757186084a45cff9b2fa6eee1f3b173caa776b45f478b2a71a572d616d2010cea9211d0ac4a02a610e4c18ac4324bde3760834 + languageName: node + linkType: hard + "decamelize@npm:^1.2.0": version: 1.2.0 resolution: "decamelize@npm:1.2.0" @@ -5933,7 +6626,7 @@ __metadata: languageName: node linkType: hard -"depd@npm:2.0.0": +"depd@npm:2.0.0, depd@npm:~2.0.0": version: 2.0.0 resolution: "depd@npm:2.0.0" checksum: abbe19c768c97ee2eed6282d8ce3031126662252c58d711f646921c9623f9052e3e1906443066beec1095832f534e57c523b7333f8e7e0d93051ab6baef5ab3a @@ -6034,6 +6727,13 @@ __metadata: languageName: node linkType: hard +"electron-to-chromium@npm:^1.5.420": + version: 1.5.422 + resolution: "electron-to-chromium@npm:1.5.422" + checksum: 4d613b3d89579cca61db5d4dbfbcbd38ff6faa86de6b30f438b04020dc0fcfd67345e5492c99c793d643a9df39fb5911cb77b2ced1212d6dbb69573455b31b5c + languageName: node + linkType: hard + "emittery@npm:^0.13.1": version: 0.13.1 resolution: "emittery@npm:0.13.1" @@ -6373,7 +7073,7 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-ft-flow@npm:^2.0.1": +"eslint-plugin-ft-flow@npm:^2.0.1, eslint-plugin-ft-flow@npm:^2.0.3": version: 2.0.3 resolution: "eslint-plugin-ft-flow@npm:2.0.3" dependencies: @@ -6404,12 +7104,12 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-prettier@npm:^5.5.5": - version: 5.5.5 - resolution: "eslint-plugin-prettier@npm:5.5.5" +"eslint-plugin-prettier@npm:^5.5.6": + version: 5.5.6 + resolution: "eslint-plugin-prettier@npm:5.5.6" dependencies: prettier-linter-helpers: ^1.0.1 - synckit: ^0.11.12 + synckit: ^0.11.13 peerDependencies: "@types/eslint": ">=8.0.0" eslint: ">=8.0.0" @@ -6420,7 +7120,7 @@ __metadata: optional: true eslint-config-prettier: optional: true - checksum: 49b1c25d75ded255a8707d5f06288ae86e8ab4f8e273d4aabdabf73cd0903848916d5a3598ba8be82f2c8dd06769c5e6c172503b3b9cfb2636b6fc23b9c024fb + checksum: 9e2d94d6631af110966733d19aea15699ef684b543de23d1812423b35a02203ac87ef2d8784f236950ecc4026828e98f00e7c2b41879ee9c974a0a0563f1c754 languageName: node linkType: hard @@ -6526,23 +7226,23 @@ __metadata: languageName: node linkType: hard -"eslint@npm:^9.39.2": - version: 9.39.2 - resolution: "eslint@npm:9.39.2" +"eslint@npm:^9.39.5": + version: 9.39.5 + resolution: "eslint@npm:9.39.5" dependencies: "@eslint-community/eslint-utils": ^4.8.0 "@eslint-community/regexpp": ^4.12.1 - "@eslint/config-array": ^0.21.1 + "@eslint/config-array": ^0.21.2 "@eslint/config-helpers": ^0.4.2 "@eslint/core": ^0.17.0 - "@eslint/eslintrc": ^3.3.1 - "@eslint/js": 9.39.2 + "@eslint/eslintrc": ^3.3.6 + "@eslint/js": 9.39.5 "@eslint/plugin-kit": ^0.4.1 "@humanfs/node": ^0.16.6 "@humanwhocodes/module-importer": ^1.0.1 "@humanwhocodes/retry": ^0.4.2 "@types/estree": ^1.0.6 - ajv: ^6.12.4 + ajv: ^6.14.0 chalk: ^4.0.0 cross-spawn: ^7.0.6 debug: ^4.3.2 @@ -6561,7 +7261,7 @@ __metadata: is-glob: ^4.0.0 json-stable-stringify-without-jsonify: ^1.0.1 lodash.merge: ^4.6.2 - minimatch: ^3.1.2 + minimatch: ^3.1.5 natural-compare: ^1.4.0 optionator: ^0.9.3 peerDependencies: @@ -6571,7 +7271,7 @@ __metadata: optional: true bin: eslint: bin/eslint.js - checksum: bfa288fe6b19b6e7f8868e1434d8e469603203d6259e4451b8be4e2172de3172f3b07ed8943ba3904f3545c7c546062c0d656774baa0a10a54483f3907c525e3 + checksum: a84d9f6b9eee55b0dab6fb823ed50e78314557c49cd6af9450286a14693f205bdf97a342d112fce38f9bad74eda263c8a5a25b239f4d727ffe4cc41a7f0ac2e1 languageName: node linkType: hard @@ -6803,14 +7503,29 @@ __metadata: languageName: node linkType: hard -"fast-xml-parser@npm:^4.4.1": - version: 4.5.3 - resolution: "fast-xml-parser@npm:4.5.3" +"fast-xml-builder@npm:^1.2.0": + version: 1.3.1 + resolution: "fast-xml-builder@npm:1.3.1" + dependencies: + path-expression-matcher: ^1.6.2 + xml-naming: ^0.3.0 + checksum: e243f8b2adeb9ffbea8318f9bed9518dfff8d607b10b66ca6dd07793a3dbc87a674518f27ad27ef1c227cb9019954eee92fed938daeb6bdf69260b85204b495b + languageName: node + linkType: hard + +"fast-xml-parser@npm:^5.3.6": + version: 5.11.1 + resolution: "fast-xml-parser@npm:5.11.1" dependencies: - strnum: ^1.1.1 + "@nodable/entities": ^3.0.0 + fast-xml-builder: ^1.2.0 + is-unsafe: ^2.0.0 + path-expression-matcher: ^1.6.2 + strnum: ^2.4.2 + xml-naming: ^0.3.0 bin: fxparser: src/cli/cli.js - checksum: cd6a184941ec6c23f9e6b514421a3f396cfdff5f4a8c7c27bd0eff896edb4a2b55c27da16f09b789663613dfc4933602b9b71ac3e9d1d2ddcc0492fc46c8fa52 + checksum: 65b7aeeb6d8fb7a59a9435bdba8ad4503c42e950d1523d3751cdb4e24c606263605e6a53a61a249156f8f360a79b20da70a003c189cfd613eddbdf07bb511f3a languageName: node linkType: hard @@ -7424,10 +8139,19 @@ __metadata: languageName: node linkType: hard -"hermes-compiler@npm:0.14.0": - version: 0.14.0 - resolution: "hermes-compiler@npm:0.14.0" - checksum: 5b614ebe621e92550efd77a6aefe85d9cbab865386dc36de9895d4684ba0af13623d045b99f5b834f91a42ba3f00982462908eaf7cb6c8423056e9d5c8280ab3 +"hasown@npm:^2.0.3": + version: 2.0.4 + resolution: "hasown@npm:2.0.4" + dependencies: + function-bind: ^1.1.2 + checksum: 4bd8f916b629e06324853593ffbdd45e200022952a85ad0c967f3bd4c2e4c7e1f9a9766fbe6186f60bd394e0afc73e719730caa1da15cd9bd832b7cdf53fd26c + languageName: node + linkType: hard + +"hermes-compiler@npm:0.14.1": + version: 0.14.1 + resolution: "hermes-compiler@npm:0.14.1" + checksum: f904c190a51977e33c9c54d2817de25d217c01057c2249fbaa7976b1aaf473a759d9899d7c020329a5840ca68d1b8d40cd8c9add2c56d02306d4d9f8c47940ba languageName: node linkType: hard @@ -7452,6 +8176,13 @@ __metadata: languageName: node linkType: hard +"hermes-estree@npm:0.35.0": + version: 0.35.0 + resolution: "hermes-estree@npm:0.35.0" + checksum: da25f2f5a9aedf1ca0844a64fad21aa3262f4998ee584da4237408d8bc08562ff6a3923b1bf52aa85b9a9c5b2b29ce54d00c61fe9f2424dae9e67261441d73ac + languageName: node + linkType: hard + "hermes-parser@npm:0.28.1": version: 0.28.1 resolution: "hermes-parser@npm:0.28.1" @@ -7470,6 +8201,15 @@ __metadata: languageName: node linkType: hard +"hermes-parser@npm:0.35.0": + version: 0.35.0 + resolution: "hermes-parser@npm:0.35.0" + dependencies: + hermes-estree: 0.35.0 + checksum: 097572045fc574afc7a1d35ab3304651605408770371f8eb74ef7a971b48b0e3cf82e45156fa431ba60bf5ee196100c69d4fa107e6fc2d4e18757aa0a1ae0382 + languageName: node + linkType: hard + "hermes-parser@npm:^0.25.1": version: 0.25.1 resolution: "hermes-parser@npm:0.25.1" @@ -7515,6 +8255,19 @@ __metadata: languageName: node linkType: hard +"http-errors@npm:^2.0.1, http-errors@npm:~2.0.1": + version: 2.0.1 + resolution: "http-errors@npm:2.0.1" + dependencies: + depd: ~2.0.0 + inherits: ~2.0.4 + setprototypeof: ~1.2.0 + statuses: ~2.0.2 + toidentifier: ~1.0.1 + checksum: 155d1a100a06e4964597013109590b97540a177b69c3600bbc93efc746465a99a2b718f43cdf76b3791af994bbe3a5711002046bf668cdc007ea44cea6df7ccd + languageName: node + linkType: hard + "http-proxy-agent@npm:^7.0.0, http-proxy-agent@npm:^7.0.1": version: 7.0.2 resolution: "http-proxy-agent@npm:7.0.2" @@ -7556,15 +8309,6 @@ __metadata: languageName: node linkType: hard -"iconv-lite@npm:0.4.24": - version: 0.4.24 - resolution: "iconv-lite@npm:0.4.24" - dependencies: - safer-buffer: ">= 2.1.2 < 3" - checksum: bd9f120f5a5b306f0bc0b9ae1edeb1577161503f5f8252a20f1a9e56ef8775c9959fd01c55f2d3a39d9a8abaf3e30c1abeb1895f367dcbbe0a8fd1c9ca01c4f6 - languageName: node - linkType: hard - "iconv-lite@npm:^0.6.2": version: 0.6.3 resolution: "iconv-lite@npm:0.6.3" @@ -7583,6 +8327,15 @@ __metadata: languageName: node linkType: hard +"iconv-lite@npm:^0.7.2, iconv-lite@npm:~0.7.0": + version: 0.7.3 + resolution: "iconv-lite@npm:0.7.3" + dependencies: + safer-buffer: ">= 2.1.2 < 3.0.0" + checksum: 36b3dc2f5a25c2cac15f7df42a1f23e796f1616b2b4103b205a5c237d9b40483184a8c07bbc2d72b01ca28f4770506fad5a43203adae9c97a5d4733af684b6a8 + languageName: node + linkType: hard + "ieee754@npm:^1.1.13": version: 1.2.1 resolution: "ieee754@npm:1.2.1" @@ -7604,17 +8357,6 @@ __metadata: languageName: node linkType: hard -"image-size@npm:^1.0.2": - version: 1.2.1 - resolution: "image-size@npm:1.2.1" - dependencies: - queue: 6.0.2 - bin: - image-size: bin/image-size.js - checksum: 8601ddd4edc1db16f097f5cf585c23214e29c3b8f4d8a8f8d59b8e3bae2338c8a5073236bfff421d8541091a98a38b802ed049203c745286a69d1aac4e5bc4c7 - languageName: node - linkType: hard - "import-fresh@npm:^3.2.1, import-fresh@npm:^3.3.0": version: 3.3.1 resolution: "import-fresh@npm:3.3.1" @@ -7668,7 +8410,7 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3": +"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.4": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 4a48a733847879d6cf6691860a6b1e3f0f4754176e4d71494c41f3475553768b10f84b5ce1d40fbd0e34e6bfbb864ee35858ad4dd2cf31e02fc4a154b724d7f1 @@ -7805,6 +8547,15 @@ __metadata: languageName: node linkType: hard +"is-core-module@npm:^2.16.1": + version: 2.16.2 + resolution: "is-core-module@npm:2.16.2" + dependencies: + hasown: ^2.0.3 + checksum: 9317844b4959f8fb268bfc1b4e24033d60058235c2e7273499c2abfd8e4510e7059b1339bd9109766293747daa3e0b5a89095fb2825a866a4093563fe8fdf16f + languageName: node + linkType: hard + "is-data-view@npm:^1.0.1, is-data-view@npm:^1.0.2": version: 1.0.2 resolution: "is-data-view@npm:1.0.2" @@ -8142,6 +8893,13 @@ __metadata: languageName: node linkType: hard +"is-unsafe@npm:^2.0.0": + version: 2.0.2 + resolution: "is-unsafe@npm:2.0.2" + checksum: c3fcbb646e312ef59e30d9b2a4d0fc646bcc0d7e2e5d79cb4633fff1846e290073c1f9aaaa936ff8d098ddf23e19222b705288e520eb2bcfc56dd9d10947a210 + languageName: node + linkType: hard + "is-weakmap@npm:^2.0.2": version: 2.0.2 resolution: "is-weakmap@npm:2.0.2" @@ -8826,14 +9584,14 @@ __metadata: languageName: node linkType: hard -"js-yaml@npm:^4.1.1": - version: 4.1.1 - resolution: "js-yaml@npm:4.1.1" +"js-yaml@npm:^4.3.2": + version: 4.3.2 + resolution: "js-yaml@npm:4.3.2" dependencies: argparse: ^2.0.1 bin: js-yaml: bin/js-yaml.js - checksum: ea2339c6930fe048ec31b007b3c90be2714ab3e7defcc2c27ebf30c74fd940358f29070b4345af0019ef151875bf3bc3f8644bea1bab0372652b5044813ac02d + checksum: 3c9294448ed83fec340f017f2be056dab05bcba3fc742468a80275d264e982dfd94350a93640eb810e0fdf283bef7c1c810690cc69b7b04a187f781e079d2555 languageName: node linkType: hard @@ -9281,10 +10039,10 @@ __metadata: languageName: node linkType: hard -"media-typer@npm:0.3.0": - version: 0.3.0 - resolution: "media-typer@npm:0.3.0" - checksum: af1b38516c28ec95d6b0826f6c8f276c58aec391f76be42aa07646b4e39d317723e869700933ca6995b056db4b09a78c92d5440dc23657e6764be5d28874bba1 +"media-typer@npm:^1.1.0": + version: 1.1.1 + resolution: "media-typer@npm:1.1.1" + checksum: ceef972e43912621b846f816e8937c200dcd83d0bdd4fdd19f28be62a9334f4e6605d0c952fe804bfab7f63a07d14c0d1bea1d9357c35ad6d3b04b184f09e397 languageName: node linkType: hard @@ -9339,69 +10097,70 @@ __metadata: languageName: node linkType: hard -"metro-babel-transformer@npm:0.83.3": - version: 0.83.3 - resolution: "metro-babel-transformer@npm:0.83.3" +"metro-babel-transformer@npm:0.83.8": + version: 0.83.8 + resolution: "metro-babel-transformer@npm:0.83.8" dependencies: "@babel/core": ^7.25.2 flow-enums-runtime: ^0.0.6 - hermes-parser: 0.32.0 + hermes-parser: 0.35.0 + metro-cache-key: 0.83.8 nullthrows: ^1.1.1 - checksum: dd178409d1718dae12dfffb6572ebc5bb78f1e0d7e93dce829c945957f8a686cb1b4c466c69585d7b982b3937fbea28d5c53a80691f2fc66717a0bcc800bc5b8 + checksum: 1c8703c965485adf9459c724b3a1a5e946197f573ec6b4fba7eefa55d44acb3075f0a3924cfcdaba057f82a29c1b2b6a1efec0f57ab2de9adcab8d8359075a7e languageName: node linkType: hard -"metro-cache-key@npm:0.83.3": - version: 0.83.3 - resolution: "metro-cache-key@npm:0.83.3" +"metro-cache-key@npm:0.83.8": + version: 0.83.8 + resolution: "metro-cache-key@npm:0.83.8" dependencies: flow-enums-runtime: ^0.0.6 - checksum: a6f9d2bf8b810f57d330d6f8f1ebf029e1224f426c5895f73d9bc1007482684048bfc7513a855626ee7f3ae72ca46e1b08cf983aefbfa84321bb7c0cef4ba4ae + checksum: 785851646f1e59c261d909aedd21f3c9f7668bf7a44812b551926df98a6ced92418722860cba2ed33bbbcd6482c245d10012234c25a6820ac541002297adf9d9 languageName: node linkType: hard -"metro-cache@npm:0.83.3": - version: 0.83.3 - resolution: "metro-cache@npm:0.83.3" +"metro-cache@npm:0.83.8": + version: 0.83.8 + resolution: "metro-cache@npm:0.83.8" dependencies: exponential-backoff: ^3.1.1 flow-enums-runtime: ^0.0.6 https-proxy-agent: ^7.0.5 - metro-core: 0.83.3 - checksum: 95606275411d85de071fd95171a9548406cd1154320850a554bf00207804f7844ed252f9750a802d6612ade839c579b23bd87927ae173f43c368e8f5d900149d + metro-core: 0.83.8 + checksum: a7c2d7353d08ac50a904e8cd0dc126fa6223847b56ab942a9268d05da49814a28af085bb63a0b741c6bfe2647f36ba676b94ca90c7a817baf2cb10b85d5e8303 languageName: node linkType: hard -"metro-config@npm:0.83.3, metro-config@npm:^0.83.3": - version: 0.83.3 - resolution: "metro-config@npm:0.83.3" +"metro-config@npm:0.83.8, metro-config@npm:^0.83.6": + version: 0.83.8 + resolution: "metro-config@npm:0.83.8" dependencies: connect: ^3.6.5 flow-enums-runtime: ^0.0.6 jest-validate: ^29.7.0 - metro: 0.83.3 - metro-cache: 0.83.3 - metro-core: 0.83.3 - metro-runtime: 0.83.3 + metro: 0.83.8 + metro-cache: 0.83.8 + metro-core: 0.83.8 + metro-runtime: 0.83.8 yaml: ^2.6.1 - checksum: a14b77668a9712abbcebe5bf6a0081f0fd46caf8d37405174f261765abcd44d7a99910533fcc05edde3de10f9b22820cc9910c7dee2b01e761692a0a322f2608 + checksum: 12a4bc7f17bc7947298c1c85296bbf46d8e2716e33186b4bd2c1d31ae12e57da146a7bdc07e9a0fc651db7184cf94ac31220da544c277e7252fe0f76175fcc18 languageName: node linkType: hard -"metro-core@npm:0.83.3, metro-core@npm:^0.83.3": - version: 0.83.3 - resolution: "metro-core@npm:0.83.3" +"metro-core@npm:0.83.8, metro-core@npm:^0.83.6": + version: 0.83.8 + resolution: "metro-core@npm:0.83.8" dependencies: flow-enums-runtime: ^0.0.6 lodash.throttle: ^4.1.1 - metro-resolver: 0.83.3 - checksum: d06871313310cd718094ecbae805bcacea3f325340f6dff3c5044b62457c4690dd729cdb938349bdd3c41efa6f28032ae07696467ef006d5509fec9045c1966f + metro-resolver: 0.83.8 + checksum: e991b89b9af2aa65e822f64faf1b659bc5b43d4561c3f8324abe7472a994182bdcf5e44c20668a4b3592bdcac9b376d7b3c8764d8ca9b99357dc156282d36449 languageName: node linkType: hard -"metro-file-map@npm:0.83.3": - version: 0.83.3 - resolution: "metro-file-map@npm:0.83.3" +"metro-file-map@npm:0.83.8": + version: 0.83.8 + resolution: "metro-file-map@npm:0.83.8" dependencies: debug: ^4.4.0 fb-watchman: ^2.0.0 @@ -9412,146 +10171,143 @@ __metadata: micromatch: ^4.0.4 nullthrows: ^1.1.1 walker: ^1.0.7 - checksum: 0dea599206e93b6e8628be2aa98452d4dae16e805b810759ec8b50cebcd83f2d053f7e5865196d464f3793f86b3b5003830c6713f91bf62fa406a4af7c93a776 + checksum: 3518bab099280f1c2b190e95fadbab546412d40ec94cb184ee3a52856c66e1c8838a7174716d4d675fa19165f6eae3015c076d606f7a4e4402c54087a04c7ac3 languageName: node linkType: hard -"metro-minify-terser@npm:0.83.3": - version: 0.83.3 - resolution: "metro-minify-terser@npm:0.83.3" +"metro-minify-terser@npm:0.83.8": + version: 0.83.8 + resolution: "metro-minify-terser@npm:0.83.8" dependencies: flow-enums-runtime: ^0.0.6 terser: ^5.15.0 - checksum: 1de88b70b7c903147807baa46497491a87600594fd0868b6538bbb9d7785242cabfbe8bccf36cc2285d0e17be72445b512d00c496952a159572545f3e6bcb199 + checksum: 2255f9bb7b2a5e7fa292688543816ed1d10c813fa10247a7725a607dd74329dae27add7d963106151e953a03e1250f597a90788fdac56de437cf2f2bd05f2391 languageName: node linkType: hard -"metro-resolver@npm:0.83.3": - version: 0.83.3 - resolution: "metro-resolver@npm:0.83.3" +"metro-resolver@npm:0.83.8": + version: 0.83.8 + resolution: "metro-resolver@npm:0.83.8" dependencies: flow-enums-runtime: ^0.0.6 - checksum: de2ae5ced6239b004a97712f98934c6e830870d11614e2dba48250930214581f0746df8a4f0f1cb71060fe21c2cf919d3359106ad4f375c2500ba08e10922896 + checksum: 31ec1edeab83a658545d5c454bd14a643aadc0c135185eacedc037cb4594c8def23d7260ebd17fbaa5f03473e74d4c23da5bcbf8d77aeefac875063ac4d34bcf languageName: node linkType: hard -"metro-runtime@npm:0.83.3, metro-runtime@npm:^0.83.3": - version: 0.83.3 - resolution: "metro-runtime@npm:0.83.3" +"metro-runtime@npm:0.83.8, metro-runtime@npm:^0.83.6": + version: 0.83.8 + resolution: "metro-runtime@npm:0.83.8" dependencies: "@babel/runtime": ^7.25.0 flow-enums-runtime: ^0.0.6 - checksum: dcbdc5502020d1e20cee1a3a8019323ab2f3ca2aa2d6ddb2b7a2b8547835a20b84fe4afc23c397f788584e108c70411db93df2f61322b44a4f0f119275052d03 + checksum: 795483d8cb63892190b120d2ef25686ca1d54537ecaf4741acb1dd52d093f80d77609213408beac05b198ad05dca925c6e946d23be97947ffc610643b3d6e39d languageName: node linkType: hard -"metro-source-map@npm:0.83.3, metro-source-map@npm:^0.83.3": - version: 0.83.3 - resolution: "metro-source-map@npm:0.83.3" +"metro-source-map@npm:0.83.8, metro-source-map@npm:^0.83.6": + version: 0.83.8 + resolution: "metro-source-map@npm:0.83.8" dependencies: - "@babel/traverse": ^7.25.3 - "@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3" - "@babel/types": ^7.25.2 + "@babel/traverse": ^7.29.0 + "@babel/types": ^7.29.0 flow-enums-runtime: ^0.0.6 invariant: ^2.2.4 - metro-symbolicate: 0.83.3 + metro-symbolicate: 0.83.8 nullthrows: ^1.1.1 - ob1: 0.83.3 + ob1: 0.83.8 source-map: ^0.5.6 vlq: ^1.0.0 - checksum: 5bf3b7a1561bc1f0ad6ab3b7b550d4b4581da31964a7f218727a3201576912076c909a2e50fba4dd3c649d79312324dec683a37228f4559811c37b69ecca8831 + checksum: 95b7845cef58e8d783f4c4a8364e2c474adbf920db55149ad5e69192f8a8815953cca7d65acd80949daae8b03e83418f80eb65e87e2a62f133f500f87ae65c2f languageName: node linkType: hard -"metro-symbolicate@npm:0.83.3": - version: 0.83.3 - resolution: "metro-symbolicate@npm:0.83.3" +"metro-symbolicate@npm:0.83.8": + version: 0.83.8 + resolution: "metro-symbolicate@npm:0.83.8" dependencies: flow-enums-runtime: ^0.0.6 invariant: ^2.2.4 - metro-source-map: 0.83.3 + metro-source-map: 0.83.8 nullthrows: ^1.1.1 source-map: ^0.5.6 vlq: ^1.0.0 bin: metro-symbolicate: src/index.js - checksum: 943cc2456d56ae2ed8369495c18966d91feff636b37909b5225ffb8ce2a50eba8fbedf116f3bea3059d431ebc621c9c9af8a8bfd181b0cd1fece051507e10ffd + checksum: 35705dcdeb498b527c93ad463d89102a657f2a85048eb612e26bb72fbfaf380da1c8add72d4459155baf6011906c7ed4cd10f39b8a115eefa3edba4ffe0a8c69 languageName: node linkType: hard -"metro-transform-plugins@npm:0.83.3": - version: 0.83.3 - resolution: "metro-transform-plugins@npm:0.83.3" +"metro-transform-plugins@npm:0.83.8": + version: 0.83.8 + resolution: "metro-transform-plugins@npm:0.83.8" dependencies: "@babel/core": ^7.25.2 - "@babel/generator": ^7.25.0 - "@babel/template": ^7.25.0 - "@babel/traverse": ^7.25.3 + "@babel/generator": ^7.29.1 + "@babel/template": ^7.28.6 + "@babel/traverse": ^7.29.0 flow-enums-runtime: ^0.0.6 nullthrows: ^1.1.1 - checksum: 6f92b9dfa53bdb63e79038bbd4d68791379ab26cf874679e64563618c578eeed3a828795debf8076ffd518431dff53191990784fb619046bcc03fff114b0cb21 + checksum: 1d846da34690481d520b89adb8265b4441d997eabb863950ef22eefb022b44beea2f15734efe2b6c77956ac4b8d5bc03eeca70fa3489aee439f12435366f762d languageName: node linkType: hard -"metro-transform-worker@npm:0.83.3": - version: 0.83.3 - resolution: "metro-transform-worker@npm:0.83.3" +"metro-transform-worker@npm:0.83.8": + version: 0.83.8 + resolution: "metro-transform-worker@npm:0.83.8" dependencies: "@babel/core": ^7.25.2 - "@babel/generator": ^7.25.0 - "@babel/parser": ^7.25.3 - "@babel/types": ^7.25.2 + "@babel/generator": ^7.29.1 + "@babel/parser": ^7.29.0 + "@babel/types": ^7.29.0 flow-enums-runtime: ^0.0.6 - metro: 0.83.3 - metro-babel-transformer: 0.83.3 - metro-cache: 0.83.3 - metro-cache-key: 0.83.3 - metro-minify-terser: 0.83.3 - metro-source-map: 0.83.3 - metro-transform-plugins: 0.83.3 + metro: 0.83.8 + metro-babel-transformer: 0.83.8 + metro-cache: 0.83.8 + metro-cache-key: 0.83.8 + metro-minify-terser: 0.83.8 + metro-source-map: 0.83.8 + metro-transform-plugins: 0.83.8 nullthrows: ^1.1.1 - checksum: fcb25ebc1ce703d830ef60c9af87325f996af4c3946325ab957b65ca59d12d181fe6c527c9ba1f932cd954d23a400052293117fe56f9a2727dfbc0a118e7bb27 + checksum: f9c473a9fc1d20007b9bbd4b165dde6af476192d891296eb9e4af698229a46e3bd6def3d4cef138603942d61523b12a020ed45816065b66e88ceba1fd7bcb277 languageName: node linkType: hard -"metro@npm:0.83.3, metro@npm:^0.83.3": - version: 0.83.3 - resolution: "metro@npm:0.83.3" +"metro@npm:0.83.8, metro@npm:^0.83.6": + version: 0.83.8 + resolution: "metro@npm:0.83.8" dependencies: - "@babel/code-frame": ^7.24.7 + "@babel/code-frame": ^7.29.0 "@babel/core": ^7.25.2 - "@babel/generator": ^7.25.0 - "@babel/parser": ^7.25.3 - "@babel/template": ^7.25.0 - "@babel/traverse": ^7.25.3 - "@babel/types": ^7.25.2 - accepts: ^1.3.7 - chalk: ^4.0.0 + "@babel/generator": ^7.29.1 + "@babel/parser": ^7.29.0 + "@babel/template": ^7.28.6 + "@babel/traverse": ^7.29.0 + "@babel/types": ^7.29.0 + accepts: ^2.0.0 ci-info: ^2.0.0 connect: ^3.6.5 debug: ^4.4.0 error-stack-parser: ^2.0.6 flow-enums-runtime: ^0.0.6 graceful-fs: ^4.2.4 - hermes-parser: 0.32.0 - image-size: ^1.0.2 + hermes-parser: 0.35.0 invariant: ^2.2.4 jest-worker: ^29.7.0 jsc-safe-url: ^0.2.2 lodash.throttle: ^4.1.1 - metro-babel-transformer: 0.83.3 - metro-cache: 0.83.3 - metro-cache-key: 0.83.3 - metro-config: 0.83.3 - metro-core: 0.83.3 - metro-file-map: 0.83.3 - metro-resolver: 0.83.3 - metro-runtime: 0.83.3 - metro-source-map: 0.83.3 - metro-symbolicate: 0.83.3 - metro-transform-plugins: 0.83.3 - metro-transform-worker: 0.83.3 - mime-types: ^2.1.27 + metro-babel-transformer: 0.83.8 + metro-cache: 0.83.8 + metro-cache-key: 0.83.8 + metro-config: 0.83.8 + metro-core: 0.83.8 + metro-file-map: 0.83.8 + metro-resolver: 0.83.8 + metro-runtime: 0.83.8 + metro-source-map: 0.83.8 + metro-symbolicate: 0.83.8 + metro-transform-plugins: 0.83.8 + metro-transform-worker: 0.83.8 + mime-types: ^3.0.1 nullthrows: ^1.1.1 serialize-error: ^2.1.0 source-map: ^0.5.6 @@ -9560,7 +10316,7 @@ __metadata: yargs: ^17.6.2 bin: metro: src/cli.js - checksum: 306d8c06b5a1a45e18df6e41f494bbc8b439700985429284eea7b3c3c82108e3c3795d859a8ab3ed7a85793d64e3160519be9aa84c6418d6ed37bd5ae4500b57 + checksum: a863cf9fe38fb44fb2d86bf35ec2ca00db116e86826ea1f6ea78e96e4664c415a189350e71d5cc14517c5b5f114887af9d785253790ea2ef620bde0af080c2a3 languageName: node linkType: hard @@ -9588,7 +10344,7 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:3.0.2": +"mime-types@npm:3.0.2, mime-types@npm:^3.0.0, mime-types@npm:^3.0.1": version: 3.0.2 resolution: "mime-types@npm:3.0.2" dependencies: @@ -9597,7 +10353,7 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:^2.1.27, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": +"mime-types@npm:~2.1.34": version: 2.1.35 resolution: "mime-types@npm:2.1.35" dependencies: @@ -9654,6 +10410,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:^3.1.5": + version: 3.1.5 + resolution: "minimatch@npm:3.1.5" + dependencies: + brace-expansion: ^1.1.7 + checksum: 47ef6f412c08be045a7291d11b1c40777925accf7252dc6d3caa39b1bfbb3a7ea390ba7aba464d762d783265c644143d2c8a204e6b5763145024d52ee65a1941 + languageName: node + linkType: hard + "minimatch@npm:^9.0.4": version: 9.0.5 resolution: "minimatch@npm:9.0.5" @@ -9884,6 +10649,13 @@ __metadata: languageName: node linkType: hard +"node-releases@npm:^2.0.54": + version: 2.0.54 + resolution: "node-releases@npm:2.0.54" + checksum: 34dd32cb489b71306e25014ccc5ddd2a1793ba13aed75dcbb6f7ec144fa093030cb91781d1d296fb7105987f42f23d3877a1ec0913702be4412f2493fdfe3102 + languageName: node + linkType: hard + "node-stream-zip@npm:^1.9.1": version: 1.15.0 resolution: "node-stream-zip@npm:1.15.0" @@ -9960,12 +10732,12 @@ __metadata: languageName: node linkType: hard -"ob1@npm:0.83.3": - version: 0.83.3 - resolution: "ob1@npm:0.83.3" +"ob1@npm:0.83.8": + version: 0.83.8 + resolution: "ob1@npm:0.83.8" dependencies: flow-enums-runtime: ^0.0.6 - checksum: 20dfe91d48d0cadd97159cfd53f5abdca435b55d58b1f562e0687485e8f44f8a95e8ab3c835badd13d0d8c01e3d7b14d639a316aa4bf82841ac78b49611d4e5c + checksum: b3e34f21f1288611f441564e027b36f206d887734a747ba561df82460eff0798a76ab98ee0955d060333ef425809f10d0a411936bf8fdce2e6d144a2a7c30a33 languageName: node linkType: hard @@ -10047,7 +10819,7 @@ __metadata: languageName: node linkType: hard -"on-finished@npm:2.4.1": +"on-finished@npm:2.4.1, on-finished@npm:^2.4.1": version: 2.4.1 resolution: "on-finished@npm:2.4.1" dependencies: @@ -10379,6 +11151,13 @@ __metadata: languageName: node linkType: hard +"path-expression-matcher@npm:^1.6.2": + version: 1.6.2 + resolution: "path-expression-matcher@npm:1.6.2" + checksum: a367990364461d48801a695db23f63d2842a3d9be60bf364148cfd69546f1eb621b76fd86c4c5c0b2545ac60fea0db85a8149a37094db0efc9d120beb5060172 + languageName: node + linkType: hard + "path-is-absolute@npm:^1.0.0": version: 1.0.1 resolution: "path-is-absolute@npm:1.0.1" @@ -10523,12 +11302,12 @@ __metadata: languageName: node linkType: hard -"prettier@npm:^3.8.1": - version: 3.8.1 - resolution: "prettier@npm:3.8.1" +"prettier@npm:^3.9.6": + version: 3.9.6 + resolution: "prettier@npm:3.9.6" bin: prettier: bin/prettier.cjs - checksum: 36fe4ecd95751aa17fea70b48afd5086e88002988238112fc1be30a5307af6983e1833be790b0cc1c54702b71f73b12bfec12c05166d7619e3151ab221654297 + checksum: 3fb6a3b6462b05afb825697a3fdcdd9bd1480c11d10d655b7ddbcaf2560beb695caffbe155b5020466005aa06bff74a61ba9c16e602bff849331b62f5db26cdf languageName: node linkType: hard @@ -10644,12 +11423,13 @@ __metadata: languageName: node linkType: hard -"qs@npm:6.13.0": - version: 6.13.0 - resolution: "qs@npm:6.13.0" +"qs@npm:^6.15.2": + version: 6.16.0 + resolution: "qs@npm:6.16.0" dependencies: - side-channel: ^1.0.6 - checksum: e9404dc0fc2849245107108ce9ec2766cde3be1b271de0bf1021d049dc5b98d1a2901e67b431ac5509f865420a7ed80b7acb3980099fe1c118a1c5d2e1432ad8 + es-define-property: ^1.0.1 + side-channel: ^1.1.1 + checksum: fb97665d1b481fadbcb17cc056f1af95ae93328195bf9536a56142137f4d9cc16fe2a1dab9cd17186c9fa69a7e6fa64a3766f71ffaa7c248ec5555f34d21047b languageName: node linkType: hard @@ -10660,15 +11440,6 @@ __metadata: languageName: node linkType: hard -"queue@npm:6.0.2": - version: 6.0.2 - resolution: "queue@npm:6.0.2" - dependencies: - inherits: ~2.0.3 - checksum: ebc23639248e4fe40a789f713c20548e513e053b3dc4924b6cb0ad741e3f264dcff948225c8737834dd4f9ec286dbc06a1a7c13858ea382d9379f4303bcc0916 - languageName: node - linkType: hard - "range-parser@npm:~1.2.1": version: 1.2.1 resolution: "range-parser@npm:1.2.1" @@ -10676,15 +11447,15 @@ __metadata: languageName: node linkType: hard -"raw-body@npm:2.5.2": - version: 2.5.2 - resolution: "raw-body@npm:2.5.2" +"raw-body@npm:^3.0.2": + version: 3.0.2 + resolution: "raw-body@npm:3.0.2" dependencies: - bytes: 3.1.2 - http-errors: 2.0.0 - iconv-lite: 0.4.24 - unpipe: 1.0.0 - checksum: ba1583c8d8a48e8fbb7a873fdbb2df66ea4ff83775421bfe21ee120140949ab048200668c47d9ae3880012f6e217052690628cf679ddfbd82c9fc9358d574676 + bytes: ~3.1.2 + http-errors: ~2.0.1 + iconv-lite: ~0.7.0 + unpipe: ~1.0.0 + checksum: bf8ce8e9734f273f24d81f9fed35609dbd25c2869faa5fb5075f7ee225c0913e2240adda03759d7e72f2a757f8012d58bb7a871a80261d5140ad65844caeb5bd languageName: node linkType: hard @@ -10726,23 +11497,23 @@ __metadata: version: 0.0.0-use.local resolution: "react-native-ahrs-example@workspace:example" dependencies: - "@babel/core": ^7.28.6 - "@babel/preset-env": ^7.28.6 - "@babel/runtime": ^7.28.6 + "@babel/core": ^7.29.7 + "@babel/preset-env": ^7.29.7 + "@babel/runtime": ^7.29.7 "@react-native-async-storage/async-storage": ^2.2.0 - "@react-native-community/cli": 20.1.1 - "@react-native-community/cli-platform-android": 20.1.1 - "@react-native-community/cli-platform-ios": 20.1.1 - "@react-native-community/slider": ^5.1.2 - "@react-native/babel-preset": 0.83.1 - "@react-native/metro-config": 0.83.1 - "@react-native/typescript-config": 0.83.1 - "@shopify/react-native-skia": ^2.4.16 - "@types/react": ^19.2.10 - react: 19.1.0 - react-native: 0.83.1 - react-native-builder-bob: ^0.40.17 - react-native-monorepo-config: ^0.3.2 + "@react-native-community/cli": 20.2.0 + "@react-native-community/cli-platform-android": 20.2.0 + "@react-native-community/cli-platform-ios": 20.2.0 + "@react-native-community/slider": ^5.2.1 + "@react-native/babel-preset": 0.83.10 + "@react-native/metro-config": 0.83.10 + "@react-native/typescript-config": 0.83.10 + "@shopify/react-native-skia": 2.9.1 + "@types/react": ^19.2.18 + react: 19.2.8 + react-native: 0.83.10 + react-native-builder-bob: ^0.40.18 + react-native-monorepo-config: ^0.3.5 react-native-orientation-director: ^2.6.5 languageName: unknown linkType: soft @@ -10751,28 +11522,29 @@ __metadata: version: 0.0.0-use.local resolution: "react-native-ahrs@workspace:." dependencies: - "@commitlint/config-conventional": ^20.4.0 + "@commitlint/config-conventional": ^20.5.3 "@eslint/compat": ^1.4.1 - "@eslint/eslintrc": ^3.3.3 - "@eslint/js": ^9.39.2 - "@react-native-community/cli": ^20.1.1 - "@react-native/babel-preset": 0.83.1 - "@react-native/eslint-config": ^0.83.1 - "@release-it/conventional-changelog": ^10.0.4 + "@eslint/eslintrc": ^3.3.7 + "@eslint/js": ^9.39.5 + "@react-native-community/cli": ^20.2.0 + "@react-native/babel-preset": 0.83.10 + "@react-native/eslint-config": ^0.83.10 + "@release-it/conventional-changelog": ^10.0.6 "@types/jest": ^29.5.14 - "@types/react": ^19.2.10 + "@types/react": ^19.2.18 commitlint: ^19.8.1 del-cli: ^7.0.0 - eslint: ^9.39.2 + eslint: ^9.39.5 eslint-config-prettier: ^10.1.8 - eslint-plugin-prettier: ^5.5.5 + eslint-plugin-ft-flow: ^2.0.3 + eslint-plugin-prettier: ^5.5.6 jest: ^29.7.0 - prettier: ^3.8.1 - react: 19.1.0 - react-native: 0.83.1 - react-native-builder-bob: ^0.40.17 + prettier: ^3.9.6 + react: 19.2.8 + react-native: 0.83.10 + react-native-builder-bob: ^0.40.18 release-it: ^19.2.4 - turbo: ^2.8.1 + turbo: ^2.10.12 typescript: ^5.9.3 peerDependencies: react: "*" @@ -10780,9 +11552,9 @@ __metadata: languageName: unknown linkType: soft -"react-native-builder-bob@npm:^0.40.17": - version: 0.40.17 - resolution: "react-native-builder-bob@npm:0.40.17" +"react-native-builder-bob@npm:^0.40.18": + version: 0.40.18 + resolution: "react-native-builder-bob@npm:0.40.18" dependencies: "@babel/core": ^7.25.2 "@babel/plugin-transform-flow-strip-types": ^7.26.5 @@ -10803,22 +11575,22 @@ __metadata: json5: ^2.2.1 kleur: ^4.1.4 prompts: ^2.4.2 - react-native-monorepo-config: ^0.3.1 + react-native-monorepo-config: ^0.3.3 which: ^2.0.2 yargs: ^17.5.1 bin: bob: bin/bob - checksum: 418b911295565994c06e827be39b7a7cd775c9a64c071dbcca37d336992f9186c4d9996da283ecb2ab93dacc0c12d126c0eaee03d15a77116d88590fbbbfc46a + checksum: 429cfa0fa4aa1bcc8410718563449dd31f450010d7d3c8be8a4a2ac0ac82b20be3bb2c2f0811df64e1cf2e4714f73c8cb9dd031e4a0a0e07ee0b1b96d8a4d70a languageName: node linkType: hard -"react-native-monorepo-config@npm:^0.3.1, react-native-monorepo-config@npm:^0.3.2": - version: 0.3.2 - resolution: "react-native-monorepo-config@npm:0.3.2" +"react-native-monorepo-config@npm:^0.3.3, react-native-monorepo-config@npm:^0.3.5": + version: 0.3.5 + resolution: "react-native-monorepo-config@npm:0.3.5" dependencies: escape-string-regexp: ^5.0.0 fast-glob: ^3.3.3 - checksum: 4ab58ff3a4e92675fad49240b396d16528cef993f4da635bbb899bfcb0d7dc655547c551747f1015ef4a726c493dcb9d571e7908a15dd8c9c2c7ef2146438bca + checksum: a2664c63f8db1115deb6c71192f3399ead2c1685e247efc7e0ca35fd43e48dc3fe50b254f0c2cd00a5073ee387949ee24e75ec486d271755884675e49ddb5aa3 languageName: node linkType: hard @@ -10836,18 +11608,46 @@ __metadata: languageName: node linkType: hard -"react-native@npm:0.83.1": - version: 0.83.1 - resolution: "react-native@npm:0.83.1" +"react-native-skia-android@npm:150.0.0": + version: 150.0.0 + resolution: "react-native-skia-android@npm:150.0.0" + checksum: e33eecf360925bc0402c1d6b0db5845f2a38d19244f9c128b7c208abb7dc2c07158cdfcd0c998c31271075ce8ad8a3b1bed329cd010a9df9a3ae58a752e8fded + languageName: node + linkType: hard + +"react-native-skia-apple-ios@npm:150.0.0": + version: 150.0.0 + resolution: "react-native-skia-apple-ios@npm:150.0.0" + checksum: d96bfdf9b5bd8eb038b29b4949f591acd9064b00e8d4229f8932b8a9e7bbcf206c6e57a32f1ad0ba6b58ff033388a6dbe059f73d9eb91686afb0f8a3742e5392 + languageName: node + linkType: hard + +"react-native-skia-apple-macos@npm:150.0.0": + version: 150.0.0 + resolution: "react-native-skia-apple-macos@npm:150.0.0" + checksum: 0963df1f238a03a6390e9c11eb3cc662a81db5ad2daee77eb7555f35f4ccf06ac32e94c37d42704a33bf8b09d1ebb31193361a5183662df29b9e28f10aa6937d + languageName: node + linkType: hard + +"react-native-skia-apple-tvos@npm:150.0.0": + version: 150.0.0 + resolution: "react-native-skia-apple-tvos@npm:150.0.0" + checksum: c96428599c75febf5f0ea9a9282d28dd100be1ddc5953cbd6afc845379ef24de2fd3e8f3643a16c1d864b395ce225f01e35621e613e88edcb5b0b9ba78011a3e + languageName: node + linkType: hard + +"react-native@npm:0.83.10": + version: 0.83.10 + resolution: "react-native@npm:0.83.10" dependencies: "@jest/create-cache-key-function": ^29.7.0 - "@react-native/assets-registry": 0.83.1 - "@react-native/codegen": 0.83.1 - "@react-native/community-cli-plugin": 0.83.1 - "@react-native/gradle-plugin": 0.83.1 - "@react-native/js-polyfills": 0.83.1 - "@react-native/normalize-colors": 0.83.1 - "@react-native/virtualized-lists": 0.83.1 + "@react-native/assets-registry": 0.83.10 + "@react-native/codegen": 0.83.10 + "@react-native/community-cli-plugin": 0.83.10 + "@react-native/gradle-plugin": 0.83.10 + "@react-native/js-polyfills": 0.83.10 + "@react-native/normalize-colors": 0.83.10 + "@react-native/virtualized-lists": 0.83.10 abort-controller: ^3.0.0 anser: ^1.4.9 ansi-regex: ^5.0.0 @@ -10857,12 +11657,12 @@ __metadata: commander: ^12.0.0 flow-enums-runtime: ^0.0.6 glob: ^7.1.1 - hermes-compiler: 0.14.0 + hermes-compiler: 0.14.1 invariant: ^2.2.4 jest-environment-node: ^29.7.0 memoize-one: ^5.0.0 - metro-runtime: ^0.83.3 - metro-source-map: ^0.83.3 + metro-runtime: ^0.83.6 + metro-source-map: ^0.83.6 nullthrows: ^1.1.1 pretty-format: ^29.7.0 promise: ^8.3.0 @@ -10883,7 +11683,7 @@ __metadata: optional: true bin: react-native: cli.js - checksum: 9de956e38287afb5d989a1dde5d5488d6c2499f6acb90d07a9526e92c0822b0c9884cd871cfe42daacc2f006bc95acc8d395ba794af415758b2a8a7e8ca1cce8 + checksum: 3b1c51a793fec42938e94ccc802c7ad2bd99cf4a0436ed12719c27736636b423f9326c30b17a477c20b86ea2bd89b1c7986a52360966c13ffa51ad098487b4c8 languageName: node linkType: hard @@ -10905,10 +11705,10 @@ __metadata: languageName: node linkType: hard -"react@npm:19.1.0": - version: 19.1.0 - resolution: "react@npm:19.1.0" - checksum: c0905f8cfb878b0543a5522727e5ed79c67c8111dc16ceee135b7fe19dce77b2c1c19293513061a8934e721292bfc1517e0487e262d1906f306bdf95fa54d02f +"react@npm:19.2.8": + version: 19.2.8 + resolution: "react@npm:19.2.8" + checksum: cdedb4786eb3eb79c8fbaa6df299de61ecd9e1cd4c7cd52213b5af7a98c67d532fd6b14b9d546ae5851f3962437aec22ad17ee6e8e0450de11c595300c5e5bd7 languageName: node linkType: hard @@ -11146,6 +11946,20 @@ __metadata: languageName: node linkType: hard +"resolve@npm:^1.22.11": + version: 1.22.12 + resolution: "resolve@npm:1.22.12" + dependencies: + es-errors: ^1.3.0 + is-core-module: ^2.16.1 + path-parse: ^1.0.7 + supports-preserve-symlinks-flag: ^1.0.0 + bin: + resolve: bin/resolve + checksum: 4dc5a614b32142ef9ab455b242ed33c472c4ea50df17dbe1e9dac5fe0eebd7d5fdb7cb9cc8ad2165e5e0f07694498a74e7fbd6cc1599e20d84682cce1b80a4dc + languageName: node + linkType: hard + "resolve@npm:^2.0.0-next.5": version: 2.0.0-next.5 resolution: "resolve@npm:2.0.0-next.5" @@ -11172,6 +11986,20 @@ __metadata: languageName: node linkType: hard +"resolve@patch:resolve@^1.22.11#~builtin": + version: 1.22.12 + resolution: "resolve@patch:resolve@npm%3A1.22.12#~builtin::version=1.22.12&hash=c3c19d" + dependencies: + es-errors: ^1.3.0 + is-core-module: ^2.16.1 + path-parse: ^1.0.7 + supports-preserve-symlinks-flag: ^1.0.0 + bin: + resolve: bin/resolve + checksum: 0cc5b060cbe081c85c331ac2eb08e8a54f0a195b899d5001822e5d3e2b335da651b1eed3d259fea904c22a0da9324a061e0e7ceab5dbeb5bcab5250b625754e1 + languageName: node + linkType: hard + "resolve@patch:resolve@^2.0.0-next.5#~builtin": version: 2.0.0-next.5 resolution: "resolve@patch:resolve@npm%3A2.0.0-next.5#~builtin::version=2.0.0-next.5&hash=c3c19d" @@ -11310,7 +12138,7 @@ __metadata: languageName: node linkType: hard -"safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0": +"safer-buffer@npm:>= 2.1.2 < 3.0.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" checksum: cab8f25ae6f1434abee8d80023d7e72b598cf1327164ddab31003c51215526801e40b66c5e65d658a0af1e9d6478cadcb4c745f4bd6751f97d8644786c0978b0 @@ -11331,7 +12159,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:7.7.3, semver@npm:^7.7.3": +"semver@npm:7.7.3": version: 7.7.3 resolution: "semver@npm:7.7.3" bin: @@ -11358,6 +12186,15 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.7.4": + version: 7.8.5 + resolution: "semver@npm:7.8.5" + bin: + semver: bin/semver.js + checksum: 0c580f17e88e2b45806dc5cf3d824f719c946999d3554bf30307c2b68b3300ab3c8bfcd84d8f489b93b4cbb5362f5fc8de4d2858954f18d834253c4450fc9f6b + languageName: node + linkType: hard + "send@npm:0.19.0": version: 0.19.0 resolution: "send@npm:0.19.0" @@ -11442,7 +12279,7 @@ __metadata: languageName: node linkType: hard -"setprototypeof@npm:1.2.0": +"setprototypeof@npm:1.2.0, setprototypeof@npm:~1.2.0": version: 1.2.0 resolution: "setprototypeof@npm:1.2.0" checksum: be18cbbf70e7d8097c97f713a2e76edf84e87299b40d085c6bf8b65314e994cc15e2e317727342fa6996e38e1f52c59720b53fe621e2eb593a6847bf0356db89 @@ -11482,6 +12319,16 @@ __metadata: languageName: node linkType: hard +"side-channel-list@npm:^1.0.1": + version: 1.0.1 + resolution: "side-channel-list@npm:1.0.1" + dependencies: + es-errors: ^1.3.0 + object-inspect: ^1.13.4 + checksum: 3499671cd52adaee739eac1e14d07530b8e3530192741aeb05e7fe4ad1b51d1368ceea2cd3c21b0f62b05410a5c70a7c4d997ba4b143303ef73d0c65dfd1c252 + languageName: node + linkType: hard + "side-channel-map@npm:^1.0.1": version: 1.0.1 resolution: "side-channel-map@npm:1.0.1" @@ -11507,7 +12354,7 @@ __metadata: languageName: node linkType: hard -"side-channel@npm:^1.0.6, side-channel@npm:^1.1.0": +"side-channel@npm:^1.1.0": version: 1.1.0 resolution: "side-channel@npm:1.1.0" dependencies: @@ -11520,6 +12367,19 @@ __metadata: languageName: node linkType: hard +"side-channel@npm:^1.1.1": + version: 1.1.1 + resolution: "side-channel@npm:1.1.1" + dependencies: + es-errors: ^1.3.0 + object-inspect: ^1.13.4 + side-channel-list: ^1.0.1 + side-channel-map: ^1.0.1 + side-channel-weakmap: ^1.0.2 + checksum: e0f217140c463636ee556260bbc8f47ba2931f1248826f58e1502704135814c763b325dd9ab122a04176aa45b4a4f8cc556415bcab7a0179d85250fb7812f063 + languageName: node + linkType: hard + "signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" @@ -11724,6 +12584,13 @@ __metadata: languageName: node linkType: hard +"statuses@npm:~2.0.2": + version: 2.0.2 + resolution: "statuses@npm:2.0.2" + checksum: 6927feb50c2a75b2a4caab2c565491f7a93ad3d8dbad7b1398d52359e9243a20e2ebe35e33726dee945125ef7a515e9097d8a1b910ba2bbd818265a2f6c39879 + languageName: node + linkType: hard + "stdin-discarder@npm:^0.2.2": version: 0.2.2 resolution: "stdin-discarder@npm:0.2.2" @@ -11741,13 +12608,6 @@ __metadata: languageName: node linkType: hard -"strict-url-sanitise@npm:0.0.1": - version: 0.0.1 - resolution: "strict-url-sanitise@npm:0.0.1" - checksum: 96d2e6d18c7b2efaabd547ec570a9a74f3a9ebcf973c8c0d1dceb06db30a52ea06341ea708360613f194f8a1cc644f8149c315196a0cec6b054a5a84742acf13 - languageName: node - linkType: hard - "string-length@npm:^4.0.1": version: 4.0.2 resolution: "string-length@npm:4.0.2" @@ -11939,10 +12799,12 @@ __metadata: languageName: node linkType: hard -"strnum@npm:^1.1.1": - version: 1.1.2 - resolution: "strnum@npm:1.1.2" - checksum: a85219eda13e97151c95e343a9e5960eacfb0a0ff98104b4c9cb7a212e3008bddf0c9714c9c37c2e508be78e741a04afc80027c2dc18509d1b5ffd4c37191fc2 +"strnum@npm:^2.4.2": + version: 2.4.2 + resolution: "strnum@npm:2.4.2" + dependencies: + anynum: ^1.0.1 + checksum: aec8958f66eee511e4bfc2d3e607a9e6821b7ed760da2df3cf6ac3e80b73f9e0a98d809766d10b12d39608d551fdbe2c6ee9a5512e95043c41ff3bc2aa51e715 languageName: node linkType: hard @@ -11971,12 +12833,12 @@ __metadata: languageName: node linkType: hard -"synckit@npm:^0.11.12": - version: 0.11.12 - resolution: "synckit@npm:0.11.12" +"synckit@npm:^0.11.13": + version: 0.11.13 + resolution: "synckit@npm:0.11.13" dependencies: - "@pkgr/core": ^0.2.9 - checksum: a53fb563d01ba8912a111b883fc3c701e267896ff8273e7aba9001f5f74711e125888f4039e93060795cd416122cf492ae419eb10a6a3e3b00e830917669d2cf + "@pkgr/core": ^0.3.6 + checksum: ec989ed45f3df2e7eb3141f00060669fbc6cac5649846d0579f336cee4ab047c8bac65baa75b4df991e1c1bd224675b10efbe69af3596ead5180ec81a6d4ec06 languageName: node linkType: hard @@ -12083,7 +12945,7 @@ __metadata: languageName: node linkType: hard -"toidentifier@npm:1.0.1": +"toidentifier@npm:1.0.1, toidentifier@npm:~1.0.1": version: 1.0.1 resolution: "toidentifier@npm:1.0.1" checksum: 952c29e2a85d7123239b5cfdd889a0dde47ab0497f0913d70588f19c53f7e0b5327c95f4651e413c74b785147f9637b17410ac8c846d5d4a20a5a33eb6dc3a45 @@ -12106,74 +12968,32 @@ __metadata: languageName: node linkType: hard -"turbo-darwin-64@npm:2.8.1": - version: 2.8.1 - resolution: "turbo-darwin-64@npm:2.8.1" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"turbo-darwin-arm64@npm:2.8.1": - version: 2.8.1 - resolution: "turbo-darwin-arm64@npm:2.8.1" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"turbo-linux-64@npm:2.8.1": - version: 2.8.1 - resolution: "turbo-linux-64@npm:2.8.1" - conditions: os=linux & cpu=x64 - languageName: node - linkType: hard - -"turbo-linux-arm64@npm:2.8.1": - version: 2.8.1 - resolution: "turbo-linux-arm64@npm:2.8.1" - conditions: os=linux & cpu=arm64 - languageName: node - linkType: hard - -"turbo-windows-64@npm:2.8.1": - version: 2.8.1 - resolution: "turbo-windows-64@npm:2.8.1" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - -"turbo-windows-arm64@npm:2.8.1": - version: 2.8.1 - resolution: "turbo-windows-arm64@npm:2.8.1" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"turbo@npm:^2.8.1": - version: 2.8.1 - resolution: "turbo@npm:2.8.1" - dependencies: - turbo-darwin-64: 2.8.1 - turbo-darwin-arm64: 2.8.1 - turbo-linux-64: 2.8.1 - turbo-linux-arm64: 2.8.1 - turbo-windows-64: 2.8.1 - turbo-windows-arm64: 2.8.1 +"turbo@npm:^2.10.12": + version: 2.10.12 + resolution: "turbo@npm:2.10.12" + dependencies: + "@turbo/darwin-64": 2.10.12 + "@turbo/darwin-arm64": 2.10.12 + "@turbo/linux-64": 2.10.12 + "@turbo/linux-arm64": 2.10.12 + "@turbo/windows-64": 2.10.12 + "@turbo/windows-arm64": 2.10.12 dependenciesMeta: - turbo-darwin-64: + "@turbo/darwin-64": optional: true - turbo-darwin-arm64: + "@turbo/darwin-arm64": optional: true - turbo-linux-64: + "@turbo/linux-64": optional: true - turbo-linux-arm64: + "@turbo/linux-arm64": optional: true - turbo-windows-64: + "@turbo/windows-64": optional: true - turbo-windows-arm64: + "@turbo/windows-arm64": optional: true bin: turbo: bin/turbo - checksum: cbd082ea464dbb45ecd9f43e7330b57a84421b7388b7106e837bd7fca65d9aa457f1cf33bb0138581792fd75f16458950bf47006205744b24efe5603f6136a62 + checksum: 578f19207c18fa5ac4952ca6c099e3d2321aa2632fb8aac144b33fa26c3c53e82038dc286581c5724eb0f8854bffeb8d23615530949e004ec78d0ad30880b358 languageName: node linkType: hard @@ -12214,13 +13034,14 @@ __metadata: languageName: node linkType: hard -"type-is@npm:~1.6.18": - version: 1.6.18 - resolution: "type-is@npm:1.6.18" +"type-is@npm:^2.1.0": + version: 2.1.0 + resolution: "type-is@npm:2.1.0" dependencies: - media-typer: 0.3.0 - mime-types: ~2.1.24 - checksum: 2c8e47675d55f8b4e404bcf529abdf5036c537a04c2b20177bcf78c9e3c1da69da3942b1346e6edb09e823228c0ee656ef0e033765ec39a70d496ef601a0c657 + content-type: ^2.0.0 + media-typer: ^1.1.0 + mime-types: ^3.0.0 + checksum: 214a44fc635fd1396bb656af1f0392d151e44e31f622ca2d030f4b4bb4f20069dab250c6798a6c6f4b215f87cd6f68099cbfdb0975696c3383f7a4cad26f6174 languageName: node linkType: hard @@ -12444,7 +13265,7 @@ __metadata: languageName: node linkType: hard -"unpipe@npm:1.0.0, unpipe@npm:~1.0.0": +"unpipe@npm:~1.0.0": version: 1.0.0 resolution: "unpipe@npm:1.0.0" checksum: 4fa18d8d8d977c55cb09715385c203197105e10a6d220087ec819f50cb68870f02942244f1017565484237f1f8c5d3cd413631b1ae104d3096f24fdfde1b4aa2 @@ -12465,6 +13286,20 @@ __metadata: languageName: node linkType: hard +"update-browserslist-db@npm:^1.3.2": + version: 1.3.2 + resolution: "update-browserslist-db@npm:1.3.2" + dependencies: + escalade: ^3.2.0 + picocolors: ^1.1.1 + peerDependencies: + browserslist: ">= 4.21.0" + bin: + update-browserslist-db: cli.js + checksum: 68b13d54c6da73d22fe36820ed3d32226811b3837386ec9cf2cf866d3c958fc5522946c20123867456cc199dcb952103a1d2a26d6b3b6eb47a317551ea24ea72 + languageName: node + linkType: hard + "uri-js@npm:^4.2.2": version: 4.4.1 resolution: "uri-js@npm:4.4.1" @@ -12765,6 +13600,13 @@ __metadata: languageName: node linkType: hard +"xml-naming@npm:^0.3.0": + version: 0.3.0 + resolution: "xml-naming@npm:0.3.0" + checksum: 842b816c0db75497588dd77ead8a6ff8a6be20b90a8fe24219b425ce91ab797e208eff8b0ddb26c0e1e69bfbc855ce6e4d4fbf7277cbe3785bbe6f28018ac804 + languageName: node + linkType: hard + "y18n@npm:^4.0.0": version: 4.0.3 resolution: "y18n@npm:4.0.3"