+static noinline void choose_isrs_idle(void);
+
+/* player1 TH */
+#define PL1_ISFR PORTD_ISFR
+#define PL1_TH() ((CORE_PIN21_PINREG >> CORE_PIN21_BIT) & 1)
+
+static void pl1th_isr_fixed(void)
+{
+ uint32_t isfr, th;
+
+ isfr = PL1_ISFR;
+ PL1_ISFR = isfr;
+ th = PL1_TH();
+
+ GPIOD_PDOR = g.pl[0].fixed_state[th];
+ g.edge_cnt++;
+}
+
+static noinline void do_to_step_pl1(void)
+{
+ g.frame_cnt++;
+
+ g.pos_to_p[0].o = (g.pos_to_p[0].o + 1) & STREAM_BUF_MASK;
+ if (g.pos_to_p[0].o == g.pos_to_p[0].i)
+ // done
+ choose_isrs_idle();
+}
+
+static void pl1th_isr_do_to_inc(void)
+{
+ uint32_t isfr, th;
+
+ isfr = PL1_ISFR;
+ PL1_ISFR = isfr;
+ th = PL1_TH();
+
+ GPIOD_PDOR = g.stream_to[0][g.pos_to_p[0].o][th];
+ if (th)
+ do_to_step_pl1();
+}
+
+static void pl1th_isr_do_to(void)
+{
+ uint32_t isfr, th;
+
+ isfr = PL1_ISFR;
+ PL1_ISFR = isfr;
+ th = PL1_TH();
+
+ GPIOD_PDOR = g.stream_to[0][g.pos_to_p[0].o][th];
+ g.edge_cnt++;
+}
+
+/* player2 TH */
+#define PL2_ISFR PORTC_ISFR
+#define PL2_TH() ((CORE_PIN15_PINREG >> CORE_PIN15_BIT) & 1)
+#define PL2_ADJ(x) ((x) | ((x) << 12))
+
+static void pl2th_isr_fixed(void)
+{
+ uint32_t isfr, th, v;
+
+ isfr = PL2_ISFR;
+ PL2_ISFR = isfr;
+ th = PL2_TH();
+
+ v = g.pl[1].fixed_state[th];
+ GPIOB_PDOR = PL2_ADJ(v);
+}
+
+static noinline void do_to_step_pl2(void)
+{
+ g.pos_to_p[1].o = (g.pos_to_p[1].o + 1) & STREAM_BUF_MASK;
+ if (g.pos_to_p[1].o == g.pos_to_p[1].i)
+ // done
+ choose_isrs_idle();
+}
+
+static void pl2th_isr_do_to_inc(void)
+{
+ uint32_t isfr, th, v;
+
+ isfr = PL2_ISFR;
+ PL2_ISFR = isfr;
+ th = PL2_TH();
+
+ v = g.stream_to[1][g.pos_to_p[1].o][th];
+ GPIOB_PDOR = PL2_ADJ(v);
+ if (th)
+ do_to_step_pl2();
+}
+
+static void pl2th_isr_do_to_p1d(void)
+{
+ uint32_t isfr, th, v;
+
+ isfr = PL2_ISFR;
+ PL2_ISFR = isfr;
+ th = PL2_TH();
+
+ v = g.stream_to[1][g.pos_to_p[0].o][th];
+ GPIOB_PDOR = PL2_ADJ(v);
+
+ g.pos_to_p[1].o = g.pos_to_p[0].o;
+}
+
+static void pl2th_isr_do_to_inc_pl1(void)
+{
+ uint32_t isfr, th, v;
+
+ isfr = PL2_ISFR;
+ PL2_ISFR = isfr;
+ th = PL2_TH();
+
+ v = g.stream_to[1][g.pos_to_p[1].o][th];
+ GPIOB_PDOR = PL2_ADJ(v);
+ if (th) {
+ do_to_step_pl1();
+ g.pos_to_p[1].o = g.pos_to_p[0].o;
+ }
+}
+
+/* vsync handler */
+#define VSYNC_ISFR PORTC_ISFR
+
+static void vsync_isr_nop(void)
+{
+ uint32_t isfr;
+
+ isfr = VSYNC_ISFR;
+ VSYNC_ISFR = isfr;
+}
+
+// /vsync starts at line 235/259 (ntsc/pal), just as vcounter jumps back
+// we care when it comes out (/vsync goes high) after 3 lines at 238/262
+static void vsync_isr_frameinc(void)
+{
+ uint32_t isfr;
+
+ isfr = VSYNC_ISFR;
+ VSYNC_ISFR = isfr;
+
+ g.pos_to_p[0].o = (g.pos_to_p[0].o + 1) & STREAM_BUF_MASK;
+ g.pos_to_p[1].o = g.pos_to_p[0].o;
+
+ if (g.pos_to_p[0].o == g.pos_to_p[0].i)
+ choose_isrs_idle();
+ g.frame_cnt++;
+}
+
+/* "recording" data */
+static noinline void do_from_step(void)
+{
+ uint32_t s;
+
+ // should hopefully give atomic fixed_state read..
+ s = g.pl[0].fixed_state32;
+ g.pl[0].fixed_state32 = g.pl[0].pending_state32;
+ g.stream_from[g.pos_from.i][0] = s;
+ g.stream_from[g.pos_from.i][1] = s >> 8;
+ g.pos_from.i = (g.pos_from.i + 1) & STREAM_BUF_MASK;
+}
+
+static void pl1th_isr_fixed_do_from(void)
+{
+ uint32_t isfr, th;
+
+ isfr = PL1_ISFR;
+ PL1_ISFR = isfr;
+ th = PL1_TH();
+
+ GPIOD_PDOR = g.pl[0].fixed_state[th];
+ if (th)
+ do_from_step();
+ g.edge_cnt++;
+}
+
+static void vsync_isr_frameinc_do_from(void)
+{
+ uint32_t isfr;
+
+ isfr = VSYNC_ISFR;
+ VSYNC_ISFR = isfr;
+
+ do_from_step();
+ g.frame_cnt++;
+}
+
+/* * */
+static void choose_isrs(void)
+{
+ void (*pl1th_handler)(void) = pl1th_isr_fixed;
+ void (*pl2th_handler)(void) = pl2th_isr_fixed;
+ void (*vsync_handler)(void) = vsync_isr_nop;
+
+ if (g.stream_enable_to) {
+ switch (g.inc_mode) {
+ case INC_MODE_VSYNC:
+ pl1th_handler = pl1th_isr_do_to;
+ pl2th_handler = pl2th_isr_do_to_p1d;
+ vsync_handler = vsync_isr_frameinc;
+ break;
+ case INC_MODE_SHARED_PL1:
+ pl1th_handler = pl1th_isr_do_to_inc;
+ pl2th_handler = pl2th_isr_do_to_p1d;
+ break;
+ case INC_MODE_SHARED_PL2:
+ pl1th_handler = pl1th_isr_do_to;
+ pl2th_handler = pl2th_isr_do_to_inc_pl1;
+ break;
+ case INC_MODE_SEPARATE:
+ pl1th_handler = pl1th_isr_do_to_inc;
+ pl2th_handler = pl2th_isr_do_to_inc;
+ break;
+ }
+ }
+ else if (g.stream_enable_from) {
+ g.use_pending = 1;
+ switch (g.inc_mode) {
+ case INC_MODE_VSYNC:
+ vsync_handler = vsync_isr_frameinc_do_from;
+ break;
+ case INC_MODE_SHARED_PL1:
+ pl1th_handler = pl1th_isr_fixed_do_from;
+ break;
+ case INC_MODE_SHARED_PL2:
+ case INC_MODE_SEPARATE:
+ /* TODO */
+ break;
+ }
+ }
+
+ attachInterruptVector(IRQ_PORTD, pl1th_handler);
+ attachInterruptVector(IRQ_PORTC, pl2th_handler);
+ attachInterruptVector(IRQ_PORTA, vsync_handler);
+}
+
+static noinline void choose_isrs_idle(void)