diff --git a/Assets/dll/bsnes.wbx.gz b/Assets/dll/bsnes.wbx.gz
index 7f128f5bf1..cf02e454ef 100644
Binary files a/Assets/dll/bsnes.wbx.gz and b/Assets/dll/bsnes.wbx.gz differ
diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesApi.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesApi.cs
index c0082221b7..4764a9e4f6 100644
--- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesApi.cs
+++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesApi.cs
@@ -146,10 +146,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES
 		}
 
 		public delegate void snes_video_frame_t(ushort* data, int width, int height, int pitch);
-		public delegate void snes_input_poll_t();
-		public delegate short snes_input_state_t(int port, int index, int id);
-		public delegate void snes_no_lag_t(bool sgb_poll);
 		public delegate void snes_audio_sample_t(short left, short right);
+		public delegate short snes_input_poll_t(int port, int index, int id);
+		public delegate void snes_controller_latch_t();
+		public delegate void snes_no_lag_t(bool sgb_poll);
 		public delegate string snes_path_request_t(int slot, string hint, bool required);
 		public delegate void snes_trace_t(string disassembly, string register_info);
 		public delegate void snes_read_hook_t(uint address);
@@ -192,11 +192,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES
 		[StructLayout(LayoutKind.Sequential)]
 		public class SnesCallbacks
 		{
-			public snes_input_poll_t inputPollCb;
-			public snes_input_state_t inputStateCb;
-			public snes_no_lag_t noLagCb;
 			public snes_video_frame_t videoFrameCb;
 			public snes_audio_sample_t audioSampleCb;
+			public snes_input_poll_t inputPollCb;
+			public snes_controller_latch_t controllerLatchCb;
+			public snes_no_lag_t noLagCb;
 			public snes_path_request_t pathRequestCb;
 			public snes_trace_t traceCb;
 			public snes_read_hook_t readHookCb;
diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesControllers.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesControllers.cs
index 71952c8a53..f3cfc7cd4a 100644
--- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesControllers.cs
+++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesControllers.cs
@@ -56,32 +56,20 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES
 			Definition.MakeImmutable();
 		}
 
-		public void CoreInputPoll(IController controller)
+		public short CoreInputPoll(IController controller, int port, int index, int id)
 		{
-			// i hope this is correct lol
-			for (int i = 0; i < 2; i++)
-			{
-				_ports[i].UpdateState(_mergers[i].UnMerge(controller));
-			}
-		}
-
-		public short CoreInputState(int port, int index, int id)
-		{
-			return _ports[port].GetState(index, id);
+			return _ports[port].GetState(_mergers[port].UnMerge(controller), index, id);
 		}
 	}
 
 	public interface IBsnesController
 	{
-		// Updates the internal state; gets called once per frame from the core
-		void UpdateState(IController controller);
-
 		/// <summary>
-		/// Returns the internal state; gets called potentially many times per frame
+		/// Corresponds to an InputPoll call from the core; gets called potentially many times per frame
 		/// </summary>
 		/// <param name="index">bsnes specific value, sometimes multitap number</param>
 		/// <param name="id">bsnes specific value, sometimes button number</param>
-		short GetState(int index, int id);
+		short GetState(IController controller, int index, int id);
 
 		ControllerDefinition Definition { get; }
 	}
@@ -92,15 +80,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES
 
 		public ControllerDefinition Definition => _definition;
 
-		public void UpdateState(IController controller) { }
-
-		public short GetState(int index, int id) => 0;
+		public short GetState(IController controller, int index, int id) => 0;
 	}
 
 	internal class BsnesController : IBsnesController
 	{
-		private readonly bool[] _state = new bool[12];
-
 		private static readonly string[] Buttons =
 		{
 			"0Up", "0Down", "0Left", "0Right", "0B", "0A", "0Y", "0X", "0L", "0R", "0Select", "0Start"
@@ -129,27 +113,17 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES
 
 		public ControllerDefinition Definition => _definition;
 
-		public void UpdateState(IController controller)
-		{
-			for (int i = 0; i < 12; i++)
-			{
-				_state[i] = controller.IsPressed(Buttons[i]);
-			}
-		}
-
-		public short GetState(int index, int id)
+		public short GetState(IController controller, int index, int id)
 		{
 			if (id >= 12)
 				return 0;
 
-			return (short) (_state[id] ? 1 : 0);
+			return (short) (controller.IsPressed(Buttons[id]) ? 1 : 0);
 		}
 	}
 
 	internal class BsnesMouseController : IBsnesController
 	{
-		private readonly short[] _state = new short[4];
-
 		private static readonly ControllerDefinition _definition = new ControllerDefinition("(SNES Controller fragment)")
 				{ BoolButtons = { "0Mouse Left", "0Mouse Right" } }
 			.AddXYPair("0Mouse {0}", AxisPairOrientation.RightAndDown, (-127).RangeTo(127), 0); //TODO verify direction against hardware, R+D inferred from behaviour in Mario Paint
@@ -157,39 +131,36 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES
 		public ControllerDefinition Definition => _definition;
 		public bool LimitAnalogChangeSensitivity { get; init; } = true;
 
-		public void UpdateState(IController controller)
+		public short GetState(IController controller, int index, int id)
 		{
-			int x = controller.AxisValue("0Mouse X");
-			if (LimitAnalogChangeSensitivity)
+			switch (id)
 			{
-				x = x.Clamp(-10, 10);
+				case 0:
+					int x = controller.AxisValue("0Mouse X");
+					if (LimitAnalogChangeSensitivity)
+					{
+						x = x.Clamp(-10, 10);
+					}
+					return (short) x;
+				case 1:
+					int y = controller.AxisValue("0Mouse Y");
+					if (LimitAnalogChangeSensitivity)
+					{
+						y = y.Clamp(-10, 10);
+					}
+					return (short) y;
+				case 2:
+					return (short) (controller.IsPressed("0Mouse Left") ? 1 : 0);
+				case 3:
+					return (short) (controller.IsPressed("0Mouse Right") ? 1 : 0);
+				default:
+					return 0;
 			}
-			_state[0] = (short) x;
-
-			int y = controller.AxisValue("0Mouse Y");
-			if (LimitAnalogChangeSensitivity)
-			{
-				y = y.Clamp(-10, 10);
-			}
-			_state[1] = (short) y;
-
-			_state[2] = (short) (controller.IsPressed("0Mouse Left") ? 1 : 0);
-			_state[3] = (short) (controller.IsPressed("0Mouse Right") ? 1 : 0);
-		}
-
-		public short GetState(int index, int id)
-		{
-			if (id >= 4)
-				return 0;
-
-			return _state[id];
 		}
 	}
 
 	internal class BsnesMultitapController : IBsnesController
 	{
-		private readonly bool[,] _state = new bool[4, 12];
-
 		private static readonly string[] Buttons =
 		{
 			"Up", "Down", "Left", "Right", "B", "A", "Y", "X", "L", "R", "Select", "Start"
@@ -221,28 +192,17 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES
 
 		public ControllerDefinition Definition => _definition;
 
-		public void UpdateState(IController controller)
-		{
-			for (int port = 0; port < 4; port++)
-			for (int i = 0; i < 12; i++)
-			{
-				_state[port, i] = controller.IsPressed(port + Buttons[i]);
-			}
-		}
-
-		public short GetState(int index, int id)
+		public short GetState(IController controller, int index, int id)
 		{
 			if (id >= 12 || index >= 4)
 				return 0;
 
-			return (short) (_state[index, id] ? 1 : 0);
+			return (short) (controller.IsPressed(index + Buttons[id]) ? 1 : 0);
 		}
 	}
 
 	internal class BsnesPayloadController : IBsnesController
 	{
-		private readonly bool[,] _state = new bool[2, 16];
-
 		private readonly int[] _buttonsOrder = {4, 5, 6, 7, 0, 8, 1, 9, 10, 11, 2, 3, 12, 13, 14, 15};
 
 		private static readonly ControllerDefinition _definition = new("(SNES Controller fragment)")
@@ -252,50 +212,32 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES
 
 		public ControllerDefinition Definition => _definition;
 
-		public void UpdateState(IController controller)
-		{
-			for (int index = 0; index < 2; index++)
-			for (int i = 0; i < 16; i++)
-			{
-				_state[index, i] = controller.IsPressed(Definition.BoolButtons[index * 16 + _buttonsOrder[i]]);
-			}
-		}
-
-		public short GetState(int index, int id)
+		public short GetState(IController controller, int index, int id)
 		{
 			if (index >= 2 || id >= 16)
 				return 0;
 
-			return (short) (_state[index, id] ? 1 : 0);
+			return (short) (controller.IsPressed(Definition.BoolButtons[index * 16 + _buttonsOrder[id]]) ? 1 : 0);
 		}
 	}
 
 	internal class BsnesSuperScopeController : IBsnesController
 	{
-		private readonly short[] _state = new short[6];
-
 		private static readonly ControllerDefinition _definition = new ControllerDefinition("(SNES Controller fragment)")
 			{ BoolButtons = { "0Trigger", "0Cursor", "0Turbo", "0Pause" } }
 			.AddLightGun("0Scope {0}");
 
 		public ControllerDefinition Definition => _definition;
 
-		public void UpdateState(IController controller)
+		public short GetState(IController controller, int index, int id)
 		{
-			_state[0] = (short) controller.AxisValue("0Scope X");
-			_state[1] = (short) controller.AxisValue("0Scope Y");
-			for (int i = 0; i < 4; i++)
+			return id switch
 			{
-				_state[i + 2] = (short) (controller.IsPressed(_definition.BoolButtons[i]) ? 1 : 0);
-			}
-		}
-
-		public short GetState(int index, int id)
-		{
-			if (id >= 6)
-				return 0;
-
-			return _state[id];
+				0 => (short) controller.AxisValue("0Scope X"),
+				1 => (short) controller.AxisValue("0Scope Y"),
+				2 or 3 or 4 or 5 => (short) (controller.IsPressed(_definition.BoolButtons[id - 2]) ? 1 : 0),
+				_ => 0
+			};
 		}
 	}
 
@@ -311,35 +253,25 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES
 				: new ControllerDefinition("(SNES Controller fragment)")
 					{BoolButtons = { "0Trigger", "0Start"} }
 					.AddLightGun("0Justifier {0}");
-			_state = new short[chained ? 8 : 4];
 			_chained = chained;
 		}
 
 		private readonly bool _chained;
-		private readonly short[] _state;
 
 		public ControllerDefinition Definition { get; }
 
-		public void UpdateState(IController controller)
+		public short GetState(IController controller, int index, int id)
 		{
-			_state[0] = (short) controller.AxisValue("0Justifier X");
-			_state[1] = (short) controller.AxisValue("0Justifier Y");
-			_state[2] = (short) (controller.IsPressed(Definition.BoolButtons[0]) ? 1 : 0);
-			_state[3] = (short) (controller.IsPressed(Definition.BoolButtons[1]) ? 1 : 0);
-			if (_chained)
-			{
-				_state[4] = (short) controller.AxisValue("1Justifier X");
-				_state[5] = (short) controller.AxisValue("1Justifier Y");
-				_state[6] = (short) (controller.IsPressed(Definition.BoolButtons[2]) ? 1 : 0);
-				_state[7] = (short) (controller.IsPressed(Definition.BoolButtons[3]) ? 1 : 0);
-			}
-		}
-		public short GetState(int index, int id)
-		{
-			if (index >= 2 || id >= 4 || (index == 1 && !_chained))
+			if (index == 1 && !_chained)
 				return 0;
 
-			return _state[index * 4 + id];
+			return id switch
+			{
+				0 => (short) controller.AxisValue($"{index}Justifier X"),
+				1 => (short) controller.AxisValue($"{index}Justifier Y"),
+				2 or 3 => (short) (controller.IsPressed(Definition.BoolButtons[index * 2 + id]) ? 1 : 0),
+				_ => 0
+			};
 		}
 	}
 }
diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.cs
index c7a4c33e2d..79997a0c0c 100644
--- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.cs
+++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.cs
@@ -54,8 +54,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES
 			BsnesApi.SnesCallbacks callbacks = new()
 			{
 				inputPollCb = snes_input_poll,
-				inputStateCb = snes_input_state,
 				noLagCb = snes_no_lag,
+				controllerLatchCb = snes_controller_latch,
 				videoFrameCb = snes_video_refresh,
 				audioSampleCb = snes_audio_sample,
 				pathRequestCb = snes_path_request,
@@ -283,30 +283,29 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES
 			_region = Api.core.snes_get_region();
 		}
 
-		// poll which updates the controller state
-		private void snes_input_poll()
-		{
-			_controllers.CoreInputPoll(_controller);
-		}
-
 		/// <param name="port">0 or 1, corresponding to L and R physical ports on the snes</param>
 		/// <param name="index">meaningless for most controllers.  for multitap, 0-3 for which multitap controller</param>
 		/// <param name="id">button ID enum; in the case of a regular controller, this corresponds to shift register position</param>
 		/// <returns>for regular controllers, one bit D0 of button status.  for other controls, varying ranges depending on id</returns>
-		private short snes_input_state(int port, int index, int id)
+		private short snes_input_poll(int port, int index, int id)
 		{
-			return _controllers.CoreInputState(port, index, id);
+			return _controllers.CoreInputPoll(_controller, port, index, id);
 		}
 
 		private void snes_no_lag(bool sgbPoll)
 		{
-			// gets called whenever there was input polled, aka no lag
+			// gets called whenever there was input read in the core
 			if (!IsSGB || sgbPoll)
 			{
 				IsLagFrame = false;
 			}
 		}
 
+		private void snes_controller_latch()
+		{
+			InputCallbacks.Call();
+		}
+
 		private readonly int[] palette = new int[0x8000];
 
 		private void generate_palette()
diff --git a/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.IDebuggable.cs b/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.IDebuggable.cs
index c89dfc6577..9235960e4b 100644
--- a/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.IDebuggable.cs
+++ b/src/BizHawk.Emulation.Cores/Consoles/Sega/gpgx64/GPGX.IDebuggable.cs
@@ -14,7 +14,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx
 
 			int n = Core.gpgx_getregs(regs);
 			if (n > regs.Length)
-					throw new InvalidOperationException("A buffer overrun has occured!");
+				throw new InvalidOperationException("A buffer overrun has occured!");
 			var ret = new Dictionary<string, RegisterValue>();
 			using (_elf.EnterExit())
 			{
diff --git a/waterbox/bsnescore/bsnes/sfc/controller/controller.cpp b/waterbox/bsnescore/bsnes/sfc/controller/controller.cpp
index 2121f2ef68..bf91c6ef85 100644
--- a/waterbox/bsnescore/bsnes/sfc/controller/controller.cpp
+++ b/waterbox/bsnescore/bsnes/sfc/controller/controller.cpp
@@ -16,6 +16,13 @@ Controller::Controller(uint port) : port(port) {
 Controller::~Controller() {
 }
 
+auto Controller::latch(bool data) -> void {
+  if(latched == data) return;
+  latched = data;
+
+  if (latched == 0 && port == ID::Port::Controller1) platform->notify("LATCH");
+}
+
 auto Controller::iobit() -> bool {
   switch(port) {
   case ID::Port::Controller1: return cpu.pio() & 0x40;
diff --git a/waterbox/bsnescore/bsnes/sfc/controller/controller.hpp b/waterbox/bsnescore/bsnes/sfc/controller/controller.hpp
index 96bc7c2e13..b8d76167f4 100644
--- a/waterbox/bsnescore/bsnes/sfc/controller/controller.hpp
+++ b/waterbox/bsnescore/bsnes/sfc/controller/controller.hpp
@@ -18,11 +18,14 @@ struct Controller {
   auto iobit() -> bool;
   auto iobit(bool data) -> void;
   virtual auto data() -> uint2 { return 0; }
-  virtual auto latch(bool data) -> void {}
+  virtual auto latch(bool data) -> void;
   virtual auto latch() -> void {}  //light guns
   virtual auto draw(uint16_t* output, uint pitch, uint width, uint height) -> void {}  //light guns
 
   const uint port;
+
+protected:
+  bool latched;
 };
 
 struct ControllerPort {
diff --git a/waterbox/bsnescore/bsnes/sfc/controller/gamepad/gamepad.cpp b/waterbox/bsnescore/bsnes/sfc/controller/gamepad/gamepad.cpp
index 39067ff3e0..087f4ad5e7 100644
--- a/waterbox/bsnescore/bsnes/sfc/controller/gamepad/gamepad.cpp
+++ b/waterbox/bsnescore/bsnes/sfc/controller/gamepad/gamepad.cpp
@@ -33,6 +33,7 @@ auto Gamepad::latch(bool data) -> void {
   counter = 0;
 
   if(latched == 0) {
+    if (port == ID::Port::Controller1) platform->notify("LATCH");
     b      = platform->inputPoll(port, ID::Device::Gamepad, B);
     y      = platform->inputPoll(port, ID::Device::Gamepad, Y);
     select = platform->inputPoll(port, ID::Device::Gamepad, Select);
diff --git a/waterbox/bsnescore/bsnes/sfc/controller/gamepad/gamepad.hpp b/waterbox/bsnescore/bsnes/sfc/controller/gamepad/gamepad.hpp
index b28c7ba274..cba6b43e74 100644
--- a/waterbox/bsnescore/bsnes/sfc/controller/gamepad/gamepad.hpp
+++ b/waterbox/bsnescore/bsnes/sfc/controller/gamepad/gamepad.hpp
@@ -9,7 +9,6 @@ struct Gamepad : Controller {
   auto latch(bool data) -> void;
 
 private:
-  bool latched;
   uint counter;
 
   boolean b, y, select, start;
diff --git a/waterbox/bsnescore/bsnes/sfc/controller/justifier/justifier.hpp b/waterbox/bsnescore/bsnes/sfc/controller/justifier/justifier.hpp
index b5923b7e24..3fce5449f2 100644
--- a/waterbox/bsnescore/bsnes/sfc/controller/justifier/justifier.hpp
+++ b/waterbox/bsnescore/bsnes/sfc/controller/justifier/justifier.hpp
@@ -13,7 +13,6 @@ struct Justifier : Controller {
 //private:
   const bool chained;  //true if the second justifier is attached to the first
   const uint device;
-  bool latched;
   uint counter;
   uint prev;
 
diff --git a/waterbox/bsnescore/bsnes/sfc/controller/mouse/mouse.cpp b/waterbox/bsnescore/bsnes/sfc/controller/mouse/mouse.cpp
index 88e450c51f..10ba3e49b8 100644
--- a/waterbox/bsnescore/bsnes/sfc/controller/mouse/mouse.cpp
+++ b/waterbox/bsnescore/bsnes/sfc/controller/mouse/mouse.cpp
@@ -64,6 +64,8 @@ auto Mouse::latch(bool data) -> void {
   latched = data;
   counter = 0;
 
+  // TODO: should the code below be guarded by a `if (latched == 0)` as well?
+  if (port == ID::Port::Controller1) platform->notify("LATCH");
   x = platform->inputPoll(port, ID::Device::Mouse, X);  //-n = left, 0 = center, +n = right
   y = platform->inputPoll(port, ID::Device::Mouse, Y);  //-n = up,   0 = center, +n = down
   l = platform->inputPoll(port, ID::Device::Mouse, Left);
diff --git a/waterbox/bsnescore/bsnes/sfc/controller/mouse/mouse.hpp b/waterbox/bsnescore/bsnes/sfc/controller/mouse/mouse.hpp
index a094d23e6b..b99aa344e8 100644
--- a/waterbox/bsnescore/bsnes/sfc/controller/mouse/mouse.hpp
+++ b/waterbox/bsnescore/bsnes/sfc/controller/mouse/mouse.hpp
@@ -9,7 +9,6 @@ struct Mouse : Controller {
   auto latch(bool data) -> void;
 
 private:
-  bool latched;
   uint counter;
 
   uint speed;  //0 = slow, 1 = normal, 2 = fast
diff --git a/waterbox/bsnescore/bsnes/sfc/controller/super-multitap/super-multitap.hpp b/waterbox/bsnescore/bsnes/sfc/controller/super-multitap/super-multitap.hpp
index f78cdf1c3d..76ed4e41fd 100644
--- a/waterbox/bsnescore/bsnes/sfc/controller/super-multitap/super-multitap.hpp
+++ b/waterbox/bsnescore/bsnes/sfc/controller/super-multitap/super-multitap.hpp
@@ -11,7 +11,6 @@ struct SuperMultitap : Controller {
 private:
   bool isPayloadController;
   uint device;
-  bool latched;
   uint counter1;
   uint counter2;
 
diff --git a/waterbox/bsnescore/bsnes/sfc/controller/super-scope/super-scope.hpp b/waterbox/bsnescore/bsnes/sfc/controller/super-scope/super-scope.hpp
index ffa475f7fc..249512558a 100644
--- a/waterbox/bsnescore/bsnes/sfc/controller/super-scope/super-scope.hpp
+++ b/waterbox/bsnescore/bsnes/sfc/controller/super-scope/super-scope.hpp
@@ -11,7 +11,6 @@ struct SuperScope : Controller {
   auto draw(uint16_t* data, uint pitch, uint width, uint height) -> void override;
 
 private:
-  bool latched;
   uint counter;
 
   int x;
diff --git a/waterbox/bsnescore/bsnes/sfc/coprocessor/icd/icd.cpp b/waterbox/bsnescore/bsnes/sfc/coprocessor/icd/icd.cpp
index 95e2bb5ab0..5f26d1c122 100644
--- a/waterbox/bsnescore/bsnes/sfc/coprocessor/icd/icd.cpp
+++ b/waterbox/bsnescore/bsnes/sfc/coprocessor/icd/icd.cpp
@@ -24,7 +24,7 @@ namespace SameBoy {
   static auto joyp_write(GB_gameboy_t*, uint8_t value) -> void {
     bool p14 = value & 0x10;
     bool p15 = value & 0x20;
-	if (!p14 || !p15) platform->notify("NOTIFY NO_LAG_SGB");
+	if (!p14 || !p15) platform->notify("NO_LAG_SGB");
     icd.joypWrite(p14, p15);
   }
 
diff --git a/waterbox/bsnescore/bsnes/sfc/cpu/io.cpp b/waterbox/bsnescore/bsnes/sfc/cpu/io.cpp
index b6490ff3f5..f08102ac05 100644
--- a/waterbox/bsnescore/bsnes/sfc/cpu/io.cpp
+++ b/waterbox/bsnescore/bsnes/sfc/cpu/io.cpp
@@ -16,7 +16,7 @@ auto CPU::readCPU(uint addr, uint8 data) -> uint8 {
   case 0x4016:  //JOYSER0
     data &= 0xfc;
     data |= controllerPort1.device->data();
-    if (!io.autoJoypadPoll) platform->notify("NOTIFY NO_LAG");
+    if (!io.autoJoypadPoll) platform->notify("NO_LAG");
     return data;
 
   //todo: it is not known what happens when reading from this register during auto-joypad polling
@@ -24,7 +24,7 @@ auto CPU::readCPU(uint addr, uint8 data) -> uint8 {
     data &= 0xe0;
     data |= 0x1c;  //pins are connected to GND
     data |= controllerPort2.device->data();
-    if (!io.autoJoypadPoll) platform->notify("NOTIFY NO_LAG");
+    if (!io.autoJoypadPoll) platform->notify("NO_LAG");
     return data;
 
   case 0x4210:  //RDNMI
@@ -53,14 +53,14 @@ auto CPU::readCPU(uint addr, uint8 data) -> uint8 {
   case 0x4217: return io.rdmpy >> 8;  //RDMPYH
 
   //todo: it is not known what happens when reading from these registers during auto-joypad polling
-  case 0x4218: platform->notify("NOTIFY NO_LAG"); return io.joy1 >> 0;   //JOY1L
-  case 0x4219: platform->notify("NOTIFY NO_LAG"); return io.joy1 >> 8;   //JOY1H
-  case 0x421a: platform->notify("NOTIFY NO_LAG"); return io.joy2 >> 0;   //JOY2L
-  case 0x421b: platform->notify("NOTIFY NO_LAG"); return io.joy2 >> 8;   //JOY2H
-  case 0x421c: platform->notify("NOTIFY NO_LAG"); return io.joy3 >> 0;   //JOY3L
-  case 0x421d: platform->notify("NOTIFY NO_LAG"); return io.joy3 >> 8;   //JOY3H
-  case 0x421e: platform->notify("NOTIFY NO_LAG"); return io.joy4 >> 0;   //JOY4L
-  case 0x421f: platform->notify("NOTIFY NO_LAG"); return io.joy4 >> 8;   //JOY4H
+  case 0x4218: platform->notify("NO_LAG"); return io.joy1 >> 0;   //JOY1L
+  case 0x4219: platform->notify("NO_LAG"); return io.joy1 >> 8;   //JOY1H
+  case 0x421a: platform->notify("NO_LAG"); return io.joy2 >> 0;   //JOY2L
+  case 0x421b: platform->notify("NO_LAG"); return io.joy2 >> 8;   //JOY2H
+  case 0x421c: platform->notify("NO_LAG"); return io.joy3 >> 0;   //JOY3L
+  case 0x421d: platform->notify("NO_LAG"); return io.joy3 >> 8;   //JOY3H
+  case 0x421e: platform->notify("NO_LAG"); return io.joy4 >> 0;   //JOY4L
+  case 0x421f: platform->notify("NO_LAG"); return io.joy4 >> 8;   //JOY4H
 
   }
 
diff --git a/waterbox/bsnescore/bsnes/target-bsnescore/bsnescore.cpp b/waterbox/bsnescore/bsnes/target-bsnescore/bsnescore.cpp
index ef3503e72e..c7b54e3570 100644
--- a/waterbox/bsnescore/bsnes/target-bsnescore/bsnescore.cpp
+++ b/waterbox/bsnescore/bsnes/target-bsnescore/bsnescore.cpp
@@ -169,12 +169,6 @@ EXPORT void snes_reset(void)
 // note: run with runahead doesn't work yet, i suspect it's due to the serialize thing breaking (cause of libco)
 EXPORT void snes_run(void)
 {
-    snesCallbacks.snes_input_poll();
-
-    // TODO: I currently have implemented separate poll and state calls, where poll updates the state and the state call just receives this
-    // based on the way this is implemented this approach might be useless in terms of reducing polling load, will need confirmation here
-    // the runahead feature should also be considered in case this is ever implemented and works
-
     emulator->run();
 }
 
diff --git a/waterbox/bsnescore/bsnes/target-bsnescore/callbacks.h b/waterbox/bsnescore/bsnes/target-bsnescore/callbacks.h
index 3c3b092625..b0008eb740 100644
--- a/waterbox/bsnescore/bsnes/target-bsnescore/callbacks.h
+++ b/waterbox/bsnescore/bsnes/target-bsnescore/callbacks.h
@@ -3,11 +3,11 @@
 
 #include <stdint.h>
 
-typedef void (*snes_input_poll_t)(void);
-typedef int16_t (*snes_input_state_t)(int port, int index, int id);
-typedef void (*snes_no_lag_t)(bool sgb_poll);
 typedef void (*snes_video_frame_t)(const uint16_t* data, int width, int height, int pitch);
 typedef void (*snes_audio_sample_t)(int16_t left, int16_t right);
+typedef int16_t (*snes_input_poll_t)(int port, int index, int id);
+typedef void (*snes_controller_latch_t)(void);
+typedef void (*snes_no_lag_t)(bool sgb_poll);
 typedef char* (*snes_path_request_t)(int slot, const char* hint, int required);
 typedef void (*snes_trace_t)(const char* disassembly, const char* register_info);
 typedef void (*snes_read_hook_t)(uint32_t address);
@@ -15,11 +15,11 @@ typedef void (*snes_write_hook_t)(uint32_t address, uint8_t value);
 typedef void (*snes_exec_hook_t)(uint32_t address);
 
 struct SnesCallbacks {
-    snes_input_poll_t snes_input_poll;
-    snes_input_state_t snes_input_state;
-    snes_no_lag_t snes_no_lag;
     snes_video_frame_t snes_video_frame;
     snes_audio_sample_t snes_audio_sample;
+    snes_input_poll_t snes_input_poll;
+    snes_controller_latch_t snes_controller_latch;
+    snes_no_lag_t snes_no_lag;
     snes_path_request_t snes_path_request;
     snes_trace_t snes_trace;
     snes_read_hook_t snes_read_hook;
diff --git a/waterbox/bsnescore/bsnes/target-bsnescore/program.cpp b/waterbox/bsnescore/bsnes/target-bsnescore/program.cpp
index e82e41cc71..c0438e314b 100644
--- a/waterbox/bsnescore/bsnes/target-bsnescore/program.cpp
+++ b/waterbox/bsnescore/bsnes/target-bsnescore/program.cpp
@@ -447,10 +447,12 @@ auto Program::audioFrame(const double* samples, uint channels) -> void
 
 auto Program::notify(string message) -> void
 {
-	if (message == "NOTIFY NO_LAG")
+	if (message == "NO_LAG")
 		snesCallbacks.snes_no_lag(false);
-	else if (message == "NOTIFY NO_LAG_SGB")
+	else if (message == "NO_LAG_SGB")
 		snesCallbacks.snes_no_lag(true);
+	else if (message == "LATCH")
+		snesCallbacks.snes_controller_latch();
 }
 
 auto Program::cpuTrace(vector<string> parts) -> void
@@ -493,7 +495,7 @@ auto Program::inputPoll(uint port, uint device, uint input) -> int16
 		id = input % 4;
 	}
 
-	return snesCallbacks.snes_input_state(port, index, id);
+	return snesCallbacks.snes_input_poll(port, index, id);
 }
 
 auto Program::inputRumble(uint port, uint device, uint input, bool enable) -> void