@@ -83,10 +83,14 @@ struct arbiter_cond_cache_entry {
8383 * No pointer-to-pointer indirection -- values[] and timestamp are
8484 * passed directly so the compiler can keep them in registers.
8585 */
86+ /* Per-condition hysteresis state bitmask (static — survives across evals). */
87+ static uint32_t hyst_state [CONFIG_ARBITER_MAX_HYSTERESIS_CONDITIONS / 32 + 1 ];
88+
8689ARBITER_ALWAYS_INLINE bool eval_condition (
8790 const struct ARBITER_condition_def * __restrict cond ,
8891 const struct ARBITER_fact_value * __restrict values ,
89- arbiter_index_t vcount , uint32_t snap_ts )
92+ arbiter_index_t vcount , uint32_t snap_ts ,
93+ arbiter_index_t cond_index )
9094{
9195 if (unlikely (cond -> fact_id >= vcount )) {
9296 return false;
@@ -147,6 +151,43 @@ ARBITER_ALWAYS_INLINE bool eval_condition(
147151 case ARBITER_OP_NOT_IN :
148152 return val != cond -> value ;
149153
154+ /* Hysteresis: rising = value, falling = aux_value.
155+ * State persists in a static bitmask across evaluations.
156+ */
157+ case ARBITER_OP_HYSTERESIS : {
158+ const int32_t rising = cond -> value ;
159+ const int32_t falling = cond -> aux_value ;
160+ bool prev_state = false;
161+
162+ if (likely (cond_index <
163+ CONFIG_ARBITER_MAX_HYSTERESIS_CONDITIONS )) {
164+ prev_state = (hyst_state [cond_index / 32 ] >>
165+ (cond_index & 31 )) & 1u ;
166+ }
167+
168+ bool result ;
169+
170+ if (val >= rising ) {
171+ result = true;
172+ } else if (val <= falling ) {
173+ result = false;
174+ } else {
175+ result = prev_state ;
176+ }
177+
178+ if (likely (cond_index <
179+ CONFIG_ARBITER_MAX_HYSTERESIS_CONDITIONS )) {
180+ if (result ) {
181+ hyst_state [cond_index / 32 ] |=
182+ (1u << (cond_index & 31 ));
183+ } else {
184+ hyst_state [cond_index / 32 ] &=
185+ ~(1u << (cond_index & 31 ));
186+ }
187+ }
188+ return result ;
189+ }
190+
150191 default :
151192 return false;
152193 }
@@ -171,15 +212,16 @@ ARBITER_ALWAYS_INLINE bool eval_condition_group(
171212 /* Fast path: single condition -- skip loop entirely */
172213 if (likely (count == 1 )) {
173214 bool r = eval_condition (& conds [start ], values ,
174- vcount , snap_ts );
215+ vcount , snap_ts , start );
175216 return (group == ARBITER_COND_NOT ) ? !r : r ;
176217 }
177218
178219 /* ALL is the overwhelmingly common group type */
179220 if (likely (group == ARBITER_COND_ALL )) {
180221 for (arbiter_index_t i = 0 ; i < count ; i ++ ) {
181222 if (!eval_condition (& conds [start + i ], values ,
182- vcount , snap_ts )) {
223+ vcount , snap_ts ,
224+ start + i )) {
183225 return false;
184226 }
185227 }
@@ -189,15 +231,16 @@ ARBITER_ALWAYS_INLINE bool eval_condition_group(
189231 if (group == ARBITER_COND_ANY ) {
190232 for (arbiter_index_t i = 0 ; i < count ; i ++ ) {
191233 if (eval_condition (& conds [start + i ], values ,
192- vcount , snap_ts )) {
234+ vcount , snap_ts ,
235+ start + i )) {
193236 return true;
194237 }
195238 }
196239 return false;
197240 }
198241
199242 /* ARBITER_COND_NOT: invert single child */
200- return !eval_condition (& conds [start ], values , vcount , snap_ts );
243+ return !eval_condition (& conds [start ], values , vcount , snap_ts , start );
201244}
202245
203246/* ── Expression evaluator ─────────────────────────────────────── */
@@ -209,10 +252,56 @@ ARBITER_ALWAYS_INLINE bool eval_condition_group(
209252 * Switch cases ordered by frequency: ASSIGN and simple arithmetic
210253 * first (PID, Kalman models hit these 80%+ of the time).
211254 */
255+ /**
256+ * Linear interpolation in a lookup table.
257+ * Clamps to table endpoints when input is outside range.
258+ */
259+ ARBITER_ALWAYS_INLINE int32_t table_lookup (
260+ const struct ARBITER_table_def * __restrict tbl ,
261+ int32_t input )
262+ {
263+ if (unlikely (tbl == NULL || tbl -> count == 0 )) {
264+ return 0 ;
265+ }
266+ const uint16_t n = tbl -> count ;
267+ const int32_t * __restrict keys = tbl -> keys ;
268+ const int32_t * __restrict vals = tbl -> values ;
269+
270+ /* Clamp below minimum */
271+ if (input <= keys [0 ]) {
272+ return vals [0 ];
273+ }
274+ /* Clamp above maximum */
275+ if (input >= keys [n - 1 ]) {
276+ return vals [n - 1 ];
277+ }
278+ /* Binary-ish scan for bracket (tables are small, linear is fine) */
279+ for (uint16_t i = 1 ; i < n ; i ++ ) {
280+ if (input <= keys [i ]) {
281+ /* Linear interpolation between [i-1] and [i] */
282+ int32_t k0 = keys [i - 1 ];
283+ int32_t k1 = keys [i ];
284+ int32_t v0 = vals [i - 1 ];
285+ int32_t v1 = vals [i ];
286+ int32_t dk = k1 - k0 ;
287+
288+ if (dk == 0 ) {
289+ return v0 ;
290+ }
291+ /* lerp: v0 + (v1-v0)*(input-k0)/(k1-k0) */
292+ int64_t num = (int64_t )(v1 - v0 ) *
293+ (int64_t )(input - k0 );
294+ return v0 + (int32_t )(num / dk );
295+ }
296+ }
297+ return vals [n - 1 ];
298+ }
299+
212300ARBITER_ALWAYS_INLINE void eval_expression (
213301 const struct ARBITER_expr_def * __restrict expr ,
214302 struct ARBITER_fact_value * __restrict values ,
215- arbiter_index_t vcount )
303+ arbiter_index_t vcount ,
304+ const struct ARBITER_model * __restrict model )
216305{
217306 const arbiter_index_t tid = expr -> target_fact_id ;
218307
@@ -295,6 +384,19 @@ ARBITER_ALWAYS_INLINE void eval_expression(
295384 case ARBITER_EXPR_SHIFT_L :
296385 result = left << (right & 31 );
297386 break ;
387+ case ARBITER_EXPR_LOOKUP : {
388+ /* scale field stores the table index */
389+ const uint16_t tbl_idx = (uint16_t )expr -> scale ;
390+
391+ if (likely (model -> tables != NULL &&
392+ tbl_idx < model -> table_count )) {
393+ result = table_lookup (
394+ & model -> tables [tbl_idx ], left );
395+ } else {
396+ result = 0 ;
397+ }
398+ break ;
399+ }
298400 default :
299401 return ;
300402 }
@@ -489,11 +591,12 @@ int ARBITER_eval(const struct ARBITER_model *model,
489591 for (arbiter_index_t i = 0 ; i < ec ; i ++ ) {
490592 const arbiter_index_t ei = es + i ;
491593
492- if (likely (ei < expr_count )) {
493- eval_expression (
494- & exprs [ei ],
495- values , vcount );
496- }
594+ if (likely (ei < expr_count )) {
595+ eval_expression (
596+ & exprs [ei ],
597+ values , vcount ,
598+ model );
599+ }
497600 }
498601 ops += ec ;
499602 }
0 commit comments