1use crate::foundation::{
8 is_truthy, CompareOp, FlagPriority, KnotId, PortSlot, Signal, SignalDomain, ONE, ZERO,
9};
10
11use crate::runtime_impl::bind::{Runtime, SenseSeed};
12use crate::runtime_impl::kind_tag::KindTag;
13
14#[allow(non_snake_case)]
15const fn PortSlot(value: u8) -> PortSlot {
16 PortSlot::new(value)
17}
18
19impl Runtime {
20 pub fn loom(&mut self) {
25 for &idx in &self.clear_port_idx {
26 debug_assert!(idx < self.port_vals.len());
27 self.port_vals[idx] = ZERO;
28 }
29
30 let seed_n = self.sense_seeds.len();
31 for si in 0..seed_n {
32 match self.sense_seeds[si] {
33 SenseSeed::Constant { kid, value } => {
34 self.set_port_hot(kid, PortSlot::new(0), value);
35 }
36 SenseSeed::SignalIn { kid } => {
37 let v = self.sense_values[usize::from(kid)];
38 self.set_port_hot(kid, PortSlot::new(0), v);
39 }
40 SenseSeed::OnStart { kid } => {
41 let ki = usize::from(kid);
42 let v = if !self.on_start_done[ki] {
43 self.on_start_done[ki] = true;
44 ONE
45 } else {
46 ZERO
47 };
48 self.set_port_hot(kid, PortSlot::new(0), v);
49 }
50 }
51 }
52
53 let topo_len = self.topo.len();
54 for ti in 0..topo_len {
55 let kid = self.topo[ti];
56 let ki = usize::from(kid);
57 if self.kind_tags[ki].is_sense() {
58 continue;
59 }
60 self.gather_inputs(kid);
61 self.eval_knot(kid);
62 }
63 self.phase = 2;
65 }
66
67 fn gather_inputs(&mut self, kid: KnotId) {
72 let ki = usize::from(kid);
73 let start = self.inbound_off[ki] as usize;
74 let end = self.inbound_off[ki + 1] as usize;
75 let n = end - start;
76 if n == 0 {
77 return;
78 }
79 if n == 1 {
80 let (f, fs, ts) = self.inbound_edges[start];
81 let v = self.get_port_hot(f, fs);
82 self.set_port_hot(kid, ts, v);
83 return;
84 }
85 if n == 2 {
86 let (f0, fs0, ts0) = self.inbound_edges[start];
87 let (f1, fs1, ts1) = self.inbound_edges[start + 1];
88 let v0 = self.get_port_hot(f0, fs0);
89 let v1 = self.get_port_hot(f1, fs1);
90 self.set_port_hot(kid, ts0, v0);
91 self.set_port_hot(kid, ts1, v1);
92 return;
93 }
94 let mut tmp: [(PortSlot, Signal); 8] = [(PortSlot::new(0), ZERO); 8];
95 let n = n.min(8);
96 for (i, &(f, fs, ts)) in self.inbound_edges[start..start + n].iter().enumerate() {
97 tmp[i] = (ts, self.get_port_hot(f, fs));
98 }
99 for &(ts, v) in tmp.iter().take(n) {
100 self.set_port_hot(kid, ts, v);
101 }
102 }
103
104 fn eval_knot(&mut self, kid: KnotId) {
105 let ki = usize::from(kid);
106 let tag = self.kind_tags[ki];
107 match tag {
108 KindTag::Sense => {}
109 KindTag::Not => {
110 let i = self.get_port_hot(kid, PortSlot::new(0));
111 let o = if is_truthy(i) { ZERO } else { ONE };
112 self.set_port_hot(kid, PortSlot::new(1), o);
113 }
114 KindTag::And { arity } => {
115 let mut ok = true;
116 for s in 0..arity {
117 if !is_truthy(self.get_port_hot(kid, PortSlot(s))) {
118 ok = false;
119 break;
120 }
121 }
122 self.set_port_hot(kid, PortSlot(arity), if ok { ONE } else { ZERO });
123 }
124 KindTag::Or { arity } => {
125 let mut ok = false;
126 for s in 0..arity {
127 if is_truthy(self.get_port_hot(kid, PortSlot(s))) {
128 ok = true;
129 break;
130 }
131 }
132 self.set_port_hot(kid, PortSlot(arity), if ok { ONE } else { ZERO });
133 }
134 KindTag::RisingFromZero => {
135 let i = self.get_port_hot(kid, PortSlot::new(0));
136 let prev = self.prev_in[ki];
137 let o = if !is_truthy(prev) && is_truthy(i) {
138 ONE
139 } else {
140 ZERO
141 };
142 self.prev_in[ki] = i;
143 self.set_port_hot(kid, PortSlot::new(1), o);
144 }
145 KindTag::Compare { op, rhs_const } => {
146 let lhs = self.get_port_hot(kid, PortSlot::new(0));
147 let rhs = match rhs_const {
148 Some(c) => c,
149 None => self.get_port_hot(kid, PortSlot::new(1)),
150 };
151 let o = if compare(op, lhs, rhs) { ONE } else { ZERO };
152 self.set_port_hot(kid, PortSlot::new(2), o);
153 }
154 KindTag::Flag {
155 priority,
156 enable_toggle,
157 } => {
158 let set_l = self.get_port_hot(kid, PortSlot::new(0));
159 let reset_l = self.get_port_hot(kid, PortSlot::new(1));
160 let toggle_l = self.get_port_hot(kid, PortSlot::new(2));
161 let set = is_truthy(set_l);
162 let reset = is_truthy(reset_l);
163 let toggle = enable_toggle && !is_truthy(self.prev_in[ki]) && is_truthy(toggle_l);
164 let mut st = self.flag[ki];
165 match priority {
166 FlagPriority::ResetWins => {
167 if reset {
168 st = false;
169 } else if set {
170 st = true;
171 } else if toggle {
172 st = !st;
173 }
174 }
175 FlagPriority::SetWins => {
176 if set {
177 st = true;
178 } else if reset {
179 st = false;
180 } else if toggle {
181 st = !st;
182 }
183 }
184 }
185 self.prev_in[ki] = toggle_l;
186 self.flag[ki] = st;
187 self.set_port_hot(kid, PortSlot::new(3), if st { ONE } else { ZERO });
188 }
189 KindTag::Counter => {
190 let inc = self.get_port_hot(kid, PortSlot::new(0));
191 let dec = self.get_port_hot(kid, PortSlot::new(1));
192 let reset = self.get_port_hot(kid, PortSlot::new(2));
193 if is_truthy(reset) {
194 self.counter[ki] = 0;
195 }
196 if !is_truthy(self.prev_in[ki]) && is_truthy(inc) {
197 self.counter[ki] = self.counter[ki].saturating_add(1);
198 }
199 if !is_truthy(self.prev_dec[ki]) && is_truthy(dec) {
200 self.counter[ki] = self.counter[ki].saturating_sub(1);
201 }
202 self.prev_in[ki] = inc;
203 self.prev_dec[ki] = dec;
204 self.set_port_hot(kid, PortSlot::new(3), crate::from_count(self.counter[ki]));
205 }
206 KindTag::TimerPulseHold { ticks } => {
207 let start = self.get_port_hot(kid, PortSlot::new(0));
208 let prev = self.prev_in[ki];
209 if !is_truthy(prev) && is_truthy(start) {
210 self.timer_left[ki] = ticks;
211 }
212 self.prev_in[ki] = start;
213 if self.timer_left[ki] > 0 {
214 self.set_port_hot(kid, PortSlot::new(1), ONE);
215 self.timer_left[ki] -= 1;
216 } else {
217 self.set_port_hot(kid, PortSlot::new(1), ZERO);
218 }
219 }
220 KindTag::TimerFedCountdown { ticks } => {
221 let feed = self.get_port_hot(kid, PortSlot::new(0));
222 let prev = self.prev_in[ki];
223 if is_truthy(feed) {
224 if !is_truthy(prev) {
225 self.timer_left[ki] = ticks;
226 }
227 if self.timer_left[ki] > 0 {
228 self.timer_left[ki] -= 1;
229 }
230 let active = if self.timer_left[ki] == 0 { ONE } else { ZERO };
231 self.set_port_hot(kid, PortSlot::new(1), active);
232 } else {
233 self.timer_left[ki] = 0;
234 self.set_port_hot(kid, PortSlot::new(1), ZERO);
235 }
236 self.prev_in[ki] = feed;
237 }
238 KindTag::Delay { ticks } => {
239 let i = self.get_port_hot(kid, PortSlot::new(0));
240 if ticks == 0 {
241 self.set_port_hot(kid, PortSlot::new(1), i);
242 } else {
243 let len = self.delay_len[ki] as usize;
244 let off = self.delay_off[ki] as usize;
245 let head = self.delay_head[ki] as usize;
246 let o = self.delay_buf[off + head];
247 self.delay_buf[off + head] = i;
248 let next = head + 1;
249 self.delay_head[ki] = if len.is_power_of_two() {
250 (next & (len - 1)) as u16
251 } else if next >= len {
252 0
253 } else {
254 next as u16
255 };
256 self.set_port_hot(kid, PortSlot::new(1), o);
257 }
258 }
259 KindTag::CalcAdd { domain } => {
260 let a = self.get_port_hot(kid, PortSlot::new(0));
261 let b = self.get_port_hot(kid, PortSlot::new(1));
262 let out = match domain {
263 SignalDomain::Count => count_add(a, b),
264 _ => crate::foundation::signal_ops::sat_add(a, b),
265 };
266 self.set_port_hot(kid, PortSlot::new(2), out);
267 }
268 KindTag::CalcSub { domain } => {
269 let a = self.get_port_hot(kid, PortSlot::new(0));
270 let b = self.get_port_hot(kid, PortSlot::new(1));
271 let out = match domain {
272 SignalDomain::Count => count_sub(a, b),
273 _ => crate::foundation::signal_ops::sat_sub(a, b),
274 };
275 self.set_port_hot(kid, PortSlot::new(2), out);
276 }
277 KindTag::CalcMulLevel => {
278 let a = self.get_port_hot(kid, PortSlot::new(0));
279 let b = self.get_port_hot(kid, PortSlot::new(1));
280 self.set_port_hot(
281 kid,
282 PortSlot::new(2),
283 crate::foundation::signal_ops::mul(a, b),
284 );
285 }
286 KindTag::CalcMulCount => {
287 let a = self.get_port_hot(kid, PortSlot::new(0));
288 let b = self.get_port_hot(kid, PortSlot::new(1));
289 self.set_port_hot(kid, PortSlot::new(2), count_mul(a, b));
290 }
291 KindTag::CalcDivLevel => {
292 let a = self.get_port_hot(kid, PortSlot::new(0));
293 let b = self.get_port_hot(kid, PortSlot::new(1));
294 self.set_port_hot(
295 kid,
296 PortSlot::new(2),
297 crate::foundation::signal_ops::div(a, b),
298 );
299 }
300 KindTag::CalcDivCount => {
301 let a = self.get_port_hot(kid, PortSlot::new(0));
302 let b = self.get_port_hot(kid, PortSlot::new(1));
303 self.set_port_hot(kid, PortSlot::new(2), count_div(a, b));
304 }
305 KindTag::CalcDivLevelConst { divisor } => {
306 let a = self.get_port_hot(kid, PortSlot::new(0));
307 self.set_port_hot(
308 kid,
309 PortSlot::new(2),
310 crate::foundation::signal_ops::div(a, divisor),
311 );
312 }
313 KindTag::CalcDivCountConst { divisor } => {
314 let a = self.get_port_hot(kid, PortSlot::new(0));
315 self.set_port_hot(kid, PortSlot::new(2), count_div(a, divisor));
316 }
317 KindTag::Abs { domain } => {
318 let i = self.get_port_hot(kid, PortSlot::new(0));
319 let o = match domain {
320 SignalDomain::Count => count_abs(i),
321 _ => level_abs(i),
322 };
323 self.set_port_hot(kid, PortSlot::new(1), o);
324 }
325 KindTag::Neg { domain } => {
326 let i = self.get_port_hot(kid, PortSlot::new(0));
327 let o = match domain {
328 SignalDomain::Count => count_neg(i),
329 _ => level_neg(i),
330 };
331 self.set_port_hot(kid, PortSlot::new(1), o);
332 }
333 KindTag::Map {
334 domain,
335 #[cfg(feature = "signal-f32")]
336 degenerate,
337 #[cfg(feature = "signal-f32")]
338 in_min,
339 #[cfg(feature = "signal-f32")]
340 out_min,
341 #[cfg(feature = "signal-f32")]
342 inv_in_span,
343 #[cfg(feature = "signal-f32")]
344 out_span,
345 #[cfg(feature = "signal-i32")]
346 plan,
347 } => {
348 let i = self.get_port_hot(kid, PortSlot::new(0));
349 #[cfg(feature = "signal-f32")]
350 let o = map_linear_fast(
351 i,
352 domain,
353 degenerate,
354 in_min,
355 out_min,
356 inv_in_span,
357 out_span,
358 );
359 #[cfg(feature = "signal-i32")]
360 let o = {
361 let _ = domain;
362 plan.map(i)
363 };
364 self.set_port_hot(kid, PortSlot::new(1), o);
365 }
366 KindTag::Select => {
367 let sel = self.get_port_hot(kid, PortSlot::new(0));
368 let a = self.get_port_hot(kid, PortSlot::new(1));
369 let b = self.get_port_hot(kid, PortSlot::new(2));
370 let o = if is_truthy(sel) { b } else { a };
371 self.set_port_hot(kid, PortSlot::new(3), o);
372 }
373 KindTag::Digitize {
374 domain,
375 degenerate,
376 in_min,
377 out_min,
378 bin_scale,
379 out_scale,
380 last_f,
381 steps,
382 last,
383 #[cfg(feature = "signal-i32")]
384 den,
385 #[cfg(feature = "signal-i32")]
386 out_span,
387 } => {
388 let i = self.get_port_hot(kid, PortSlot::new(0));
389 let o = digitize_fast(
390 i,
391 domain,
392 degenerate,
393 in_min,
394 out_min,
395 bin_scale,
396 out_scale,
397 last_f,
398 steps,
399 last,
400 #[cfg(feature = "signal-i32")]
401 den,
402 #[cfg(feature = "signal-i32")]
403 out_span,
404 );
405 self.set_port_hot(kid, PortSlot::new(1), o);
406 }
407 KindTag::Threshold {
408 high,
409 low,
410 use_hysteresis,
411 } => {
412 let x = self.get_port_hot(kid, PortSlot::new(0));
413 let prev = self.flag[ki];
414 let mut latched = prev;
415 if use_hysteresis {
416 if latched {
417 if x < low {
418 latched = false;
419 }
420 } else if x >= high {
421 latched = true;
422 }
423 } else {
424 latched = x >= high;
425 }
426 self.flag[ki] = latched;
427 self.set_port_hot(kid, PortSlot::new(1), if latched { ONE } else { ZERO });
428 self.set_port_hot(
429 kid,
430 PortSlot::new(2),
431 if !prev && latched { ONE } else { ZERO },
432 );
433 self.set_port_hot(
434 kid,
435 PortSlot::new(3),
436 if prev && !latched { ONE } else { ZERO },
437 );
438 }
439 tag @ (KindTag::RandomLevel {
440 require_gate,
441 min_wired,
442 max_wired,
443 }
444 | KindTag::RandomCount {
445 require_gate,
446 min_wired,
447 max_wired,
448 }) => {
449 let min_v = if min_wired {
451 self.get_port_hot(kid, PortSlot::new(0))
452 } else {
453 ZERO
454 };
455 let max_v = if max_wired {
456 self.get_port_hot(kid, PortSlot::new(1))
457 } else {
458 match tag {
459 KindTag::RandomCount { .. } => crate::foundation::from_count(1),
460 _ => ONE,
461 }
462 };
463 let gate = self.get_port_hot(kid, PortSlot::new(2));
464 let prev = self.prev_in[ki];
465 let rising = !is_truthy(prev) && is_truthy(gate);
466 let sample = if require_gate { rising } else { true };
467 if sample {
468 let u = self.next_rng_u32();
469 let o = match tag {
470 KindTag::RandomCount { .. } => random_count_in_range(u, min_v, max_v),
471 _ => random_level_in_range(u, min_v, max_v),
472 };
473 self.set_port_hot(kid, PortSlot::new(3), o);
474 #[cfg(feature = "signal-f32")]
476 {
477 self.counter[ki] = o.to_bits() as i32;
478 }
479 #[cfg(feature = "signal-i32")]
480 {
481 self.counter[ki] = o;
482 }
483 } else {
484 #[cfg(feature = "signal-f32")]
485 {
486 self.set_port_hot(
487 kid,
488 PortSlot::new(3),
489 f32::from_bits(self.counter[ki] as u32),
490 );
491 }
492 #[cfg(feature = "signal-i32")]
493 {
494 self.set_port_hot(kid, PortSlot::new(3), self.counter[ki]);
495 }
496 }
497 self.prev_in[ki] = gate;
498 }
499 KindTag::SqrtLevel => {
500 let i = self.get_port_hot(kid, PortSlot::new(0));
501 let o = level_sqrt(i);
502 self.set_port_hot(kid, PortSlot::new(1), o);
503 }
504 KindTag::SqrtCount => {
505 let i = self.get_port_hot(kid, PortSlot::new(0));
506 let o = count_sqrt(i);
507 self.set_port_hot(kid, PortSlot::new(1), o);
508 }
509 KindTag::ConvertBoolToLevel | KindTag::ConvertIdentity => {
510 let i = self.get_port_hot(kid, PortSlot::new(0));
511 self.set_port_hot(kid, PortSlot::new(1), i);
512 }
513 KindTag::ConvertBoolToCount => {
514 let i = self.get_port_hot(kid, PortSlot::new(0));
515 let o = crate::foundation::from_count(i32::from(is_truthy(i)));
516 self.set_port_hot(kid, PortSlot::new(1), o);
517 }
518 KindTag::ConvertLevelToBool | KindTag::ConvertCountToBool => {
519 let i = self.get_port_hot(kid, PortSlot::new(0));
520 self.set_port_hot(kid, PortSlot::new(1), if is_truthy(i) { ONE } else { ZERO });
521 }
522 KindTag::ConvertLevelToCount => {
523 let i = self.get_port_hot(kid, PortSlot::new(0));
524 self.set_port_hot(kid, PortSlot::new(1), level_to_count(i));
525 }
526 KindTag::ConvertCountToLevel => {
527 let i = self.get_port_hot(kid, PortSlot::new(0));
528 self.set_port_hot(kid, PortSlot::new(1), count_to_level(i));
529 }
530 KindTag::Xor => {
531 let a = is_truthy(self.get_port_hot(kid, PortSlot::new(0)));
532 let b = is_truthy(self.get_port_hot(kid, PortSlot::new(1)));
533 self.set_port_hot(kid, PortSlot::new(2), if a ^ b { ONE } else { ZERO });
534 }
535 KindTag::FallingToZero => {
536 let i = self.get_port_hot(kid, PortSlot::new(0));
537 let prev = self.prev_in[ki];
538 let o = if is_truthy(prev) && !is_truthy(i) {
539 ONE
540 } else {
541 ZERO
542 };
543 self.prev_in[ki] = i;
544 self.set_port_hot(kid, PortSlot::new(1), o);
545 }
546 KindTag::Change => {
547 let i = self.get_port_hot(kid, PortSlot::new(0));
548 let prev = self.prev_in[ki];
549 let o = if is_truthy(prev) != is_truthy(i) {
550 ONE
551 } else {
552 ZERO
553 };
554 self.prev_in[ki] = i;
555 self.set_port_hot(kid, PortSlot::new(1), o);
556 }
557 KindTag::Clamp { min, max } => {
558 debug_assert!(min <= max);
559 let i = self.get_port_hot(kid, PortSlot::new(0));
560 let o = if i < min {
561 min
562 } else if i > max {
563 max
564 } else {
565 i
566 };
567 self.set_port_hot(kid, PortSlot::new(1), o);
568 }
569 KindTag::SignalOut => {
570 let v = self.get_port_hot(kid, PortSlot::new(0));
571 if let Some(path) = self.knots[ki].path {
572 self.push_signal_out(path, v);
573 }
574 }
575 KindTag::EmitCommand { enable_wired } => {
576 let trig = self.get_port_hot(kid, PortSlot::new(0));
577 let enable = if enable_wired {
579 self.get_port_hot(kid, PortSlot::new(1))
580 } else {
581 ONE
582 };
583 let payload = self.get_port_hot(kid, PortSlot::new(2));
584 let prev = self.prev_in[ki];
585 if !is_truthy(prev) && is_truthy(trig) && is_truthy(enable) {
586 if let Some(cmd) = self.knots[ki].cmd {
587 self.push_emit(cmd, payload);
588 }
589 }
590 self.prev_in[ki] = trig;
591 }
592 }
593 }
594}
595
596fn compare(op: CompareOp, lhs: Signal, rhs: Signal) -> bool {
597 match op {
598 CompareOp::Eq => lhs == rhs,
599 CompareOp::Ne => lhs != rhs,
600 CompareOp::Lt => lhs < rhs,
601 CompareOp::Lte => lhs <= rhs,
602 CompareOp::Gt => lhs > rhs,
603 CompareOp::Gte => lhs >= rhs,
604 }
605}
606
607#[inline]
608#[cfg(feature = "signal-f32")]
609fn normalize_count(value: Signal) -> Signal {
610 const MAX_COUNT: f32 = 2_147_483_520.0;
611 if value.is_nan() {
612 ZERO
613 } else {
614 libm::truncf(value).clamp(i32::MIN as f32, MAX_COUNT)
615 }
616}
617
618#[cfg(all(test, feature = "signal-f32"))]
619#[test]
620fn normalize_count_rejects_nan() {
621 assert_eq!(normalize_count(f32::NAN), ZERO);
622}
623
624#[inline]
625fn count_add(a: Signal, b: Signal) -> Signal {
626 #[cfg(feature = "signal-f32")]
627 {
628 normalize_count(a + b)
629 }
630 #[cfg(feature = "signal-i32")]
631 {
632 a.saturating_add(b)
633 }
634}
635
636#[inline]
637fn count_sub(a: Signal, b: Signal) -> Signal {
638 #[cfg(feature = "signal-f32")]
639 {
640 normalize_count(a - b)
641 }
642 #[cfg(feature = "signal-i32")]
643 {
644 a.saturating_sub(b)
645 }
646}
647
648#[inline]
649fn count_mul(a: Signal, b: Signal) -> Signal {
650 #[cfg(feature = "signal-f32")]
651 {
652 normalize_count((a as f64 * b as f64) as f32)
653 }
654 #[cfg(feature = "signal-i32")]
655 {
656 a.saturating_mul(b)
657 }
658}
659
660#[inline]
661fn count_div(a: Signal, b: Signal) -> Signal {
662 #[cfg(feature = "signal-f32")]
663 {
664 let a = a as i32;
665 let b = b as i32;
666 if b == 0 {
667 0.0
668 } else {
669 normalize_count(a.checked_div(b).unwrap_or(i32::MAX) as f32)
670 }
671 }
672 #[cfg(feature = "signal-i32")]
673 {
674 if b == 0 {
675 0
676 } else {
677 a.checked_div(b).unwrap_or(i32::MAX)
678 }
679 }
680}
681
682#[inline]
683fn level_to_count(value: Signal) -> Signal {
684 #[cfg(feature = "signal-f32")]
685 {
686 let rounded = if value >= 0.0 {
687 value + 0.5
688 } else {
689 value - 0.5
690 };
691 normalize_count(rounded)
692 }
693 #[cfg(feature = "signal-i32")]
694 {
695 let value = value as i64;
696 let half = (ONE / 2) as i64;
697 let rounded = if value >= 0 {
698 (value + half) / (ONE as i64)
699 } else {
700 (value - half) / (ONE as i64)
701 };
702 rounded.clamp(i32::MIN as i64, i32::MAX as i64) as i32
703 }
704}
705
706#[inline]
707fn count_abs(value: Signal) -> Signal {
708 #[cfg(feature = "signal-f32")]
709 {
710 normalize_count(libm::fabsf(value))
711 }
712 #[cfg(feature = "signal-i32")]
713 {
714 value.saturating_abs()
715 }
716}
717
718#[inline]
719fn count_neg(value: Signal) -> Signal {
720 #[cfg(feature = "signal-f32")]
721 {
722 normalize_count(-value)
723 }
724 #[cfg(feature = "signal-i32")]
725 {
726 value.saturating_neg()
727 }
728}
729
730#[inline]
731fn level_abs(value: Signal) -> Signal {
732 #[cfg(feature = "signal-f32")]
733 {
734 libm::fabsf(value)
735 }
736 #[cfg(feature = "signal-i32")]
737 {
738 value.saturating_abs()
739 }
740}
741
742#[inline]
743fn level_neg(value: Signal) -> Signal {
744 #[cfg(feature = "signal-f32")]
745 {
746 -value
747 }
748 #[cfg(feature = "signal-i32")]
749 {
750 value.saturating_neg()
751 }
752}
753
754#[inline]
755fn count_to_level(value: Signal) -> Signal {
756 #[cfg(feature = "signal-f32")]
757 {
758 value
759 }
760 #[cfg(feature = "signal-i32")]
761 {
762 value.saturating_mul(ONE)
763 }
764}
765
766#[cfg(feature = "signal-f32")]
767#[inline]
768fn map_linear_fast(
769 i: Signal,
770 domain: SignalDomain,
771 degenerate: bool,
772 in_min: Signal,
773 out_min: Signal,
774 inv_in_span: Signal,
775 out_span: Signal,
776) -> Signal {
777 if degenerate {
778 return out_min;
779 }
780 let t = ((i - in_min) * inv_in_span).clamp(0.0, 1.0);
781 let mapped = out_min + t * out_span;
782 if domain == SignalDomain::Count {
783 normalize_count(mapped)
784 } else {
785 mapped
786 }
787}
788
789#[cfg(test)]
790pub(crate) fn map_linear_for_test(
791 i: Signal,
792 in_min: Signal,
793 in_max: Signal,
794 out_min: Signal,
795 out_max: Signal,
796) -> Signal {
797 map_linear_for_domain_test(SignalDomain::Level, i, in_min, in_max, out_min, out_max)
798}
799
800#[cfg(test)]
801pub(crate) fn map_linear_for_domain_test(
802 domain: SignalDomain,
803 i: Signal,
804 in_min: Signal,
805 in_max: Signal,
806 out_min: Signal,
807 out_max: Signal,
808) -> Signal {
809 map_linear_from_tag_for_test(
810 KindTag::map_precomputed(domain, in_min, in_max, out_min, out_max),
811 i,
812 out_min,
813 )
814}
815
816#[cfg(test)]
817fn map_linear_from_tag_for_test(tag: KindTag, i: Signal, fallback_out_min: Signal) -> Signal {
818 match tag {
819 KindTag::Map {
820 domain,
821 #[cfg(feature = "signal-f32")]
822 degenerate,
823 #[cfg(feature = "signal-f32")]
824 in_min,
825 #[cfg(feature = "signal-f32")]
826 out_min,
827 #[cfg(feature = "signal-f32")]
828 inv_in_span,
829 #[cfg(feature = "signal-f32")]
830 out_span,
831 #[cfg(feature = "signal-i32")]
832 plan,
833 } => {
834 #[cfg(feature = "signal-f32")]
835 {
836 map_linear_fast(
837 i,
838 domain,
839 degenerate,
840 in_min,
841 out_min,
842 inv_in_span,
843 out_span,
844 )
845 }
846 #[cfg(feature = "signal-i32")]
847 {
848 let _ = domain;
849 plan.map(i)
850 }
851 }
852 _ => fallback_out_min,
853 }
854}
855
856#[inline]
858#[allow(clippy::too_many_arguments)]
859fn digitize_fast(
860 i: Signal,
861 domain: SignalDomain,
862 degenerate: bool,
863 in_min: Signal,
864 out_min: Signal,
865 bin_scale: Signal,
866 out_scale: Signal,
867 last_f: Signal,
868 steps: u16,
869 last: u16,
870 #[cfg(feature = "signal-i32")] den: i64,
871 #[cfg(feature = "signal-i32")] out_span: i64,
872) -> Signal {
873 if degenerate {
874 return out_min;
875 }
876 #[cfg(feature = "signal-f32")]
877 {
878 let _ = (steps, last);
879 let raw = (i - in_min) * bin_scale;
880 let bin = raw.max(0.0).min(last_f) as u32;
881 #[cfg(feature = "std")]
882 let mapped = { out_scale.mul_add(bin as f32, out_min) };
883 #[cfg(not(feature = "std"))]
884 let mapped = { out_scale * bin as f32 + out_min };
885 if domain == SignalDomain::Count {
886 normalize_count(mapped)
887 } else {
888 mapped
889 }
890 }
891 #[cfg(feature = "signal-i32")]
892 {
893 let _ = (domain, bin_scale, out_scale, last_f);
894 let last_i = last as i64;
895 let t = ((i as i64) - (in_min as i64)).clamp(0, den);
896 let mut bin = t * (steps as i64) / den;
897 if bin > last_i {
898 bin = last_i;
899 }
900 (out_min as i64 + bin * out_span / last_i) as i32
901 }
902}
903
904#[cfg(test)]
906pub(crate) fn digitize_for_test(
907 i: Signal,
908 steps: u16,
909 in_min: Signal,
910 in_max: Signal,
911 out_min: Signal,
912 out_max: Signal,
913) -> Signal {
914 digitize_for_domain_test(
915 SignalDomain::Level,
916 i,
917 steps,
918 in_min,
919 in_max,
920 out_min,
921 out_max,
922 )
923}
924
925#[cfg(test)]
926pub(crate) fn digitize_for_domain_test(
927 domain: SignalDomain,
928 i: Signal,
929 steps: u16,
930 in_min: Signal,
931 in_max: Signal,
932 out_min: Signal,
933 out_max: Signal,
934) -> Signal {
935 digitize_from_tag_for_test(
936 KindTag::digitize_precomputed(domain, steps, in_min, in_max, out_min, out_max),
937 i,
938 out_min,
939 )
940}
941
942#[cfg(test)]
943fn digitize_from_tag_for_test(tag: KindTag, i: Signal, fallback_out_min: Signal) -> Signal {
944 match tag {
945 KindTag::Digitize {
946 domain,
947 degenerate,
948 in_min,
949 out_min,
950 bin_scale,
951 out_scale,
952 last_f,
953 steps,
954 last,
955 #[cfg(feature = "signal-i32")]
956 den,
957 #[cfg(feature = "signal-i32")]
958 out_span,
959 } => digitize_fast(
960 i,
961 domain,
962 degenerate,
963 in_min,
964 out_min,
965 bin_scale,
966 out_scale,
967 last_f,
968 steps,
969 last,
970 #[cfg(feature = "signal-i32")]
971 den,
972 #[cfg(feature = "signal-i32")]
973 out_span,
974 ),
975 _ => fallback_out_min,
976 }
977}
978
979#[cfg(test)]
980mod test_helper_dispatch_tests {
981 use super::{digitize_from_tag_for_test, map_linear_from_tag_for_test, KindTag};
982 use crate::foundation::{from_count, ONE};
983
984 #[test]
985 fn nonmatching_precompute_tags_return_the_requested_fallback() {
986 assert_eq!(
987 map_linear_from_tag_for_test(KindTag::Not, ONE, from_count(7)),
988 from_count(7)
989 );
990 assert_eq!(
991 digitize_from_tag_for_test(KindTag::Not, ONE, from_count(9)),
992 from_count(9)
993 );
994 }
995}
996
997fn random_level_in_range(u: u32, min_v: Signal, max_v: Signal) -> Signal {
1000 #[cfg(feature = "signal-f32")]
1001 {
1002 let t = (u as f32) / (u32::MAX as f32);
1003 let lo = min_v.min(max_v);
1004 let hi = min_v.max(max_v);
1005 lo + t * (hi - lo)
1006 }
1007 #[cfg(feature = "signal-i32")]
1008 {
1009 let lo = min_v.min(max_v) as i64;
1010 let hi = min_v.max(max_v) as i64;
1011 let span = hi - lo;
1012 if span <= 0 {
1013 return lo as i32;
1014 }
1015 let offset = ((u as u64) * (span as u64)) / (u32::MAX as u64);
1016 (lo + offset as i64) as i32
1017 }
1018}
1019
1020fn random_count_in_range(u: u32, min_v: Signal, max_v: Signal) -> Signal {
1021 #[cfg(feature = "signal-f32")]
1022 let (min_v, max_v) = (min_v as i32, max_v as i32);
1023 let lo = min_v.min(max_v) as i64;
1024 let hi = min_v.max(max_v) as i64;
1025 let span = hi - lo;
1026 if span <= 0 {
1027 return crate::foundation::from_count(lo as i32);
1028 }
1029 let offset = ((u as u64) * (span as u64)) / (u32::MAX as u64);
1030 crate::foundation::from_count((lo + offset as i64) as i32)
1031}
1032
1033fn level_sqrt(i: Signal) -> Signal {
1034 #[cfg(feature = "signal-f32")]
1035 {
1036 if i <= 0.0 {
1037 0.0
1038 } else {
1039 sqrt_f32(i)
1040 }
1041 }
1042 #[cfg(feature = "signal-i32")]
1043 {
1044 if i <= 0 {
1045 0
1046 } else {
1047 isqrt_u64((i as u64) * (ONE as u64)) as i32
1048 }
1049 }
1050}
1051
1052#[cfg(test)]
1053mod sense_dispatch_tests {
1054 use super::*;
1055 use crate::authoring::Weave;
1056 use crate::foundation::{KnotKind, SignalDomain};
1057 use crate::BindOpts;
1058
1059 #[test]
1060 fn sense_dispatch_is_a_noop_after_seeding() {
1061 let mut builder = Weave::builder("sense-dispatch").unwrap();
1062 builder
1063 .knot("constant", KnotKind::constant(ONE, SignalDomain::Bool))
1064 .unwrap();
1065 let mut runtime = Runtime::bind(builder.build().unwrap(), BindOpts::default()).unwrap();
1066 let kid = KnotId::try_from(0usize).unwrap();
1067
1068 runtime.set_port_hot(kid, PortSlot::new(0), ONE);
1069 runtime.eval_knot(kid);
1070
1071 assert_eq!(runtime.get_port_hot(kid, PortSlot::new(0)), ONE);
1072 }
1073}
1074
1075#[cfg(test)]
1076mod count_div_dispatch_tests {
1077 use super::Runtime;
1078 use crate::authoring::Weave;
1079 use crate::foundation::{from_count, CalcOp, KnotKind, PortSlot, SignalDomain};
1080 use crate::BindOpts;
1081
1082 #[test]
1083 fn dynamic_count_division_uses_the_general_dispatch_path() {
1084 let mut builder = Weave::builder("dynamic-count-divisor").unwrap();
1085 let numerator = builder
1086 .knot("numerator", KnotKind::signal_in(SignalDomain::Count))
1087 .unwrap();
1088 let denominator = builder
1089 .knot("denominator", KnotKind::signal_in(SignalDomain::Count))
1090 .unwrap();
1091 let divide = builder
1092 .knot("divide", KnotKind::calc(CalcOp::Div, SignalDomain::Count))
1093 .unwrap();
1094 let numerator_out = builder.output(&numerator, "out").unwrap();
1095 let denominator_out = builder.output(&denominator, "out").unwrap();
1096 let divide_a = builder.input(÷, "a").unwrap();
1097 let divide_b = builder.input(÷, "b").unwrap();
1098 builder.connect(numerator_out, divide_a).unwrap();
1099 builder.connect(denominator_out, divide_b).unwrap();
1100
1101 let mut runtime = Runtime::bind(builder.build().unwrap(), BindOpts::default()).unwrap();
1102 let numerator = runtime.sense_id("numerator").unwrap();
1103 let denominator = runtime.sense_id("denominator").unwrap();
1104 {
1105 let mut writer = runtime.port_writer();
1106 writer.set_sense(numerator, from_count(9)).unwrap();
1107 writer.set_sense(denominator, from_count(2)).unwrap();
1108 }
1109 runtime.loom();
1110
1111 let divide = runtime.knot_id("divide").unwrap();
1112 assert_eq!(
1113 runtime.get_port_checked(divide, PortSlot::new(2)),
1114 Ok(from_count(4))
1115 );
1116 }
1117}
1118
1119fn count_sqrt(i: Signal) -> Signal {
1120 #[cfg(feature = "signal-f32")]
1121 {
1122 if i <= 0.0 {
1123 0.0
1124 } else {
1125 (sqrt_f32(i) as i32) as f32
1126 }
1127 }
1128 #[cfg(feature = "signal-i32")]
1129 {
1130 if i <= 0 {
1131 0
1132 } else {
1133 isqrt_u64(i as u64) as i32
1134 }
1135 }
1136}
1137
1138#[cfg(feature = "signal-f32")]
1140#[inline]
1141fn sqrt_f32(value: f32) -> f32 {
1142 #[cfg(feature = "std")]
1143 {
1144 value.sqrt()
1145 }
1146 #[cfg(not(feature = "std"))]
1147 {
1148 libm::sqrtf(value)
1149 }
1150}
1151
1152#[cfg(all(test, feature = "signal-f32"))]
1153mod sqrt_f32_tests {
1154 #[test]
1155 fn libm_matches_std_across_f32_magnitudes() {
1156 let values = [
1157 f32::from_bits(1),
1158 f32::MIN_POSITIVE,
1159 1.0e-20,
1160 0.25,
1161 2.0,
1162 1.0e20,
1163 f32::MAX,
1164 ];
1165
1166 for value in values {
1167 let expected = value.sqrt();
1168 let actual = libm::sqrtf(value);
1169 let tolerance = expected.abs() * (4.0 * f32::EPSILON);
1170 assert!(
1171 (actual - expected).abs() <= tolerance,
1172 "sqrt({value:e}): expected {expected:e}, got {actual:e}"
1173 );
1174 }
1175 }
1176
1177 #[test]
1178 fn std_dispatch_preserves_native_sqrt_semantics() {
1179 for value in [f32::from_bits(1), 2.0, f32::MAX] {
1180 assert_eq!(super::sqrt_f32(value), value.sqrt());
1181 }
1182 }
1183}
1184
1185#[cfg(feature = "signal-i32")]
1190#[inline]
1191fn isqrt_u64(mut n: u64) -> u64 {
1192 let mut result = 0u64;
1193 let mut bit = 1u64 << 62;
1194
1195 while bit > n {
1196 bit >>= 2;
1197 }
1198 while bit != 0 {
1199 let trial = result + bit;
1200 if n >= trial {
1201 n -= trial;
1202 result = (result >> 1) + bit;
1203 } else {
1204 result >>= 1;
1205 }
1206 bit >>= 2;
1207 }
1208 result
1209}
1210
1211#[cfg(all(test, feature = "signal-i32"))]
1212#[test]
1213fn isqrt_matches_dense_range_and_boundaries() {
1214 for n in 0u64..=65_536 {
1215 let root = isqrt_u64(n);
1216 assert!(root * root <= n, "isqrt({n}) overshot with {root}");
1217 assert!(
1218 (root + 1) * (root + 1) > n,
1219 "isqrt({n}) undershot with {root}"
1220 );
1221 }
1222 for k in 0i32..200 {
1223 let n = k * k;
1224 assert_eq!(isqrt_u64(n as u64), k as u64, "isqrt({n})");
1225 if k > 0 {
1226 assert_eq!(isqrt_u64((n - 1) as u64), (k - 1) as u64);
1227 }
1228 }
1229 assert_eq!(isqrt_u64(0), 0);
1230 assert_eq!(isqrt_u64(u64::MAX), u32::MAX as u64);
1231}
1232
1233#[cfg(all(test, feature = "signal-i32"))]
1234mod random_i32_range_tests {
1235 use super::{random_count_in_range, random_level_in_range};
1236
1237 #[test]
1238 fn full_i32_ranges_preserve_both_endpoints() {
1239 for range in [(i32::MIN, i32::MAX), (i32::MAX, i32::MIN)] {
1240 assert_eq!(random_level_in_range(0, range.0, range.1), i32::MIN);
1241 assert_eq!(random_level_in_range(u32::MAX, range.0, range.1), i32::MAX);
1242 assert_eq!(random_count_in_range(0, range.0, range.1), i32::MIN);
1243 assert_eq!(random_count_in_range(u32::MAX, range.0, range.1), i32::MAX);
1244 }
1245 }
1246}
1247
1248#[cfg(test)]
1249mod domain_math_tests {
1250 use super::{
1251 count_abs, count_add, count_div, count_mul, count_neg, count_sqrt, count_sub,
1252 count_to_level, level_sqrt, level_to_count,
1253 };
1254 use crate::foundation::{from_count, from_level};
1255
1256 #[test]
1257 fn count_arithmetic_is_integer_and_saturating() {
1258 assert_eq!(count_mul(from_count(6), from_count(7)), from_count(42));
1259 assert_eq!(
1260 count_mul(from_count(50_000), from_count(50_000)),
1261 from_count(i32::MAX)
1262 );
1263 assert_eq!(count_div(from_count(7), from_count(2)), from_count(3));
1264 assert_eq!(count_div(from_count(7), from_count(0)), from_count(0));
1265 assert_eq!(
1266 count_div(from_count(i32::MIN), from_count(-1)),
1267 from_count(i32::MAX)
1268 );
1269 assert_eq!(
1270 count_add(from_count(i32::MAX), from_count(1)),
1271 from_count(i32::MAX)
1272 );
1273 assert_eq!(
1274 count_sub(from_count(i32::MIN), from_count(1)),
1275 from_count(i32::MIN)
1276 );
1277 assert_eq!(count_abs(from_count(i32::MIN)), from_count(i32::MAX));
1278 assert_eq!(count_neg(from_count(i32::MIN)), from_count(i32::MAX));
1279 }
1280
1281 #[test]
1282 fn numeric_conversions_round_and_saturate() {
1283 assert_eq!(level_to_count(from_level(2.5)), from_count(3));
1284 assert_eq!(level_to_count(from_level(-2.5)), from_count(-3));
1285 assert_eq!(count_to_level(from_count(2)), from_level(2.0));
1286
1287 #[cfg(feature = "signal-i32")]
1288 assert_eq!(count_to_level(i32::MAX), i32::MAX);
1289 }
1290
1291 #[test]
1292 fn sqrt_respects_level_and_count_representations() {
1293 assert_eq!(level_sqrt(from_level(0.25)), from_level(0.5));
1294 assert_eq!(count_sqrt(from_count(15)), from_count(3));
1295 assert_eq!(count_sqrt(from_count(-1)), from_count(0));
1296 }
1297}
1298
1299#[cfg(test)]
1300mod digitize_tests {
1301 use super::{digitize_for_domain_test, digitize_for_test};
1302 use crate::foundation::{from_count, SignalDomain, ONE, ZERO};
1303
1304 #[test]
1305 fn digitize_precompute_matches_endpoints_and_mids() {
1306 let steps = 4u16;
1307 let in0 = from_count(0);
1308 let in4 = from_count(4);
1309 let o0 = from_count(0);
1310 let o30 = from_count(30);
1311 assert_eq!(
1312 digitize_for_test(from_count(0), steps, in0, in4, o0, o30),
1313 from_count(0)
1314 );
1315 assert_eq!(
1316 digitize_for_test(from_count(1), steps, in0, in4, o0, o30),
1317 from_count(10)
1318 );
1319 assert_eq!(
1320 digitize_for_test(from_count(2), steps, in0, in4, o0, o30),
1321 from_count(20)
1322 );
1323 assert_eq!(
1324 digitize_for_test(from_count(3), steps, in0, in4, o0, o30),
1325 from_count(30)
1326 );
1327 assert_eq!(
1328 digitize_for_test(from_count(4), steps, in0, in4, o0, o30),
1329 from_count(30)
1330 );
1331 assert_eq!(
1332 digitize_for_test(ONE, 1, ZERO, ONE, from_count(7), from_count(9)),
1333 from_count(7)
1334 );
1335 }
1336
1337 #[test]
1338 fn count_digitize_truncates_non_divisible_ranges() {
1339 assert_eq!(
1340 digitize_for_domain_test(
1341 SignalDomain::Count,
1342 from_count(1),
1343 4,
1344 from_count(0),
1345 from_count(3),
1346 from_count(0),
1347 from_count(10),
1348 ),
1349 from_count(3)
1350 );
1351 }
1352}
1353
1354#[cfg(test)]
1355mod map_tests {
1356 use super::{map_linear_for_domain_test, map_linear_for_test};
1357 use crate::foundation::{from_count, SignalDomain, ONE, ZERO};
1358
1359 #[cfg(feature = "signal-i32")]
1360 use crate::runtime_impl::kind_tag::{I32MapPlan, KindTag};
1361
1362 #[test]
1363 fn map_precompute_endpoints_mid_and_degenerate() {
1364 let i0 = from_count(0);
1365 let i4 = from_count(4);
1366 let o0 = from_count(0);
1367 let o40 = from_count(40);
1368 assert_eq!(map_linear_for_test(from_count(0), i0, i4, o0, o40), o0);
1369 assert_eq!(
1370 map_linear_for_test(from_count(2), i0, i4, o0, o40),
1371 from_count(20)
1372 );
1373 assert_eq!(map_linear_for_test(from_count(4), i0, i4, o0, o40), o40);
1374 assert_eq!(map_linear_for_test(from_count(8), i0, i4, o0, o40), o40);
1375 assert_eq!(map_linear_for_test(from_count(-2), i0, i4, o0, o40), o0);
1376 assert_eq!(
1377 map_linear_for_test(ONE, from_count(3), from_count(3), from_count(7), o40),
1378 from_count(7)
1379 );
1380 let mid = map_linear_for_test(
1381 #[cfg(feature = "signal-f32")]
1382 {
1383 0.5
1384 },
1385 #[cfg(feature = "signal-i32")]
1386 {
1387 ONE / 2
1388 },
1389 ZERO,
1390 ONE,
1391 ZERO,
1392 ONE,
1393 );
1394 #[cfg(feature = "signal-f32")]
1395 assert!((mid - 0.5).abs() < 1e-5);
1396 #[cfg(feature = "signal-i32")]
1397 assert_eq!(mid, ONE / 2);
1398 }
1399
1400 #[test]
1401 fn count_map_truncates_non_divisible_ranges() {
1402 assert_eq!(
1403 map_linear_for_domain_test(
1404 SignalDomain::Count,
1405 from_count(1),
1406 from_count(0),
1407 from_count(3),
1408 from_count(0),
1409 from_count(10),
1410 ),
1411 from_count(3)
1412 );
1413 }
1414
1415 #[cfg(feature = "signal-i32")]
1416 #[test]
1417 fn map_full_i32_range_uses_wide_intermediates() {
1418 assert_eq!(
1419 map_linear_for_test(i32::MIN, i32::MIN, i32::MAX, i32::MIN, i32::MAX),
1420 i32::MIN
1421 );
1422 assert_eq!(
1423 map_linear_for_test(i32::MAX, i32::MIN, i32::MAX, i32::MIN, i32::MAX),
1424 i32::MAX
1425 );
1426 assert_eq!(
1427 map_linear_for_test(0, i32::MIN, i32::MAX, i32::MIN, i32::MAX),
1428 0
1429 );
1430
1431 assert_eq!(
1432 map_linear_for_test(i32::MIN, i32::MIN, i32::MAX, i32::MAX, i32::MIN),
1433 i32::MAX
1434 );
1435 assert_eq!(
1436 map_linear_for_test(i32::MAX, i32::MIN, i32::MAX, i32::MAX, i32::MIN),
1437 i32::MIN
1438 );
1439 assert_eq!(
1440 map_linear_for_test(0, i32::MIN, i32::MAX, i32::MAX, i32::MIN),
1441 -1
1442 );
1443 }
1444
1445 #[cfg(feature = "signal-i32")]
1446 #[test]
1447 fn map_full_i32_output_range_clamps_inputs_outside_range() {
1448 assert_eq!(
1449 map_linear_for_test(i32::MIN, -1, 1, i32::MIN, i32::MAX),
1450 i32::MIN
1451 );
1452 assert_eq!(
1453 map_linear_for_test(i32::MAX, -1, 1, i32::MIN, i32::MAX),
1454 i32::MAX
1455 );
1456 }
1457
1458 #[cfg(feature = "signal-i32")]
1459 fn reference_i32_map(input: i32, in_min: i32, in_max: i32, out_min: i32, out_max: i32) -> i32 {
1460 let den = (in_max as i128) - (in_min as i128);
1461 if den == 0 {
1462 return out_min;
1463 }
1464 let t = ((input as i128) - (in_min as i128)).clamp(0, den);
1465 ((out_min as i128) + t * ((out_max as i128) - (out_min as i128)) / den) as i32
1466 }
1467
1468 #[cfg(feature = "signal-i32")]
1469 fn i32_map_plan(tag: KindTag) -> I32MapPlan {
1470 match tag {
1471 KindTag::Map { plan, .. } => plan,
1472 _ => panic!("map precompute must return a Map tag"),
1473 }
1474 }
1475
1476 #[cfg(feature = "signal-i32")]
1477 #[test]
1478 fn map_i32_uses_specialized_bind_plans() {
1479 let plan = |in_min, in_max, out_min, out_max| {
1480 i32_map_plan(KindTag::map_precomputed(
1481 SignalDomain::Count,
1482 in_min,
1483 in_max,
1484 out_min,
1485 out_max,
1486 ))
1487 };
1488
1489 assert!(matches!(plan(3, 3, 7, 9), I32MapPlan::Constant { .. }));
1490 assert!(matches!(plan(-8, 8, 5, 21), I32MapPlan::Unit { .. }));
1491 assert!(matches!(plan(0, 8, 0, 16), I32MapPlan::Scale { .. }));
1492 assert!(matches!(plan(0, 8, 0, 4), I32MapPlan::Shift { .. }));
1493 assert!(matches!(plan(0, 10, 0, 3), I32MapPlan::Divide { .. }));
1494 }
1495
1496 #[cfg(feature = "signal-i32")]
1497 #[test]
1498 #[should_panic(expected = "map precompute must return a Map tag")]
1499 fn i32_map_plan_rejects_non_map_tags() {
1500 let _ = i32_map_plan(KindTag::Not);
1501 }
1502
1503 #[cfg(feature = "signal-i32")]
1504 #[test]
1505 fn map_i32_matches_wide_reference_across_edge_and_seeded_ranges() {
1506 let cases = [
1507 (i32::MIN, i32::MAX, i32::MIN, i32::MAX),
1508 (i32::MIN, i32::MAX, i32::MAX, i32::MIN),
1509 (-65_536, 65_536, 0, 65_536),
1510 (0, 8, 0, 16),
1511 (0, 8, 0, 4),
1512 (0, 10, -37, 997),
1513 (3, 3, -5, 42),
1514 ];
1515 for (in_min, in_max, out_min, out_max) in cases {
1516 for input in [
1517 i32::MIN,
1518 in_min.saturating_sub(1),
1519 in_min,
1520 0,
1521 in_max,
1522 in_max.saturating_add(1),
1523 i32::MAX,
1524 ] {
1525 assert_eq!(
1526 map_linear_for_domain_test(
1527 SignalDomain::Count,
1528 input,
1529 in_min,
1530 in_max,
1531 out_min,
1532 out_max,
1533 ),
1534 reference_i32_map(input, in_min, in_max, out_min, out_max),
1535 "input={input}, in={in_min}..{in_max}, out={out_min}..{out_max}"
1536 );
1537 }
1538 }
1539
1540 let mut state = 0x9E37_79B9u32;
1541 for _ in 0..512 {
1542 let next = |state: &mut u32| {
1543 *state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
1544 *state as i32
1545 };
1546 let a = next(&mut state);
1547 let b = next(&mut state);
1548 let (in_min, in_max) = if a <= b { (a, b) } else { (b, a) };
1549 let out_min = next(&mut state);
1550 let out_max = next(&mut state);
1551 let input = next(&mut state);
1552 assert_eq!(
1553 map_linear_for_domain_test(
1554 SignalDomain::Count,
1555 input,
1556 in_min,
1557 in_max,
1558 out_min,
1559 out_max,
1560 ),
1561 reference_i32_map(input, in_min, in_max, out_min, out_max),
1562 );
1563 }
1564 }
1565}