/**
 * @file phi.h
 * @brief T06215 injection-angle algorithm — compact public interface.
 *
 * AUTO-GENERATED by tools/extract_t06215_cal.py
 * Source ROM: rom_eeprom_dump_0000-9FFF_424026.bin
 * Bases:      RWA4 = 0x9BD8, RWC6 = 0x7E56
 *
 * DO NOT EDIT -- regenerate with:
 *   python tools/extract_t06215_cal.py --target compact
 *
 * Companion to phi.c (single-translation-unit port of the per-function
 * verbose tree under variants/T06215/src/) and the auto-generated
 * phi_cal_tables.c. Callers see only this header; embedded
 * `runtime_state_t` / `calibration_t` fields are IMPLEMENTATION DETAIL
 * — touch only the outputs struct and the getter vtable.
 *
 * Lifecycle:
 *
 *     phi_input_getters_t getters = { .get_rpm = ..., ... };
 *     phi_state_t state;
 *     phi_cal_t   cal = phi_t06215_cal;   // copy from auto-extracted master
 *     phi_outputs_t out;
 *
 *     phi_init(&state, &cal, &getters);     // boot-once
 *     for each tick:
 *         phi_service(&state, &cal, &out);
 *         use(out.target_inj_angle, ...);
 *
 * The producer chain runs in this fixed order each tick (see
 * variants/T06211/docs/open-questions.md §4 / §5 — fused scheduler
 * `FUN_698C` enclosing `0x6a3f LCALL FUN_7453` is authoritative):
 *
 *   1. CAN / external inputs pulled via the getter vtable.
 *   2. compute_tein_overtemp_guard       (FUN_5ca1)  -> temperature, tein_overtemp_guard
 *   3. compute_accel_comp_gain    (FUN_72b0)  -> accel_comp_gain @ *(0x0164)
 *   4. compute_accel_comp_offset  (FUN_732d)  -> RW3C, REC.0
 *   5. compute_gate_0220          (orphan @ 0x77ff) -> gate_0220 hysteresis
 *   6. compute_tein_valve_fault_guard (FUN_62a2) -> RW7A
 *   7. compute_target_injection_angle (FUN_7453) -> RW48 / RW5A
 *
 * The orphan temperature-kick chain (compute_temp_comp_factor → orphan
 * FUN_5D58 angle_kick + `RW52 += RW3E` fold-in @ 0x7A3E) is ported in
 * variants/T06215/src/ and wired into this compact phi_service.
 * RW9E (the IIR state high word consumed by compute_temp_comp_factor)
 * is produced internally by compute_temp_phi_comp — see
 * compute_temp_phi_comp.h for the 1 kHz hook the host must drive.
 * See variants/T06211/docs/open-questions.md §9.
 */
#ifndef PHI_T06215_H
#define PHI_T06215_H

#include <stdint.h>
#include <stddef.h>

/* ─── Internal runtime / calibration types ───────────────────────────
 * Inlined verbatim from variants/T06215/src/injection_angle_state.h.
 * Treat all fields as IMPLEMENTATION DETAIL — see the lifecycle
 * comment above; the supported surface is `phi_outputs_t` and the
 * getter vtable. */

typedef struct {
    int16_t stride_bytes;    /* +0: 2 * stride_items                  */
    int16_t delta;           /* +2: axis[idx-1] - axis[idx]            */
    int16_t fraction;        /* +4: input_var - axis[idx]              */
    int16_t index_bytes;     /* +6: byte offset into the axis array    */
} submap_scratch_t;

typedef struct {
    int16_t  runtime_slot;           /* +0: written by eval_submap_to_scratch */
    int16_t *input_var;              /* +2: pointer to the RW input variable */
    int16_t  stride_items;           /* +4: breakpoints in the axis */
    const int16_t *axis;             /* +6: axis breakpoint table (descending) */
} submap_descriptor_t;

typedef struct {
    /* ---- Per-tick inputs (produced elsewhere / by earlier producers) ---- */
    uint16_t rpm;                   /* RW40 @ 0x0040 — engine RPM (external, HSI.0 ISR) */
    int16_t  angle_dec_cmd;         /* RW42 @ 0x0042 — angle-dec command (external input) */
    int16_t  inj_qty_demand;        /* RW44 @ 0x0044 — injection-quantity command. EXTERNAL CAN input. */
    int16_t  accel_comp_offset;     /* RW3C @ 0x003C — internally produced by compute_accel_comp_offset (FUN_732d). */
    int16_t  tein_valve_fault_guard;   /* RW7A @ 0x007A — internally produced by compute_tein_valve_fault_guard (FUN_62a2). */

    /* ---- Reset/run flag byte (REC) ---- */
    uint8_t  rec;                   /* REC — bit 0 tested by FUN_7453 at 0x745c. */

    /* ---- Persistent accumulator ---- */
    int16_t  angle_accumulator;     /* RW52 @ 0x0052 — class-B state, updated in-place by FUN_7453. */

    /* ---- Per-tick angle-kick fold-in (orphan FUN_5D58 / 0x7A3E) ---- */
    int16_t  angle_kick_2d;         /* RW3E @ 0x003E — produced by compute_angle_kick_2d (port of orphan
                                     * FUN_5D58 @ 0x5D58). Folded into angle_accumulator at 0x7A3E
                                     * before FUN_7453. */

    /* ---- Outputs of the chain ---- */
    int16_t  target_inj_angle;      /* RW48 @ 0x0048 — SOLE writer is FUN_7453 @ 0x749f */
    int16_t  target_eoi;            /* RW5A @ 0x005A — written @ 0x74a2 as target_inj_angle + phi1. */

    /* ---- Absolute-address RAM touched by FUN_7453 ---- */
    int16_t  scratch_0200;          /* *(0x0200) — event accumulator. */
    int16_t  tein_overtemp_guard;          /* *(0x011a) — seed addend, produced by FUN_5ca1 @ 0x5d3e. */
    int16_t  dphi;                  /* *(0x014c) — angle delta. */

    /* ---- Inputs to FUN_732d (compute_accel_comp_offset) ---- */
    int16_t  rpm_baseline;          /* *(0x0138) — slow RPM baseline. */
    int16_t  accel_comp_gain;       /* *(0x0164) — Δ-rpm × gain multiplier. */
    uint8_t  reset_gate_0226;       /* *(0x0226) — accel_comp_offset enable gate. */
    uint8_t  gate_0220;             /* *(0x0220) — RPM-hysteresis gate. */

    /* ---- Inputs/state for FUN_5ca1 (compute_tein_overtemp_guard) ---- */
    int16_t  temperature;           /* *(0x0146) — engine temperature (external input boundary). */

    /* ---- Per-tick temperature-compensation factor (orphan FUN_5DAB / 0x5DE2) ----
     * The R0CB-gated `ADD RW20, RWCE` branch at 0x5DC4–0x5DCB is
     * intentionally not modeled — both slots are observed zero. */
    int16_t  temp_comp_factor;      /* *(0x02F4) — produced by compute_temp_comp_factor (port of orphan
                                     * FUN_5DAB @ 0x5DAB). Consumed by compute_angle_kick_2d at 0x5D8D. */
    int16_t  rw9a;                  /* RW9A @ 0x009A — published by compute_temp_comp_factor at 0x5DBD. */
    int16_t  rw9c;                  /* RW9C @ 0x009C — low word of the {rw9e:rw9c} signed IIR-filter
                                     * state pair updated by compute_temp_phi_comp (port of FUN_6b4e
                                     * @ 0x6b4e). Boot-zero (FUN_6ba3 @ 0x6bbf clears it). */
    int16_t  rw9e;                  /* RW9E @ 0x009E — high word of the {rw9e:rw9c} signed IIR-filter
                                     * state pair, sole writer is phi_tick_1khz at the 10 Hz
                                     * derived rate (port of FUN_6b2a @ 0x6b2a). Consumed by
                                     * compute_temp_comp_factor at 0x6AFC. */
    uint8_t  temp_phi_comp_r94;     /* R94 byte register slot driven by Timer_1khz at 0x79e2 / 0x7a26.
                                     * Prescaler that gates the IIR step inside phi_tick_1khz: counts
                                     * down on every 1 ms call, IIR fires when it reaches 0 and reloads
                                     * to DAT_6061 = 0x64 = 100 → effective 10 Hz cadence. Seeded to
                                     * 100 by phi_init. */

    /* ---- 3-D angle-accumulator submap scratches (FUN_722e @ 0x722e) ---- */
    submap_scratch_t scratch_rpm;     /* *(0x0184) — RPM axis scratch. */
    submap_scratch_t scratch_demand;  /* *(0x0194) — inj_qty_demand axis scratch. */
    submap_scratch_t scratch_dec_cmd; /* *(0x018c) — angle_dec_cmd axis scratch. */
    int16_t scratch_0158;             /* *(0x0158) — debug mirror of angle_accumulator result. */

    /* ---- State for FUN_62a2 (compute_tein_valve_fault_guard, RW7A producer) ---- */
    uint16_t rwc2;                  /* RWC2 — timing-baseline scratch compared against dat_604c. */
    uint8_t  rf5_gate;              /* RF5 byte — peripheral input gate. */
    uint8_t  scratch_0221;          /* *(0x0221) — FUN_62a2 early-out gate (baked to 1 in phi_init). */
    uint8_t  scratch_010e;          /* *(0x010e) — Phase-1 counter byte. */
    uint8_t  scratch_010f;          /* *(0x010f) — Phase-2 state byte. */
    uint8_t  scratch_0103;          /* *(0x0103) — Phase-1 counter firing threshold. */
    uint8_t  scratch_0108;          /* *(0x0108) — Phase-2 init/re-load value. */
} runtime_state_t;

typedef struct {
    /* ---- RWA4-relative scalar calibration ---- */
    int16_t tein_nominal;                 /* CAL[0x1e] — seed base added to tein_valve_fault_guard at 0x747b. */
    int16_t cal_48;                 /* CAL[0x48] — value loaded into RW7A when FUN_62a2 fires (= 0x04B0). */
    int16_t phi0;                   /* CAL[0x4c] — base/initial angle. */
    uint8_t cal_byte_9c;            /* CAL_byte[0x9c] — counter increment in FUN_62a2 (= 0x03). */
    int16_t cal_54;                 /* CAL[0x54] — UPPER clamp on target_inj_angle at 0x7493. */
    int16_t cal_74;                 /* CAL[0x74] — LOWER RPM threshold for gate_0220 hysteresis. */
    int16_t cal_76;                 /* CAL[0x76] — gate_0220 hysteresis width. */
    int16_t cal_78;                 /* CAL[0x78] — UPPER clamp on accel_comp_offset. */
    int16_t cal_7a;                 /* CAL[0x7a] — LOWER clamp on accel_comp_offset. */
    int16_t cal_7e;                 /* CAL[0x7e] — temperature reference subtrahend in compute_temp_comp_factor (FUN_5DAB 0x5DD1). */
    int16_t cal_temp_comp_switch_dynamic;  /* CAL[0x80] — boot value of *(0x02F6) (German label TK_AT_W). */
    int16_t cal_82;                 /* CAL[0x82] — IIR input-gain `b` (Q16 unsigned) used by compute_temp_phi_comp
                                     * at the FUN_6b4e MULU @ 0x6B66. Read via (uint16_t) cast. */
    int16_t cal_84;                 /* CAL[0x84] — IIR pole `a` (Q16 unsigned) used by compute_temp_phi_comp
                                     * at the FUN_6b4e MULUs @ 0x6B4E / 0x6B57. Read via (uint16_t) cast.
                                     * On this ROM cal_82 + (uint16_t)cal_84 == 0x10000 (unity DC). */
    int16_t cal_temp_comp_switch_complete; /* CAL[0x86] — boot value of *(0x02F2) (German label F_TK_TE_W). */
    int16_t cal_92;                 /* CAL[0x92] — temperature offset subtracted in FUN_5ca1. */
    int16_t cal_94;                 /* CAL[0x94] — multiplier in FUN_5ca1. */
    int16_t cal_96;                 /* CAL[0x96] — UPPER clamp on RW1C in FUN_5ca1 (unsigned). */
    int16_t cal_98;                 /* CAL[0x98] — LOWER rpm threshold for tein_overtemp_guard. */
    int16_t cal_9a;                 /* CAL[0x9a] — UPPER rpm threshold for tein_overtemp_guard. */

    /* ---- accel_comp_gain (FUN_72b0) submaps & tables (RWA4-relative) ---- */
    submap_descriptor_t desc_accel_rpm;    /* CAL+0x58 — RPM axis */
    submap_descriptor_t desc_accel_demand; /* CAL+0x60 — inj_qty_demand axis */
    submap_descriptor_t desc_accel_temp;   /* CAL+0x68 — temperature axis */
    const int16_t *accel_combine_table;    /* CAL+0x70 — 2D RPM×demand combine table */
    const int16_t *accel_refine_table;     /* CAL+0x72 — 1D temperature refine table */

    /* ---- ROM constants ---- */
    int16_t dat_604c;               /* *(0x604c) = 0x0444 — RWC2 timing threshold in FUN_62a2. */
    int16_t cal_byte_402;           /* *(0x0402) — boot sign-extended byte. Subtracted in FUN_5DAB at 0x5DD6. */

    /* ---- RWC6-relative: 3-D angle-accumulator submap (FUN_722e) ---- */
    submap_descriptor_t desc_rpm;      /* RWC6+0x00 — RPM axis */
    submap_descriptor_t desc_demand;   /* RWC6+0x08 — inj_qty_demand axis */
    submap_descriptor_t desc_dec_cmd;  /* RWC6+0x10 — angle_dec_cmd axis */
    const int16_t      *data_table_3d; /* word @ RWC6+0x1a — 3-D table data */

    /* ---- RWC6-relative: 2-D angle-kick submap + scalar (FUN_5D58) ---- */
    const int16_t *data_table_2d_kick; /* word @ RWC6+0x32 — 2-D kick table (RPM × demand). */
    int16_t cal_rwc6_34;               /* CAL[RWC6+0x34] — demand-weight multiplier in FUN_5D58. */
} calibration_t;

/* ─── Outputs surfaced after every phi_service ───────────────────── */
typedef struct {
    int16_t  target_inj_angle;     /* RW48 — primary output (FUN_7453 @ 0x749f) */
    int16_t  target_eoi;           /* RW5A — target_inj_angle + phi1; phi1 = phi0 + dphi */
    int16_t  angle_accumulator;    /* RW52 — class-B accumulator state */
    int16_t  accel_comp_offset;    /* RW3C — FUN_732d output */
    int16_t  accel_comp_gain;      /* *(0x0164) — FUN_72b0 output */
    int16_t  tein_valve_fault_guard;  /* RW7A — FUN_62a2 output */
    int16_t  tein_overtemp_guard;         /* *(0x011a) — FUN_5ca1 seed addend */
    int16_t  temperature;          /* *(0x0146) — FUN_5ca1 filtered temperature */
    int16_t  temp_comp_factor;     /* *(0x02F4) — compute_temp_comp_factor (FUN_5DAB) output */
    int16_t  angle_kick_2d;        /* RW3E — compute_angle_kick_2d (FUN_5D58) output (folded into angle_accumulator) */
    uint8_t  gate_0220;            /* *(0x0220) — RPM hysteresis gate */
    uint8_t  flags;                /* bit 0: target_inj_angle invalid (sign bit set);
                                    * bit 1: REC.0 was set entering FUN_7453;
                                    * bit 2: gate_0220 active */
} phi_outputs_t;

/* ─── Getter vtable ────────────────────────────────────────────────── */
typedef struct {
    int16_t  (*get_inj_qty_demand)(void);    /* RW44 */
    int16_t  (*get_angle_dec_cmd)(void);     /* RW42 */
    uint16_t (*get_rpm)(void);               /* RW40 */
    int16_t  (*get_temperature)(void);       /* *(0x0146) */
    int16_t  (*get_rpm_baseline)(void);      /* *(0x0138) */
    uint16_t (*get_rwc2)(void);              /* RWC2 */
    uint8_t  (*get_reset_gate_0226)(void);   /* *(0x0226) */
    int16_t  (*get_dphi)(void);              /* *(0x014c) */
    uint8_t  (*get_scratch_0103)(void);      /* *(0x0103) — boot-only */
    uint8_t  (*get_scratch_0108)(void);      /* *(0x0108) — boot-only */
} phi_input_getters_t;

/* ─── Lifecycle types ────────────────────────────────────────────────── */
typedef struct {
    runtime_state_t            rt;
    const phi_input_getters_t *getters;
} phi_state_t;

typedef calibration_t phi_cal_t;

/* ─── Public API ───────────────────────────────────────────────────── */

void phi_init(phi_state_t                  *state,
              const phi_cal_t              *cal,
              const phi_input_getters_t    *getters);

void phi_service(phi_state_t        *state,
                 const phi_cal_t    *cal,
                 phi_outputs_t      *out);

/** 1 kHz hook. Host invokes once every 1 ms from its millisecond timer.
 *  Internally decrements `state->rt.temp_phi_comp_r94`; on R94 == 0 runs
 *  the IIR step (port of FUN_6b2a + FUN_6b4e @ 0x6b2a / 0x6b4e) and
 *  reloads R94 to 100 (DAT_6061). The IIR updates `{rt->rw9e, rt->rw9c}`
 *  in place using `rt->rw9a` as the input and `cal->cal_82` / `cal->cal_84`
 *  as coefficients. Reentrant per phi_state_t — the prescaler lives in
 *  runtime_state_t. */
void phi_tick_1khz(phi_state_t *state, const phi_cal_t *cal);

size_t phi_state_size(void);
size_t phi_cal_size(void);

/* ─── Auto-generated cal export (defined in phi_cal_tables.c) ──────── */

extern calibration_t phi_t06215_cal;

void phi_t06215_bind_inputs(runtime_state_t *rt, calibration_t *cal);

#endif /* PHI_T06215_H */
