/**
 * @file pwm.c
 * @brief Compact single-file implementation of the VP44 PWM solenoid control
 *        pipeline. Combines interpolation, setpoint, supervisor, PI, PWM
 *        output, orchestrator, ported ex-external routines, and default
 *        cal data.
 *
 * Every arithmetic choice (MUL vs MULU, SHRA vs SHR, JGE vs JC) mirrors the
 * MCS-96 assembly — see the original src/*.c files for per-block address
 * annotations and disassembly/*.txt for the source.
 */
#include "pwm.h"
#include "cal_tables_rom.h"

/* Forward decls for ported ex-external routines (defined below). */
static int16_t s_eval      (const pwm_submap_descr_t *descr,
                            pwm_interp_slot_t *slot);
static int16_t s_combine   (const int16_t *y_base,
                            const pwm_interp_slot_t *sx,
                            const pwm_interp_slot_t *sy);
static int16_t s_refine    (const int16_t *y_base,
                            const pwm_interp_slot_t *slot);
static void    s_correction(pwm_runtime_t *rt,
                            const pwm_calibration_t *cal);
static void    s_recovery  (pwm_runtime_t *rt,
                            const pwm_calibration_t *cal,
                            uint16_t rpm);

/* ═════════════════════════════════════════════════════════════════════
 * Runtime init (file-static — exposed via pwm_init)
 * ═════════════════════════════════════════════════════════════════════ */
static void runtime_reset(pwm_runtime_t *rt)
{
    memset(rt, 0, sizeof *rt);
    rt->mode_flags              = 0x03;          /* first_call + outer */
    rt->min_rpm_openloop        = 0x01A4;        /* 420 RPM */
    rt->upper_breakpoint        = 107;
    rt->lower_breakpoint        = (int16_t)0xFF95;
    rt->center_slope            = 2560;
    rt->upper_outer_slope       = 5120;
    rt->lower_outer_slope       = 5120;
    rt->upper_integrator_gain   = 1024;
    rt->center_integrator_gain  = 1024;
    rt->lower_integrator_gain   = 2048;
    rt->pwm_period              = 29851;
    rt->shape_scale             = 24;
    rt->pwm_min                 = 205;
    rt->pwm_max                 = 3890;
    /* Observed defaults from Ghidra annotations on compute_closed_loop_correction
     * (0x01F4, 0x028A) and fast_recovery (0x01F4). TODO: locate flash source. */
    rt->pos_error_normalizer    = 500;           /* 0x01F4 */
    rt->neg_error_normalizer    = 650;           /* 0x028A */
    rt->recovery_count_threshold = 500;          /* 0x01F4 */
}

static void apply_calibration(pwm_runtime_t *rt, const pwm_calibration_t *c)
{
    rt->upper_breakpoint        = c->pi_upper_breakpoint;
    rt->lower_breakpoint        = c->pi_lower_breakpoint;
    rt->center_slope            = c->pi_center_slope;
    rt->upper_outer_slope       = c->pi_upper_outer_slope;
    rt->lower_outer_slope       = c->pi_lower_outer_slope;
    rt->upper_integrator_gain   = c->pi_upper_integrator_gain;
    rt->center_integrator_gain  = c->pi_center_integrator_gain;
    rt->lower_integrator_gain   = c->pi_lower_integrator_gain;
    rt->setpoint_offset         = c->setpoint_offset;
}

void pwm_init(pwm_runtime_t *rt,
              const pwm_calibration_t   *cal,
              const pwm_flash_t         *flash,
              const pwm_input_getters_t *getters)
{
    runtime_reset(rt);
    apply_calibration(rt, cal);
    rt->bound_cal     = cal;
    rt->bound_flash   = flash;
    rt->bound_getters = getters;
    /* Resolve each descriptor's input_ptr into this rt's inputs block, so
     * s_eval() can dereference live values. Descriptor order/IDs are fixed
     * by the PWM_SUBMAP_* enum in cal_tables_rom.h. */
    pwm_bind_submap_inputs(rt, pwm_submap_descrs, PWM_SUBMAP_COUNT);
}

static void read_inputs(pwm_runtime_t *rt)
{
    const pwm_input_getters_t *g = rt->bound_getters;
    void *ctx = g->ctx;
    rt->inputs.ckp_in         = g->ckp_in        (ctx);
    rt->inputs.rpm            = g->rpm           (ctx);
    rt->inputs.angle_dec_cmd  = g->angle_dec_cmd (ctx);
    rt->inputs.inj_qty_demand = g->inj_qty_demand(ctx);
    rt->inputs.b_fb_kw        = g->b_fb_kw       (ctx);
    rt->inputs.state_130      = g->state_130     (ctx);
    rt->inputs.supply_voltage = g->supply_voltage(ctx);
    rt->inputs.temperature    = g->temperature   (ctx);
}

/* ═════════════════════════════════════════════════════════════════════
 * Interpolation — helper_from_cal (0x838b–0x83fa)
 * Descending-X piecewise linear, signed MUL + signed DIV.
 * ═════════════════════════════════════════════════════════════════════ */
int16_t pwm_interp_lookup(const int16_t *x, const int16_t *y,
                          uint16_t n, int16_t in)
{
    if (in >= x[0])       return y[0];
    if (in <= x[n - 1u])  return y[n - 1u];

    /* One-step binary midpoint jump, then linear scan. */
    uint16_t mid = (n & 0xFFFEu) / 2u;
    uint16_t k = (in < x[mid]) ? (uint16_t)(mid + 1u) : 1u;
    while (k < n && in < x[k]) k++;

    int16_t num = (int16_t)(in - x[k]);
    int16_t dx  = (int16_t)(x[k] - x[k - 1u]);
    int16_t dy  = (int16_t)(y[k] - y[k - 1u]);
    int32_t prod = MUL_S16(num, dy);
    return (int16_t)((int16_t)(prod / (int32_t)dx) + y[k]);
}

/* ═════════════════════════════════════════════════════════════════════
 * Setpoint — precompute_control_support_maps (0x8e36–0x8f1f)
 * ═════════════════════════════════════════════════════════════════════ */
static void setpoint_compute(pwm_runtime_t *rt, const pwm_calibration_t *cal)
{
    /* Pair 0: slot_x=sp0_A(rpm), slot_y=sp0_B(inj_qty_demand) */
    s_eval(&pwm_submap_descrs[PWM_SUBMAP_SP0_A], &rt->setpoint_slot_x);
    s_eval(&pwm_submap_descrs[PWM_SUBMAP_SP0_B], &rt->setpoint_slot_y);
    rt->compensation_angle =
        s_combine(cal->y_pair[0], &rt->setpoint_slot_x, &rt->setpoint_slot_y);

    /* Pair 1: slot_x reused (still sp0_A rpm), slot_y=sp1_B(temperature) */
    s_eval(&pwm_submap_descrs[PWM_SUBMAP_SP1_B], &rt->setpoint_slot_y);
    rt->compensation_angle = (int16_t)(rt->compensation_angle +
        s_combine(cal->y_pair[1], &rt->setpoint_slot_x, &rt->setpoint_slot_y));

    /* Pair 2: slot_x=sp2_A(rpm), slot_y=sp2_B(angle_dec_cmd) */
    s_eval(&pwm_submap_descrs[PWM_SUBMAP_SP2_A], &rt->setpoint_slot_x);
    s_eval(&pwm_submap_descrs[PWM_SUBMAP_SP2_B], &rt->setpoint_slot_y);
    rt->compensation_angle = (int16_t)(rt->compensation_angle +
        s_combine(cal->y_pair[2], &rt->setpoint_slot_x, &rt->setpoint_slot_y));

    int16_t sum  = (int16_t)(rt->inputs.b_fb_kw + rt->compensation_angle);
    int16_t half = shra16(sum, 1);  /* SHRA — signed halve */
    rt->pre_offset_target = half;

    int16_t t = (int16_t)(half + rt->setpoint_offset);
    if (t < cal->target_minimum) t = cal->target_minimum;
    rt->target = t;
}

/* ═════════════════════════════════════════════════════════════════════
 * Supervisor — update_closed_loop_supervisor (0x929b–0x92f1)
 * One-sided (upper-only) clamp on cl_error_delta.
 * ═════════════════════════════════════════════════════════════════════ */
static void supervisor_update(pwm_runtime_t *rt)
{
    if (rt->reset_flag == 1) {
        rt->reset_flag = 0;
        rt->cl_enable_counter = 0;
        rt->supervisor_state_17e = 0;
        /* ROM snapshots integrator_hi → 0x033e here; confirmed dead-store, dropped */
    } else {
        rt->cl_enable_counter = (int16_t)(rt->cl_enable_counter + 1);
    }
    int16_t err = (int16_t)(rt->target - rt->inputs.ckp_in);
    err = (int16_t)(err + rt->angle_error_shaped);
    rt->cl_error_delta = err;
    if (err > rt->max_cl_error) rt->cl_error_delta = rt->max_cl_error;
}

/* ═════════════════════════════════════════════════════════════════════
 * PI controller — finalize_angle_request (0x713b–0x7414)
 * ═════════════════════════════════════════════════════════════════════ */
static void shape_error(pwm_runtime_t *rt)
{
    int16_t e = rt->angle_error;
    int16_t bp, slope, gain;

    if (e > rt->upper_breakpoint) {
        rt->mode_flags |= MODE_OUTER_REGION;
        bp = rt->upper_breakpoint; slope = rt->upper_outer_slope;
        gain = rt->upper_integrator_gain;
    } else if (e < rt->lower_breakpoint) {
        rt->mode_flags |= MODE_OUTER_REGION;
        bp = rt->lower_breakpoint; slope = rt->lower_outer_slope;
        gain = rt->lower_integrator_gain;
    } else {
        rt->mode_flags &= (uint8_t)~MODE_OUTER_REGION;
        rt->integrator_gain = rt->center_integrator_gain;
        rt->angle_error_shaped =
            (int16_t)shra32(MUL_S16(rt->center_slope, e), 8);
        return;
    }
    rt->integrator_gain = gain;
    int32_t outer  = MUL_S16((int16_t)(e - bp), slope);
    int32_t center = MUL_S16(rt->center_slope, bp);
    rt->angle_error_shaped = (int16_t)shra32(outer + center, 8);
}

static void pi_controller_update(pwm_runtime_t *rt,
                                 const pwm_calibration_t *cal,
                                 const pwm_flash_t *flash)
{
    const uint16_t rpm = rt->inputs.rpm;
    const int16_t  inj_qty_demand = rt->inputs.inj_qty_demand;

    /* A. Entry conditions — all three must hold for closed-loop. */
    bool open_loop = (rt->system_flags_110 & 0x20) != 0
                  || rt->inputs.state_130 < 0
                  || (uint16_t)rpm < (uint16_t)rt->min_rpm_openloop;

    if (open_loop) {
        rt->mode_flags |= MODE_FIRST_CALL;
        rt->active_request = rt->target;
        if (rt->active_request < flash->request_upper_limit)
            rt->active_request = flash->request_upper_limit;
        goto rotate_flags;
    }

    /* C. Error */
    rt->angle_error = (int16_t)(rt->target - rt->estimated_angle);

    /* D/E/F. Large-positive / large-negative / normal-range paths */
    if (rt->angle_error > flash->large_pos_error_thresh) {
        rt->mode_flags = (uint8_t)((rt->mode_flags | MODE_LARGE_POS) & ~MODE_LARGE_NEG);
        s_recovery(rt, cal, rpm);
    } else if (rt->angle_error < flash->large_neg_error_thresh) {
        rt->mode_flags = (uint8_t)((rt->mode_flags | MODE_LARGE_NEG) & ~MODE_LARGE_POS);
        if ((uint16_t)inj_qty_demand > (uint16_t)flash->inj_qty_demand_thresh)
            s_recovery(rt, cal, rpm);
        else
            rt->recovery_counter = 0;
    } else {
        rt->mode_flags &= (uint8_t)~(MODE_LARGE_POS | MODE_LARGE_NEG);
        if ((uint16_t)rt->error_persist_counter > 0u)
            rt->error_persist_counter = (int16_t)(rt->error_persist_counter - 1);
        if (rt->error_persist_counter == 0)
            rt->system_flags_110 &= (uint8_t)~0x01;
        if (rt->recovery_counter > 0)
            rt->recovery_counter = (int16_t)(rt->recovery_counter - 1);
    }

    /* G. Shape error */
    shape_error(rt);

    /* H. Integrator — first-call init or anti-windup-gated accumulate */
    if (rt->mode_flags & MODE_FIRST_CALL) {
        int16_t lo_prod = (int16_t)MUL_S16(rt->first_call_gain, rt->angle_error);
        int16_t init_hi = (int16_t)(shra16(lo_prod, 4) + rt->inputs.ckp_in);
        rt->integrator_state = (int32_t)(((uint32_t)(uint16_t)init_hi << 16) |
                                         INTEG_LO(rt->integrator_state));
        rt->mode_flags &= (uint8_t)~MODE_FIRST_CALL;
    } else {
        bool freeze = (rt->angle_error < 0)
                    ? (rt->mode_flags & MODE_NEG_SAT) != 0
                    : (rt->mode_flags & MODE_POS_SAT) != 0;
        if (!freeze) {
            int32_t inc = MUL_S16(rt->angle_error, rt->integrator_gain) << 4;
            rt->integrator_state += inc;
        }
    }

    /* I. Output + saturation clamps (signed) */
    rt->active_request = (int16_t)(rt->angle_error_shaped +
                                   INTEG_HI(rt->integrator_state));
    if (rt->active_request < flash->request_upper_limit) {
        rt->active_request = flash->request_upper_limit;
        rt->mode_flags = (uint8_t)((rt->mode_flags | MODE_NEG_SAT) & ~MODE_POS_SAT);
    } else if (rt->active_request > rt->max_cl_error) {
        rt->active_request = rt->max_cl_error;
        rt->mode_flags = (uint8_t)((rt->mode_flags | MODE_POS_SAT) & ~MODE_NEG_SAT);
    } else {
        rt->mode_flags &= (uint8_t)~(MODE_NEG_SAT | MODE_POS_SAT);
    }

rotate_flags:
    /* J. Rotate bits 4-5 into bits 6-7 (prev_sat_shadow). Consumed next
     * cycle by fast_recovery (FUN_70ae at 0x70ae/0x70b6) via the pattern
     *     sustained = (mode_flags << 2) & mode_flags
     * to detect two-cycle sustained saturation. [0x73f4–0x7414] */
    rt->mode_flags = (uint8_t)((rt->mode_flags & 0x3Fu) |
                               (((uint16_t)rt->mode_flags << 2) & 0xC0u));
}

/* ═════════════════════════════════════════════════════════════════════
 * PWM output — compute_pwm_duty_from_maps (0x7415–0x7760)
 * Nearly all UNSIGNED arithmetic (MULU, SHR, DIVU, JC/JNH).
 * ═════════════════════════════════════════════════════════════════════ */
static bool rpm_in_any_window(uint16_t rpm, const uint16_t *bp,
                              uint16_t lo_pad, uint16_t hi_pad)
{
    for (int i = 0; i < 4; i++) {
        uint16_t lo = (uint16_t)(bp[2 * i]     - lo_pad);
        uint16_t hi = (uint16_t)(bp[2 * i + 1] + hi_pad);
        if (rpm > lo && rpm < hi) return true;
    }
    return false;
}

static void pwm_output_compute(pwm_runtime_t *rt, const pwm_flash_t *flash)
{
    const uint16_t rpm = rt->inputs.rpm;

    /* A. Base duty from submap pair — slot_x=pwm_A(rpm),
     *    slot_y=pwm_B(active_request) [0x0046 = PI output feed-forward] */
    s_eval(&pwm_submap_descrs[PWM_SUBMAP_PWM_A], &rt->pwm_slot_x);
    s_eval(&pwm_submap_descrs[PWM_SUBMAP_PWM_B], &rt->pwm_slot_y);
    rt->pwm_duty = (uint16_t)s_combine(
        flash->pwm_y_table, &rt->pwm_slot_x, &rt->pwm_slot_y);

    /* B. Duty status flag (unsigned) */
    rt->pwm_status_flag = (rt->pwm_duty < 0x29u)  ? 1u
                        : (rt->pwm_duty > 0xFD7u) ? 2u : 0u;

    /* C/D. Coarse RPM window — shape_intermediate = +shape_scale if in any */
    rt->shape_scale = flash->shape_scale_src;
    bool coarse = rpm_in_any_window(rpm, flash->rpm_breakpoints, 0, 0);

    if (coarse) {
        rt->shape_intermediate = rt->shape_scale;
    } else {
        /* E. Detail pass — expanded windows padded by rpm_values[0] */
        uint16_t pad = flash->rpm_values[0];
        bool detail = rpm_in_any_window(rpm, flash->rpm_breakpoints, pad, pad);
        if (!detail && rt->pwm_period < flash->period_max_limit)
            rt->shape_intermediate = (int16_t)(-rt->shape_scale);
        /* else: shape_intermediate unchanged */
    }

    /* F. Period adjust + unsigned clamp to [min, max] */
    rt->pwm_period = (uint16_t)(rt->pwm_period - (uint16_t)rt->shape_intermediate);
    if (rt->pwm_period > flash->period_max_limit) rt->pwm_period = flash->period_max_limit;
    if (rt->pwm_period < flash->period_min_limit) rt->pwm_period = flash->period_min_limit;

    /* G. Shape refinement (signed clamp to [0, 0x199]) — shape_eval(supply_voltage) */
    s_eval(&pwm_submap_descrs[PWM_SUBMAP_SHAPE_EVAL], &rt->pwm_slot_x);
    rt->shape_output = s_refine(flash->shape_y_table, &rt->pwm_slot_x);
    if (rt->shape_output < 0)          rt->shape_output = 0;
    if (rt->shape_output > (int16_t)0x199) rt->shape_output = (int16_t)0x199;

    /* H. Duty refinement — all UNSIGNED (SHR, MULU, DIVU) */
    uint16_t range = (uint16_t)((uint16_t)(flash->period_max_limit
                                         - flash->period_min_limit) >> 8);
    rt->shape_scale = (int16_t)range;
    uint16_t delta = (uint16_t)((uint16_t)(flash->period_max_limit
                                         - rt->pwm_period) >> 8);
    uint32_t prod = MULU_U16(delta, (uint16_t)rt->shape_output);
    uint16_t trunc = (uint16_t)(prod & 0xFFFFu);        /* assembly CLR RW1E */
    uint16_t quot  = range ? (uint16_t)(trunc / range) : 0u;
    rt->pwm_duty = (uint16_t)(rt->pwm_duty + quot);

    /* I. Absolute duty clamp (unsigned) */
    if (rt->pwm_duty < rt->pwm_min) rt->pwm_duty = rt->pwm_min;
    if (rt->pwm_duty > rt->pwm_max) rt->pwm_duty = rt->pwm_max;

    /* J. HW register split — critical section in assembly (DI/EI) */
    uint32_t hw = MULU_U16(rt->pwm_period, rt->pwm_duty);
    rt->pwm_on_time  = (uint16_t)(hw / 0xFFFu);
    rt->pwm_off_time = (uint16_t)(rt->pwm_period - rt->pwm_on_time);
}

/* ═════════════════════════════════════════════════════════════════════
 * Orchestrator — main_pwm_control_service (0x8cc9–0x8cf1)
 * + inlined publish_closed_loop_request (0x937d–0x938f)
 * ═════════════════════════════════════════════════════════════════════ */
void pwm_service(pwm_runtime_t *rt)
{
    /* 0. Refresh external inputs via user getters. */
    read_inputs(rt);

    const pwm_calibration_t *cal   = rt->bound_cal;
    const pwm_flash_t       *flash = rt->bound_flash;

    /* 1. Max-error interpolation */
    if (flash->max_error_count > 0)
        rt->max_cl_error = pwm_interp_lookup(flash->max_error_x,
                                             flash->max_error_y,
                                             flash->max_error_count,
                                             (int16_t)rt->inputs.rpm);
    /* 2. Target setpoint */
    setpoint_compute(rt, cal);

    /* 3. Supervisor */
    supervisor_update(rt);

    /* 4. Publish correction + angle estimate */
    s_correction(rt, cal);
    rt->estimated_angle = (int16_t)(rt->inputs.ckp_in + rt->angle_offset);

    /* 5. PI controller */
    pi_controller_update(rt, cal, flash);

    /* 6. PWM duty + HW registers */
    pwm_output_compute(rt, flash);
}

/* ═════════════════════════════════════════════════════════════════════
 * External function impls (ported 1:1 from disassembly)
 * ═════════════════════════════════════════════════════════════════════ */
/* real_eval_submap (disassembled 81db–8236). */
static int16_t s_eval(const pwm_submap_descr_t *descr, pwm_interp_slot_t *slot)
{
    int16_t input_val = (descr && descr->input_ptr) ? *descr->input_ptr : 0;
    uint16_t count = descr ? descr->count : 0u;
    const int16_t *x = descr ? descr->x : NULL;
    if (count == 0u || x == NULL) {
        memset(slot, 0, sizeof *slot);
        return 0;
    }
    slot->row_stride = (int16_t)(count * 2u);
    uint16_t k;
    for (k = 1; k < count; k++) {
        if (input_val >= x[k]) break;
    }
    if (k == 1 && input_val >= x[0]) {
        slot->x_interval = 2; slot->x_offset = 2; slot->y_byte_off = 2;
        return 0;
    }
    if (k >= count) k = (uint16_t)(count - 1u);
    slot->x_interval = (int16_t)(x[k - 1] - x[k]);
    slot->x_offset   = (int16_t)(input_val - x[k]);
    slot->y_byte_off = (int16_t)(k * 2u);
    return 0;
}

/* real_combine_submaps (disassembled 8258–82b4). Bilinear over row-major
 * [B_count][A_count] int16 Y-table. slot_x->row_stride is A-axis byte stride. */
static int16_t s_combine(const int16_t *y_base,
                         const pwm_interp_slot_t *sx,
                         const pwm_interp_slot_t *sy)
{
    if (y_base == NULL || sx->x_interval == 0 || sy->x_interval == 0) return 0;
    int32_t row_off = MUL_S16(sy->y_byte_off, sx->row_stride) / 2;
    const int16_t *yp = (const int16_t *)((const uint8_t *)y_base
                                          + row_off + sx->y_byte_off);
    int16_t y_here = *yp;
    int16_t y_prev = *(const int16_t *)((const uint8_t *)yp - 2);
    int32_t diff_a = MUL_S16((int16_t)(y_prev - y_here), sx->x_offset);
    int16_t rowB = (int16_t)(y_here + (int32_t)(diff_a / (int32_t)sx->x_interval));

    const int16_t *yp_p = (const int16_t *)((const uint8_t *)yp - sx->row_stride);
    int16_t y_here_p = *yp_p;
    int16_t y_prev_p = *(const int16_t *)((const uint8_t *)yp_p - 2);
    int32_t diff_a_p = MUL_S16((int16_t)(y_prev_p - y_here_p), sx->x_offset);
    int16_t rowBp = (int16_t)(y_here_p
                              + (int32_t)(diff_a_p / (int32_t)sx->x_interval));

    int32_t diff_b = MUL_S16((int16_t)(rowBp - rowB), sy->x_offset);
    return (int16_t)(rowB + (int32_t)(diff_b / (int32_t)sy->x_interval));
}

/* compute_closed_loop_correction (disassembled 92f2–9339). */
static void s_correction(pwm_runtime_t *rt, const pwm_calibration_t *cal)
{
    int16_t correction = 0;
    if ((uint8_t)(rt->cl_enable_counter & 0xFF) != 0) {
        int16_t normalizer = (rt->cl_error_delta > 0)
            ? rt->pos_error_normalizer
            : rt->neg_error_normalizer;
        int32_t product = MUL_S16(rt->cl_error_delta,
                                   cal->closed_loop_gain_const);
        correction = (int16_t)((product << 4) / (int32_t)normalizer);
    }
    rt->cl_correction_raw = correction;
    rt->supervisor_state_17e = (int16_t)(rt->supervisor_state_17e + correction);
    rt->angle_offset = shra16(rt->supervisor_state_17e, 4);
}

/* real_refine_submap (disassembled 8237–8257). 1D variant of combine. */
static int16_t s_refine(const int16_t *y_base, const pwm_interp_slot_t *slot)
{
    if (y_base == NULL || slot->x_interval == 0) return 0;
    const int16_t *yp = (const int16_t *)((const uint8_t *)y_base + slot->y_byte_off);
    int16_t y_here = *yp;
    int16_t y_prev = *(const int16_t *)((const uint8_t *)yp - 2);
    int32_t diff = MUL_S16((int16_t)(y_prev - y_here), slot->x_offset);
    return (int16_t)(y_here + (int32_t)(diff / (int32_t)slot->x_interval));
}

/* fast_recovery FUN_70ae (disassembled 70ae–713a). */
static void s_recovery(pwm_runtime_t *rt,
                       const pwm_calibration_t *cal,
                       uint16_t rpm)
{
    uint16_t mf = (uint16_t)rt->mode_flags;
    uint16_t sustained = (uint16_t)(((mf << 2) & mf));
    if (sustained > 0x30u) {
        if ((uint16_t)rt->recovery_counter
                >= (uint16_t)rt->recovery_count_threshold) {
            rt->system_flags_110 |= 0x01u;
            rt->recovery_counter = 0;
            rt->error_persist_counter = cal->error_persist_init_count;
        } else if ((int16_t)rpm > cal->recovery_rpm_threshold
                   && (rt->system_flags_110 & 0x10u) == 0u
                   && (rt->system_flags_110 & 0x20u) == 0u
                   && rt->error_persist_counter == 0) {
            rt->recovery_counter = (int16_t)(rt->recovery_counter + 1);
        }
    } else if ((rt->system_flags_110 & 0x01u) == 0u) {
        rt->recovery_counter = 0;
    }
}

/* ═════════════════════════════════════════════════════════════════════
 * Default calibration (TODO-placeholder values)
 * ═════════════════════════════════════════════════════════════════════ */
const pwm_calibration_t pwm_cal_default = {
    .target_minimum   = 0,
    .y_pair           = { NULL, NULL, NULL },
    .pi_upper_breakpoint       = 107,
    .pi_lower_breakpoint       = (int16_t)0xFF95,
    .pi_center_slope           = 2560,
    .pi_upper_outer_slope      = 5120,
    .pi_lower_outer_slope      = 5120,
    .pi_upper_integrator_gain  = 1024,
    .pi_center_integrator_gain = 1024,
    .pi_lower_integrator_gain  = 2048,
    .setpoint_offset           = 0,    /* CAL+0x52 - CAL+0x54 — populated from ROM */
};

const pwm_flash_t pwm_flash_default = {
    .max_error_x     = {0},
    .max_error_y     = {0},
    .max_error_count = 1,
    .max_error_input_addr    = 0,
    .request_upper_limit     = 0,
    .large_pos_error_thresh  = 0x200,
    .large_neg_error_thresh  = (int16_t)0xFE00,
    .inj_qty_demand_thresh   = 0,
    .rpm_breakpoints = {0},
    .rpm_values      = {0},
    .shape_scale_src = 24,
    .period_max_limit = 0x8000,
    .period_min_limit = 0x6000,
    .pwm_y_table   = NULL,
    .shape_y_table = NULL,
};

/* ROM-extracted pwm_cal_rom, pwm_flash_rom, pwm_submap_descrs[], and all
 * Y-table / submap-x static payloads live in cal_tables_rom.{h,c} — both
 * auto-generated by tools/extract_calibration.py --target compact. Link
 * cal_tables_rom.c alongside pwm.c to get them. */
