diff --git a/python_payload/apps/cap_touch_demo.py b/python_payload/apps/cap_touch_demo.py
index c1d9c89eb7e8f2220c7fce0edfe03ff358e90cd0..8a866b7f164717f32a763cc355c80a4016267a28 100644
--- a/python_payload/apps/cap_touch_demo.py
+++ b/python_payload/apps/cap_touch_demo.py
@@ -1,4 +1,8 @@
 from st3m import logging
+from st4m import application
+from st4m.goose import List
+from st4m.ui.ctx import Ctx
+from st4m.input import InputState
 
 log = logging.Log(__name__, level=logging.INFO)
 log.info("import")
@@ -8,16 +12,15 @@ import math
 import time
 
 import hardware
-from st4m import application
 
 
 class Dot:
-    def __init__(self, size, imag, real):
+    def __init__(self, size: float, imag: float, real: float) -> None:
         self.size = size
         self.imag = imag
         self.real = real
 
-    def draw(self, i, ctx):
+    def draw(self, i: int, ctx: Ctx) -> None:
         imag = self.imag
         real = self.real
         size = self.size
@@ -31,9 +34,9 @@ class Dot:
 
 
 class CapTouchDemo(application.Application):
-    def __init__(self, name):
+    def __init__(self, name: str) -> None:
         super().__init__(name)
-        self.dots = []
+        self.dots: List[Dot] = []
         self.last_calib = None
         # self.ui_autocalib = ui.IconLabel("Autocalib done", size=30)
 
@@ -52,36 +55,35 @@ class CapTouchDemo(application.Application):
     def think(self, ins: InputState, delta_ms: int) -> None:
         super().think(ins, delta_ms)
         self.dots = []
-        cps = captouch.read()
         for i in range(10):
-            petal = cps.petals[i]
-            (rad, phi) = petal.position
+            (rad, phi) = ins.petal_pos[i]
             size = 4
-            if petal.pressed:
+            if ins.petal_pressed[i]:
                 size += 4
-            x = 70 + (rad / 1000)
+            x = 70 + (rad / 1000) + 0j
             x += (phi / 600) * 1j
             rot = cmath.exp(2j * math.pi * i / 10)
             x = x * rot
 
             self.dots.append(Dot(size, x.imag, x.real))
 
-    def draw(self, ctx):
+    def draw(self, ctx: Ctx) -> None:
         # print(self.last_calib)
 
         # TODO (q3k) bug: we have to do this, otherwise we have horrible blinking
         ctx.rgb(1, 1, 1)
 
         ctx.rgb(0, 0, 0).rectangle(-120, -120, 240, 240).fill()
+        for i, dot in enumerate(self.dots):
             dot.draw(i, ctx)
         # if not self.last_calib is None and self.last_calib > 0:
         #    self.last_calib -= 1
         #    self.ui_autocalib.draw(ctx)
 
-    def do_autocalib(self, data):
-        log.info("Performing captouch autocalibration")
-        captouch.calibration_request()
-        self.last_calib = 50
+    # def do_autocalib(self, data) -> None:
+    #    log.info("Performing captouch autocalibration")
+    #    captouch.calibration_request()
+    #    self.last_calib = 50
 
 
 app = CapTouchDemo("cap touch")
diff --git a/python_payload/apps/demo_worms4.py b/python_payload/apps/demo_worms4.py
index 74261e3a9f8756bd2ff61f89548c40b4a7df64c2..b0d9de8c66a197077a1f87a9faeefea2f2ffbae3 100644
--- a/python_payload/apps/demo_worms4.py
+++ b/python_payload/apps/demo_worms4.py
@@ -4,9 +4,11 @@ import time
 import math
 
 # flow3r imports
-from st3m import event, ui
 from st4m import application
 from st4m import Ctx, InputState
+from st4m.property import BLUE, WHITE
+from st4m.goose import Optional
+from st4m.utils import xy_from_polar
 
 
 tau = 2 * math.pi
@@ -14,7 +16,7 @@ tau = 2 * math.pi
 
 # Subclass Application
 class AppWorms(application.Application):
-    def __init__(self, name):
+    def __init__(self, name: str) -> None:
         super().__init__(name)
 
         # HACK: we work against double buffering by keeping note of how many
@@ -38,32 +40,26 @@ class AppWorms(application.Application):
 
         self.just_shown = True
 
-    def on_enter(self):
+    def on_enter(self) -> None:
         # print("on foreground")
         super().on_enter()
         self.just_shown = True
 
-    def draw(self, ctx):
+    def draw(self, ctx: Ctx) -> None:
         if self.bufn <= 5:
             # TODO (q3k) bug: we have to do this, otherwise we have horrible blinking
 
-            ctx.rgb(*ui.BLUE).rectangle(
-                -ui.WIDTH / 2, -ui.HEIGHT / 2, ui.WIDTH, ui.HEIGHT
-            ).fill()
+            ctx.rgb(*BLUE).rectangle(-120, -120, 240, 240).fill()
             ctx.rgb(1, 1, 1)
 
-            ctx.rgb(*ui.BLUE).rectangle(
-                -ui.WIDTH / 2, -ui.HEIGHT / 2, ui.WIDTH, ui.HEIGHT
-            ).fill()
+            ctx.rgb(*BLUE).rectangle(-120, -120, 240, 240).fill()
             ctx.rgb(1, 1, 1)
 
-            ctx.rgb(*ui.BLUE).rectangle(
-                -ui.WIDTH / 2, -ui.HEIGHT / 2, ui.WIDTH, ui.HEIGHT
-            ).fill()
+            ctx.rgb(*BLUE).rectangle(-120, -120, 240, 240).fill()
 
             ctx.text_align = ctx.CENTER
             ctx.text_baseline = ctx.MIDDLE
-            ctx.move_to(0, 0).rgb(*ui.WHITE).text("touch me :)")
+            ctx.move_to(0, 0).rgb(*WHITE).text("touch me :)")
             self.bufn += 1
 
             return
@@ -83,32 +79,32 @@ class AppWorms(application.Application):
         for index, petal in enumerate(self.input.captouch.petals):
             if petal.pressed or petal.repeated:
                 self.worms.append(Worm(tau * index / 10 + math.pi))
+        while len(self.worms) > 10:
+            self.worms.pop(0)
 
-    def handle_input(self, data):
-        worms = self.worms
-        worms.append(Worm(data.get("index", 0) * 2 * math.pi / 10 + math.pi))
-        while len(worms) > 10:
-            worms.pop(0)
+
+def randrgb() -> tuple[float, float, float]:
+    return (random.random(), random.random(), random.random())
 
 
 class Worm:
-    def __init__(self, direction=None):
-        self.color = ui.randrgb()
+    def __init__(self, direction: Optional[float] = None) -> None:
+        self.color = randrgb()
 
-        if direction:
+        if direction is not None:
             self.direction = direction
         else:
             self.direction = random.random() * math.pi * 2
 
         self.size = 50
         self.speed = self.size / 5
-        (x, y) = ui.xy_from_polar(100, self.direction)
+        (x, y) = xy_from_polar(100, self.direction)
         self.x = x
         self.y = y
         # (self.dx,self.dy) = xy_from_polar(1,self.direction)
         self._lastdist = 0.0
 
-    def draw(self, ctx):
+    def draw(self, ctx: Ctx) -> None:
         ctx.rgb(*self.color)
         ctx.round_rectangle(
             self.x - self.size / 2,
@@ -118,12 +114,14 @@ class Worm:
             self.size // 2,
         ).fill()
 
-    def mutate(self):
-        self.color = [
-            max(0, min(1, x + ((random.random() - 0.5) * 0.3))) for x in self.color
-        ]
+    def mutate(self) -> None:
+        self.color = (
+            max(0, min(1, self.color[0] + ((random.random() - 0.5) * 0.3))),
+            max(0, min(1, self.color[1] + ((random.random() - 0.5) * 0.3))),
+            max(0, min(1, self.color[2] + ((random.random() - 0.5) * 0.3))),
+        )
 
-    def move(self):
+    def move(self) -> None:
         dist = math.sqrt(self.x**2 + self.y**2)
         target_size = (130 - dist) / 3
 
@@ -137,7 +135,7 @@ class Worm:
 
         self.direction += (random.random() - 0.5) * math.pi / 4
 
-        (dx, dy) = ui.xy_from_polar(self.speed, self.direction)
+        (dx, dy) = xy_from_polar(self.speed, self.direction)
         self.x += dx
         self.y += dy
 
diff --git a/python_payload/apps/harmonic_demo.py b/python_payload/apps/harmonic_demo.py
index ec9ac5305bbc24e2a16b92129b13f7874000fa31..46398229f4992fe7e078a5a6b94d651504f5d2c3 100644
--- a/python_payload/apps/harmonic_demo.py
+++ b/python_payload/apps/harmonic_demo.py
@@ -1,7 +1,11 @@
 from bl00mbox import tinysynth
-from hardware import *
 import captouch
 import leds
+import hardware
+
+from st4m.ui.ctx import Ctx
+from st4m.goose import List
+from st4m.input import InputState
 
 chords = [
     [-4, 0, 3, 8, 10],
@@ -16,12 +20,12 @@ from st4m.application import Application
 
 
 class HarmonicApp(Application):
-    def __init__(self, name):
+    def __init__(self, name: str) -> None:
         super().__init__(name)
 
-        self.color_intensity = 0
-        self.chord_index = None
-        self.chord = None
+        self.color_intensity = 0.0
+        self.chord_index = 0
+        self.chord: List[int] = []
         self.synths = [tinysynth(440) for i in range(15)]
         for i, synth in enumerate(self.synths):
             synth.decay_ms(100)
@@ -44,7 +48,7 @@ class HarmonicApp(Application):
                 synth.release_ms(800)
         self._set_chord(3)
 
-    def _set_chord(self, i):
+    def _set_chord(self, i: int) -> None:
         hue = int(72 * (i + 0.5)) % 360
         if i != self.chord_index:
             self.chord_index = i
@@ -53,12 +57,12 @@ class HarmonicApp(Application):
             self.chord = chords[i]
             leds.update()
 
-    def draw(self, ctx):
+    def draw(self, ctx: Ctx) -> None:
         i = self.color_intensity
         ctx.rgb(i, i, i).rectangle(-120, -120, 240, 240).fill()
 
         ctx.rgb(0, 0, 0)
-        scope_draw(ctx)
+        hardware.scope_draw(ctx)
         ctx.fill()
 
     def think(self, ins: InputState, delta_ms: int) -> None:
diff --git a/python_payload/apps/melodic_demo.py b/python_payload/apps/melodic_demo.py
index c1496508385418ecfd5bbd649cc5a2c34c897222..78000e5dd3dfa0a5e71fcc7f5b6d829670edc470 100644
--- a/python_payload/apps/melodic_demo.py
+++ b/python_payload/apps/melodic_demo.py
@@ -3,18 +3,22 @@ from hardware import *
 import captouch
 import leds
 
+from st4m.goose import List
+from st4m.input import InputState
+from st4m.ui.ctx import Ctx
+
 octave = 0
-synths = []
+synths: List[tinysynth] = []
 scale = [0, 2, 4, 5, 7, 9, 11]
 
 
-def highlight_bottom_petal(num, r, g, b):
+def highlight_bottom_petal(num: int, r: int, g: int, b: int) -> None:
     start = 4 + 8 * num
     for i in range(7):
         leds.set_rgb(((i + start) % 40), r, g, b)
 
 
-def change_playing_field_color(r, g, b):
+def change_playing_field_color(r: int, g: int, b: int) -> None:
     highlight_bottom_petal(0, r, g, b)
     highlight_bottom_petal(1, r, g, b)
     highlight_bottom_petal(3, r, g, b)
@@ -27,7 +31,7 @@ def change_playing_field_color(r, g, b):
     leds.update()
 
 
-def adjust_playing_field_to_octave():
+def adjust_playing_field_to_octave() -> None:
     global octave
     if octave == -1:
         change_playing_field_color(0, 0, 55)
@@ -37,13 +41,12 @@ def adjust_playing_field_to_octave():
         change_playing_field_color(0, 55, 0)
 
 
-def run():
+def run(ins: InputState) -> None:
     global scale
     global octave
     global synths
-    cts = captouch.read()
     for i in range(10):
-        if cts.petals[i].pressed:
+        if ins.petal_pressed[i]:
             if i == 4:
                 octave = -1
                 adjust_playing_field_to_octave()
@@ -63,7 +66,7 @@ def run():
                 synths[0].start()
 
 
-def init():
+def init() -> None:
     global synths
     for i in range(1):
         synths += [tinysynth(440)]
@@ -71,30 +74,31 @@ def init():
         synth.decay_ms(100)
 
 
-def foreground():
+def foreground() -> None:
     adjust_playing_field_to_octave()
 
 
 from st4m.application import Application
 
 
+# TODO(q3k): properly port this app
 class MelodicApp(Application):
-    def __init__(self, name):
+    def __init__(self, name: str) -> None:
         super().__init__(name)
         init()
 
-    def draw(self, ctx):
+    def draw(self, ctx: Ctx) -> None:
         ctx.rgb(1, 1, 1).rectangle(-120, -120, 240, 240).fill()
         ctx.rgb(0, 0, 0)
         scope_draw(ctx)
         ctx.fill()
 
-    def on_enter(self):
+    def on_enter(self) -> None:
         foreground()
 
     def think(self, ins: InputState, delta_ms: int) -> None:
         super().think(ins, delta_ms)
-        run()
+        run(ins)
 
 
 app = MelodicApp("melodic")
diff --git a/python_payload/apps/nick.py b/python_payload/apps/nick.py
index e4b2df6e2ca5177607bf865116b822aee6c6622d..085f6cdfb4fd7b412f4ed273e295d0e610b16cc2 100644
--- a/python_payload/apps/nick.py
+++ b/python_payload/apps/nick.py
@@ -1,30 +1,65 @@
 from st4m.application import Application
 from st4m.property import PUSH_RED, GO_GREEN, BLACK
+from st4m.goose import Dict, Any
+from st4m.ui.ctx import Ctx
+from st4m.input import InputState
 import leds
 
 import json
 
 
+class Configuration:
+    def __init__(self) -> None:
+        self.name = "flow3r"
+        self.size: int = 75
+        self.font: int = 5
+
+    @classmethod
+    def load(cls, path: str) -> "Configuration":
+        res = cls()
+        try:
+            with open(path) as f:
+                jsondata = f.read()
+            data = json.loads(jsondata)
+        except OSError:
+            data = {}
+        if "name" in data and type(data["name"]) == str:
+            res.name = data["name"]
+        if "size" in data:
+            if type(data["size"]) == float:
+                res.size = int(data["size"])
+            if type(data["size"]) == int:
+                res.size = data["size"]
+        if "font" in data and type(data["font"]) == int:
+            res.font = data["font"]
+        return res
+
+    def save(self, path: str) -> None:
+        d = {
+            "name": self.name,
+            "size": self.size,
+            "font": self.font,
+        }
+        jsondata = json.dumps(d)
+        with open(path, "w") as f:
+            f.write(jsondata)
+            f.close()
+
+
 class NickApp(Application):
-    def __init__(self, name):
+    def __init__(self, name: str) -> None:
         super().__init__(name)
-        self._scale = 1
+        self._scale = 1.0
         self._dir = 1
         self._led = 0.0
-        self._filename = "nick.json"
-        self._defaults = {
-            "name": "flow3r",
-            "size": 75,
-            "font": 5,
-        }
-        self._data = {}
-        self._load()
+        self._filename = "/flash/nick.json"
+        self._config = Configuration.load(self._filename)
 
-    def draw(self, ctx):
+    def draw(self, ctx: Ctx) -> None:
         ctx.text_align = ctx.CENTER
         ctx.text_baseline = ctx.MIDDLE
-        ctx.font_size = self._data["size"]
-        ctx.font = ctx.get_font_name(self._data["font"])
+        ctx.font_size = self._config.size
+        ctx.font = ctx.get_font_name(self._config.font)
 
         # TODO (q3k) bug: we have to do this, otherwise we have horrible blinking
         ctx.rgb(1, 1, 1)
@@ -36,7 +71,7 @@ class NickApp(Application):
         ctx.move_to(0, 0)
         ctx.save()
         ctx.scale(self._scale, 1)
-        ctx.text(self._data["name"])
+        ctx.text(self._config.name)
         ctx.restore()
 
         leds.set_hsv(int(self._led), abs(self._scale) * 360, 1, 0.2)
@@ -44,11 +79,8 @@ class NickApp(Application):
         leds.update()
         # ctx.fill()
 
-    def on_enter(self):
-        super().on_enter()
-
-    def on_exit(self):
-        self._save_to_json(self._data)
+    def on_exit(self) -> None:
+        self._config.save(self._filename)
 
     def think(self, ins: InputState, delta_ms: int) -> None:
         super().think(ins, delta_ms)
@@ -62,27 +94,5 @@ class NickApp(Application):
         if self._led >= 40:
             self._led = 0
 
-    def _load(self):
-        self._data = self._defaults.copy()
-        self._data.update(self._load_from_json())
-
-    def _load_from_json(self):
-        jsondata = ""
-        data = {}
-        try:
-            with open(self._filename) as f:
-                jsondata = f.read()
-            data = json.loads(jsondata)
-        except OSError:
-            pass
-        return data
-
-    def _save_to_json(self, data):
-        jsondata = json.dumps(data)
-
-        with open(self._filename, "w") as f:
-            f.write(jsondata)
-            f.close()
-
 
 app = NickApp("nick")
diff --git a/python_payload/main.py b/python_payload/main.py
index 1807af46193c8d20e0440f4007d7d6e66fdf9215..76cbbda2f40b5cb86eb20ee3dcfc09ef75467a7a 100644
--- a/python_payload/main.py
+++ b/python_payload/main.py
@@ -44,7 +44,7 @@ from apps.demo_worms4 import app as worms
 from apps.harmonic_demo import app as harmonic
 from apps.melodic_demo import app as melodic
 from apps.nick import app as nick
-from apps.cap_touch_demo import app as captouch
+from apps.cap_touch_demo import app as captouch_demo
 
 
 # Set the view_managers for the apps, otherwise leaving the app (back) will not work
@@ -52,7 +52,7 @@ worms._view_manager = vm
 harmonic._view_manager = vm
 melodic._view_manager = vm
 nick._view_manager = vm
-captouch._view_manager = vm
+captouch_demo._view_manager = vm
 
 menu_music = SimpleMenu(
     [
@@ -69,7 +69,7 @@ menu_music = SimpleMenu(
 menu_apps = SimpleMenu(
     [
         MenuItemBack(),
-        MenuItemForeground("captouch", captouch),
+        MenuItemForeground("captouch", captouch_demo),
         MenuItemForeground("worms", worms),
     ],
     vm,
diff --git a/python_payload/main_st4m.py b/python_payload/main_st4m.py
index 04aa335d21a94119dc3be8e240c2875c2417f583..437e8e2b02984326a3e7e83a377f7792ede1602c 100644
--- a/python_payload/main_st4m.py
+++ b/python_payload/main_st4m.py
@@ -47,6 +47,7 @@ menu_apps = SimpleMenu(
     vm,
 )
 
+
 class USBIcon(Responder):
     """
     Found in the bargain bin at an Aldi Süd.
diff --git a/python_payload/st3m/logging.py b/python_payload/st3m/logging.py
index 7ff504555e892757792978aea5a8a36075a8d8a6..a5e37649846aea70c8a8b7dbc1dd0653dc118dc8 100644
--- a/python_payload/st3m/logging.py
+++ b/python_payload/st3m/logging.py
@@ -1,43 +1,63 @@
 import sys
 import time
+from st4m.goose import Enum
 
-DEBUG = 0
-INFO = 1
-WARNING = 2
-ERROR = 3
 
-# this is so ugly, but works
-levels = ["DEBUG", "INFO", "WARNING", "ERROR"]
+class Level(Enum):
+    DEBUG = 0
+    INFO = 1
+    WARNING = 2
+    ERROR = 3
+
+
+DEBUG = Level.DEBUG
+INFO = Level.INFO
+WARNING = Level.WARNING
+ERROR = Level.ERROR
+
+_log_levels = {
+    DEBUG: 0,
+    INFO: 1,
+    WARNING: 2,
+    ERROR: 3,
+}
+
+_log_level_names = {
+    DEBUG: "DEBUG",
+    INFO: "INFO",
+    WARNING: "WARNING",
+    ERROR: "ERROR",
+}
 
 
 class Log:
-    def __init__(self, name="log", level=INFO):
+    def __init__(self, name: str = "log", level: Level = INFO):
         self.name = name
         self.level = level
         self.logstring = "{timestamp} {name} ({level}): {msg}"
 
-    def debug(self, msg):
+    def debug(self, msg: str) -> None:
         self.message(msg, DEBUG)
 
-    def info(self, msg):
+    def info(self, msg: str) -> None:
         self.message(msg, INFO)
 
-    def warning(self, msg):
+    def warning(self, msg: str) -> None:
         self.message(msg, WARNING)
 
-    def error(self, msg):
+    def error(self, msg: str) -> None:
         self.message(msg, ERROR)
 
-    def message(self, msg, level):
-        if self.level <= level:
+    def message(self, msg: str, level: Level) -> None:
+        if _log_levels[self.level] <= _log_levels[level]:
             self._emit(
                 self.logstring.format(
                     timestamp=time.ticks_ms() / 1000,
                     name=self.name,
                     msg=msg,
-                    level=levels[level],
+                    level=_log_level_names[level],
                 )
             )
 
-    def _emit(self, line):
+    def _emit(self, line: str) -> None:
         print(line)
diff --git a/python_payload/st3m/system/__init__.py b/python_payload/st3m/system/__init__.py
index f2512322c2b05e13b73849779ca80d1a941cfcfd..256751f87b74bc15b48b9b0ccb629204a6999e35 100644
--- a/python_payload/st3m/system/__init__.py
+++ b/python_payload/st3m/system/__init__.py
@@ -1,41 +1,7 @@
 import hardware as _hardware
+import audio as _audio
 import captouch as _captouch
 
-
-class NamedObject:
-    def __init__(self, name="foo"):
-        self.__name = name
-
-    def __repr__(self):
-        return self.__name
-
-
-class MockObject(NamedObject):
-    def __getattr__(self, attr):
-        attr_name = "{}.{}".format(str(self), attr)
-        print("mock attr", attr_name)
-        return MockObject(attr_name)
-
-    def __call__(self, *args, **kwargs):
-        call_name = "{}({}{})".format(str(self), args, kwargs)
-        print("mock call", call_name)
-        return MockObject(call_name)
-
-
-try:
-    import audio as _audio
-except ModuleNotFoundError:
-    print("no real audio, using mock module")
-    _audio = MockObject("audio")
-
-
-try:
-    import audio as _audio
-except ModuleNotFoundError:
-    print("no real audio, using mock module")
-    _audio = MockObject("audio")
-
-
 hardware = _hardware
 captouch = _captouch
 audio = _audio
diff --git a/python_payload/st4m/application.py b/python_payload/st4m/application.py
index 5cb0255db7967a7a2d50f083eb83c4e14b57a759..165ab5fccca80f811e964e5b82d764c6784c1aed 100644
--- a/python_payload/st4m/application.py
+++ b/python_payload/st4m/application.py
@@ -1,14 +1,15 @@
-from st4m.ui.view import ViewWithInputState, ViewTransitionSwipeRight
+from st4m.ui.view import ViewWithInputState, ViewTransitionSwipeRight, ViewManager
 from st4m.input import InputState
+from st4m.goose import Optional
 
 
 class Application(ViewWithInputState):
-    def __init__(self, name: str = __name__):
+    def __init__(self, name: str = __name__) -> None:
         self._name = name
-        self._view_manager = None
+        self._view_manager: Optional[ViewManager] = None
         super().__init__()
 
-    def on_exit(self):
+    def on_exit(self) -> None:
         pass
 
     def think(self, ins: InputState, delta_ms: int) -> None:
diff --git a/python_payload/st4m/goose.py b/python_payload/st4m/goose.py
index bb1c0224894db616163d3cefba86b7897c6868f1..8fb29609b4f36d94518a1b54729918f629f4ef7a 100644
--- a/python_payload/st4m/goose.py
+++ b/python_payload/st4m/goose.py
@@ -17,7 +17,7 @@ if TYPE_CHECKING:
     class ABCBase(metaclass=ABCMeta):
         pass
 
-    from typing import List, Optional, Tuple
+    from typing import List, Optional, Tuple, Dict, Any
     from enum import Enum
 else:
     # We're in CPython or Micropython.
@@ -31,13 +31,15 @@ else:
         return _fail
 
     try:
-        from typing import List, Optional, Tuple
+        from typing import List, Optional, Tuple, Dict, Any
         from enum import Enum
     except ImportError:
         # We're in Micropython.
         List = None
         Optional = None
         Tuple = None
+        Dict = None
+        Any = None
 
         class Enum:
             pass
@@ -51,4 +53,6 @@ __all__ = [
     "Optional",
     "Enum",
     "Tuple",
+    "Dict",
+    "Any",
 ]
diff --git a/python_payload/st4m/input.py b/python_payload/st4m/input.py
index b982bfe62392e566c26354274739b925dc88a9a8..b85f918a762d464e46f9eefa6605ae88002fffcc 100644
--- a/python_payload/st4m/input.py
+++ b/python_payload/st4m/input.py
@@ -1,4 +1,4 @@
-from st4m.goose import List, Optional, Enum
+from st4m.goose import List, Optional, Enum, Tuple
 from st4m.ui.ctx import Ctx
 
 import hardware
@@ -19,7 +19,7 @@ class InputState:
         self,
         petal_pressed: List[bool],
         # petal_pads: List[List[int]],
-        petal_pos: List[List[int]],
+        petal_pos: List[Tuple[int, int]],
         left_button: int,
         right_button: int,
     ) -> None:
@@ -41,10 +41,7 @@ class InputState:
         #    [hardware.captouch_get_petal_pad(petal_ix, pad_ix) for pad_ix in range(3)]
         #    for petal_ix in range(10)
         # ]
-        petal_pos = [
-            ctx.petals[petal_ix].position
-            for petal_ix in range(10)
-        ]
+        petal_pos = [cts.petals[petal_ix].position for petal_ix in range(10)]
 
         left_button = hardware.left_button_get()
         right_button = hardware.right_button_get()
@@ -192,82 +189,82 @@ class PetalState(Pressable):
         super().__init__(False)
 
 
-class Touchable(Pressable):
-    """
-    An object that can receive touch gestures (captouch petal)
-    """
-
-    BEGIN = "begin"
-    RESTING = "resting"
-    MOVED = "moved"
-    ENDED = "ended"
-
-    def __init__(self, pos=(0, 0)):
-        super().__init__(False)
-        self._pos = pos
-        self._prev_pos = pos
-        self._polar = self._prev_polar = (0, 0)
-        self._dx = 0.0
-        self._dy = 0.0
-        self._dphi = 0.0
-        self._dr = 0.0
-
-    def _update(self, ts, state, pos):
-        self._prev_pos = self._pos
-        self._pos = pos
-
-        self._prev_polar = self._polar
-
-        self._dx = self._pos[0] - self._prev_pos[0]
-        self._dy = self._pos[1] - self._prev_pos[1]
-
-        x0 = -pos[0] / 500
-        x1 = pos[1] / 500
-
-        phi = math.atan2(x0, x1) - math.pi / 2
-        r = math.sqrt(x0 * x0 + x1 * x1)
-        self._polar = (r, phi)
-
-        self._dr = self._polar[0] - self._prev_polar[0]
-
-        v = self._polar[1] - self._prev_polar[1]
-
-        for sign in [1, -1]:
-            t = v + sign * 2 * math.pi
-            if abs(t) < abs(v):
-                v = t
-
-        self._dphi = v
-
-        super()._update(ts, state)
-        if self.state != self.DOWN:
-            self._dx = self._dy = self._dphi = self._dr = 0
-        else:
-            pass
-            # print(r, phi, self._dr, self._dphi)
-
-    def phase(self) -> str:
-        if self.state == self.UP:
-            return self.UP
-        if self.state == self.RELEASED:
-            return self.ENDED
-        if self.state == self.PRESSED:
-            return self.BEGIN
-        if self.state == self.DOWN or self.state == self.REPEATED:
-            if abs(self._dr) > 1 or abs(self._dphi) > 0.01:
-                return self.MOVED
-            else:
-                return self.RESTING
-        return "HUHUHU"
-
-
-class PetalGestureState(Touchable):
-    def __init__(self, ix: int) -> None:
-        self.ix = ix
-        super().__init__()
-
-    def _update(self, ts: int, hr: InputState) -> None:
-        super()._update(ts, hr.petal_pressed[self.ix], hr.petal_pos[self.ix])
+# class Touchable(Pressable):
+#    """
+#    An object that can receive touch gestures (captouch petal)
+#    """
+#
+#    BEGIN = "begin"
+#    RESTING = "resting"
+#    MOVED = "moved"
+#    ENDED = "ended"
+#
+#    def __init__(self, pos: tuple[int,int] = (0, 0)) -> None:
+#        super().__init__(False)
+#        self._pos = pos
+#        self._prev_pos = pos
+#        self._polar = self._prev_polar = (0, 0)
+#        self._dx = 0.0
+#        self._dy = 0.0
+#        self._dphi = 0.0
+#        self._dr = 0.0
+#
+#    def _update(self, ts, state, pos) -> None:
+#        self._prev_pos = self._pos
+#        self._pos = pos
+#
+#        self._prev_polar = self._polar
+#
+#        self._dx = self._pos[0] - self._prev_pos[0]
+#        self._dy = self._pos[1] - self._prev_pos[1]
+#
+#        x0 = -pos[0] / 500
+#        x1 = pos[1] / 500
+#
+#        phi = math.atan2(x0, x1) - math.pi / 2
+#        r = math.sqrt(x0 * x0 + x1 * x1)
+#        self._polar = (r, phi)
+#
+#        self._dr = self._polar[0] - self._prev_polar[0]
+#
+#        v = self._polar[1] - self._prev_polar[1]
+#
+#        for sign in [1, -1]:
+#            t = v + sign * 2 * math.pi
+#            if abs(t) < abs(v):
+#                v = t
+#
+#        self._dphi = v
+#
+#        super()._update(ts, state)
+#        if self.state != self.DOWN:
+#            self._dx = self._dy = self._dphi = self._dr = 0
+#        else:
+#            pass
+#            # print(r, phi, self._dr, self._dphi)
+#
+#    def phase(self) -> str:
+#        if self.state == self.UP:
+#            return self.UP
+#        if self.state == self.RELEASED:
+#            return self.ENDED
+#        if self.state == self.PRESSED:
+#            return self.BEGIN
+#        if self.state == self.DOWN or self.state == self.REPEATED:
+#            if abs(self._dr) > 1 or abs(self._dphi) > 0.01:
+#                return self.MOVED
+#            else:
+#                return self.RESTING
+#        return "HUHUHU"
+
+
+# class PetalGestureState(Touchable):
+#    def __init__(self, ix: int) -> None:
+#        self.ix = ix
+#        super().__init__()
+#
+#    def _update(self, ts: int, hr: InputState) -> None:
+#        super()._update(ts, hr.petal_pressed[self.ix], hr.petal_pos[self.ix])
 
 
 class CaptouchState:
@@ -373,14 +370,15 @@ class InputController:
         self.right_shoulder._ignore_pressed()
 
 
-class PetalController:
-    def __init__(self, ix):
-        self._ts = 0
-        self._input = PetalGestureState(ix)
-
-    def think(self, hr: InputState, delta_ms: int) -> None:
-        self._ts += delta_ms
-        self._input._update(self._ts, hr)
-
-    def _ignore_pressed(self) -> None:
-        self._input._ignore_pressed()
+# class PetalController:
+#    def __init__(self, ix):
+#        self._ts = 0
+#        self._input = PetalGestureState(ix)
+#
+#    def think(self, hr: InputState, delta_ms: int) -> None:
+#        self._ts += delta_ms
+#        self._input._update(self._ts, hr)
+#
+#    def _ignore_pressed(self) -> None:
+#        self._input._ignore_pressed()
+#
diff --git a/python_payload/st4m/property.py b/python_payload/st4m/property.py
index fa4292cf443d9f8d1fd1b82c340ebd8349b453ac..55311e5e5eef39a89afa8ac2eb40cf229b99db89 100644
--- a/python_payload/st4m/property.py
+++ b/python_payload/st4m/property.py
@@ -3,43 +3,42 @@ class Property:
 
 
 class PropertyFloat(Property):
-    def __init__(self):
+    def __init__(self) -> None:
         self.min = 0.0
         self.max = 1.0
 
-    def set_value(self, value):
+    def set_value(self, value: float) -> None:
         self._value = value
 
-    def mod_value(self, delta):
-        self._value += delta_ms
+    def mod_value(self, delta: float) -> None:
+        self._value += delta
 
-    def get_value(self):
+    def get_value(self) -> float:
         return self._value
 
 
 # Define a few RGB (0.0 to 1.0) colors
-BLACK = (0, 0, 0)
-RED = (1, 0, 0)
-GREEN = (0, 1, 0)
-BLUE = (0, 0, 1)
-WHITE = (1, 1, 1)
+BLACK = (0.0, 0.0, 0.0)
+RED = (1.0, 0.0, 0.0)
+GREEN = (0.0, 1.0, 0.0)
+BLUE = (0.0, 0.0, 1.0)
+WHITE = (1.0, 1.0, 1.0)
 GREY = (0.5, 0.5, 0.5)
 GO_GREEN = (63 / 255, 255 / 255, 33 / 255)
 PUSH_RED = (251 / 255, 72 / 255, 196 / 255)
 
 
-class Color:
-    @classmethod
-    def from_rgb888(cls, r: int = 0, g: int = 0, b: int = 0):
-        return cls(r / 255, g / 255, b / 255)
-
-    def __init__(self, r: float = 0.0, g: float = 0.0, b: float = 0.0) -> None:
-        self.r = r
-        self.g = g
-        self.b = b
-
-    def as_normal_tuple(self) -> tuple[float]:
-        return (self.r, self.g, self.b)
-
-
-print(Color.from_rgb888(255, 255, 128).as_normal_tuple())
+# Unused?
+# class Color:
+#    @classmethod
+#    def from_rgb888(cls, r: int = 0, g: int = 0, b: int = 0) -> Color:
+#        return cls(r / 255, g / 255, b / 255)
+#
+#    def __init__(self, r: float = 0.0, g: float = 0.0, b: float = 0.0) -> None:
+#        self.r = r
+#        self.g = g
+#        self.b = b
+#
+#    def as_normal_tuple(self) -> tuple[float, float, float]:
+#        return (self.r, self.g, self.b)
+#
diff --git a/python_payload/st4m/ui/ctx.py b/python_payload/st4m/ui/ctx.py
index ddd00768fcd721e8f92291ca1cb8e9f84e45dc15..7119f205d3157721035212e87c4654de555a034d 100644
--- a/python_payload/st4m/ui/ctx.py
+++ b/python_payload/st4m/ui/ctx.py
@@ -38,6 +38,24 @@ class Ctx(ABCBase):
         self.text_baseline: str = "alphabetic"
         self.line_width: float = 1.0
         self.global_alpha: float = 1.0
+        self.font: str = ""
+
+    @abstractmethod
+    def text_width(self, text: str) -> float:
+        """
+        Calculates width of rendered text, without rendering it.
+        """
+        pass
+
+    @abstractmethod
+    def get_font_name(self, ix: int) -> str:
+        """
+        Returns font name from internal font index. See ctx_config.h for
+        defined fonts.
+
+        TODO(q3k): expose these font indices into mpy
+        """
+        pass
 
     @abstractmethod
     def begin_path(self) -> "Ctx":
diff --git a/python_payload/st4m/ui/elements/menus.py b/python_payload/st4m/ui/elements/menus.py
index f3d8b7f93fd3271c3de8ec33931dc9df583a528b..7afab42b6131e091d0bda9a8697496f31baf2708 100644
--- a/python_payload/st4m/ui/elements/menus.py
+++ b/python_payload/st4m/ui/elements/menus.py
@@ -5,9 +5,8 @@ from st4m.ui.menu import MenuController, MenuItem
 
 from st4m import Ctx, InputState
 
-from st4m.utils import lerp
+from st4m.utils import lerp, tau
 import math
-from st4m.vector import tau
 
 
 class SimpleMenu(MenuController):
@@ -95,14 +94,15 @@ class FlowerMenu(MenuController):
         "_sun",
     )
 
-    def __init__(self, items: List[MenuItem], vm: ViewManager, name="flow3r") -> None:
+    def __init__(
+        self, items: List[MenuItem], vm: ViewManager, name: str = "flow3r"
+    ) -> None:
         self._ts = 0
         self.name = name
         self.ui = GroupRing(r=80)
         for item in items:
             self.ui.items_ring.append(FlowerIcon(label=item.label()))
         super().__init__(items, vm)
-        self._scroll_controller.wrap = True
 
         self.icon = FlowerIcon(label=self.name)
         self.icon.rotation_time = -5000
@@ -119,14 +119,14 @@ class FlowerMenu(MenuController):
     def draw(self, ctx: Ctx) -> None:
         ctx.gray(0)
         ctx.rectangle(-120, -120, 240, 240).fill()
-        for item in self.ui.items_ring:
-            item.highlighted = False
-            item.rotation_time = 10000
+        # for item in self.ui.items_ring:
+        #    item.highlighted = False
+        #    item.rotation_time = 10000
         current = self._scroll_controller.current_position()
         current_int = round(current) % len(self._items)
         # print("current", current, current_int)
-        self.ui.items_ring[current_int].highlighted = True
-        self.ui.items_ring[current_int].rotation_time = 3000
+        # self.ui.items_ring[current_int].highlighted = True
+        # self.ui.items_ring[current_int].rotation_time = 3000
         self.ui.angle_offset = math.pi - (tau * current / len(self.ui.items_ring))
 
         self.ui.draw(ctx)
diff --git a/python_payload/st4m/ui/elements/visuals.py b/python_payload/st4m/ui/elements/visuals.py
index f975eedae4420b7a39257a239aa47ac998632b2c..64f68d045e1b2fef7c714ac6624f505d594eaab9 100644
--- a/python_payload/st4m/ui/elements/visuals.py
+++ b/python_payload/st4m/ui/elements/visuals.py
@@ -1,11 +1,11 @@
-from st4m.utils import xy_from_polar
+from st4m.utils import xy_from_polar, tau
 from st4m.property import PUSH_RED, GO_GREEN, BLACK
+from st4m.goose import List, Optional
 from st4m import Responder, Ctx, InputState
 
 
 import random
 import math
-from st4m.vector import tau
 
 
 class Sun(Responder):
@@ -52,26 +52,25 @@ class Sun(Responder):
 
 
 class GroupRing(Responder):
-    def __init__(self, r=100, x=0, y=0):
+    def __init__(self, r: float = 100, x: float = 0, y: float = 0) -> None:
         self.r = r
         self.x = x
         self.y = y
-        self.items_ring = []
-        self.item_center = None
-        self.angle_offset = 0
+        self.items_ring: List[Responder] = []
+        self.item_center: Optional[Responder] = None
+        self.angle_offset = 0.0
         self.ts = 0.0
 
     def think(self, ins: InputState, delta_ms: int) -> None:
         self.ts += delta_ms
-        self.item_center.think(ins, delta_ms)
+        if self.item_center is not None:
+            self.item_center.think(ins, delta_ms)
         for item in self.items_ring:
             item.think(ins, delta_ms)
 
     def draw(self, ctx: Ctx) -> None:
         if self.item_center:
-            self.item_center.has_highlight = False
             self.item_center.draw(ctx)
-            # self.items_ring[0].draw(ctx)
 
         # ctx.save()
         for index, item in enumerate(self.items_ring):
@@ -91,7 +90,7 @@ class FlowerIcon(Responder):
     A flower icon
     """
 
-    def __init__(self, label="?") -> None:
+    def __init__(self, label: str = "?") -> None:
         self.x = 0.0
         self.y = 0.0
         self.size = 50.0
@@ -114,7 +113,7 @@ class FlowerIcon(Responder):
     def draw(self, ctx: Ctx) -> None:
         x = self.x
         y = self.y
-        petal_size = 0
+        petal_size = 0.0
         if self.petal_count:
             petal_size = 2.3 * self.size / self.petal_count + self.size_offset
 
diff --git a/python_payload/st4m/ui/interactions.py b/python_payload/st4m/ui/interactions.py
index e9dac9ceb22c09a292ef020f1b69fbe9eeb8e0a3..0bfcb74ef3eb89bd8c6bf5c0a6916ee63d0006cc 100644
--- a/python_payload/st4m/ui/interactions.py
+++ b/python_payload/st4m/ui/interactions.py
@@ -1,6 +1,6 @@
 import st4m
 
-from st4m.input import InputState, PetalController
+from st4m.input import InputState
 from st4m.ui.ctx import Ctx
 from st4m import Responder
 
@@ -31,12 +31,11 @@ class ScrollController(st4m.Responder):
         "_velocity",
     )
 
-    def __init__(self, wrap=False) -> None:
+    def __init__(self) -> None:
         self._nitems = 0
         self._target_position = 0
         self._current_position = 0.0
         self._velocity: float = 0.0
-        self.wrap = wrap
 
     def set_item_count(self, count: int) -> None:
         """
@@ -56,7 +55,7 @@ class ScrollController(st4m.Responder):
         press).
         """
         self._target_position -= 1
-        self._velocity = -30
+        self._velocity = -10
 
     def scroll_right(self) -> None:
         """
@@ -64,7 +63,7 @@ class ScrollController(st4m.Responder):
         press).
         """
         self._target_position += 1
-        self._velocity = 30
+        self._velocity = 10
 
     def think(self, ins: InputState, delta_ms: int) -> None:
         if self._nitems == 0:
@@ -72,13 +71,10 @@ class ScrollController(st4m.Responder):
             self._current_position = 0
             self._velocity = 0
             return
-        if self.wrap:
-            self._target_position = self._target_position % self._nitems
-        else:
-            if self._target_position < 0:
-                self._target_position = 0
-            if self._target_position >= self._nitems:
-                self._target_position = self._nitems - 1
+        if self._target_position < 0:
+            self._target_position = 0
+        if self._target_position >= self._nitems:
+            self._target_position = self._nitems - 1
 
         self._physics_step(delta_ms / 1000.0)
 
@@ -96,7 +92,7 @@ class ScrollController(st4m.Responder):
 
         Use this value to animate the scroll list.
         """
-        return round(self._current_position, 4) % self._nitems
+        return round(self._current_position, 4)
 
     def target_position(self) -> int:
         """
@@ -121,27 +117,16 @@ class ScrollController(st4m.Responder):
         return self._target_position >= self._nitems - 1
 
     def _physics_step(self, delta: float) -> None:
-        diff = self._nitems
-        for i in [0, 1, -1]:
-            d = (
-                float(self._target_position) + self._nitems * i
-            ) - self._current_position
-            if abs(d) < abs(diff):
-                diff = d
-        # diff =
-        # print(diff)
-        max_velocity = 0.2
+        diff = float(self._target_position) - self._current_position
+        max_velocity = 500
         velocity = self._velocity
 
-        if abs(velocity) < 1:
-            self._target_position = int(self._current_position)
-
-        if abs(diff) > 0.2:
+        if abs(diff) > 0.1:
             # Apply force to reach target position.
             if diff > 0:
-                velocity += 5 * delta
+                velocity += 80 * delta
             else:
-                velocity -= 5 * delta
+                velocity -= 80 * delta
 
             # Clamp velocity.
             if velocity > max_velocity:
@@ -149,149 +134,144 @@ class ScrollController(st4m.Responder):
             if velocity < -max_velocity:
                 velocity = -max_velocity
             self._velocity = velocity
-        elif diff == 0:
-            pass
-            print("at target")
         else:
             # Try to snap to target position.
             pos = self._velocity > 0 and diff > 0
             neg = self._velocity < 0 and diff < 0
-            if self.wrap or pos or neg:
-                print("snapped")
-                self._current_position = self._target_position % self._nitems
+            if pos or neg:
+                self._current_position = self._target_position
                 self._velocity = 0
 
         self._physics_integrate(delta)
 
     def _physics_integrate(self, delta: float) -> None:
         self._velocity -= self._velocity * delta * 10
-        self._current_position = (
-            self._current_position + self._velocity * delta
-        ) % self._nitems
-
-
-class GestureScrollController(ScrollController):
-    """
-    GestureScrollController extends ScrollController to also react to swipe gestures
-    on a configurable petal.
-
-    #TODO (iggy): rewrite both to extend a e.g. "PhysicsController"
-    """
-
-    def __init__(self, petal_index, wrap=False):
-        super().__init__(wrap)
-        self._petal = PetalController(petal_index)
-        self._speedbuffer = [0.0]
-        self._ignore = 0
-
-    def scroll_left(self) -> None:
-        """
-        Call when the user wants to scroll left by discrete action (eg. button
-        press).
-        """
-        # self._target_position -= 1
-        self._speedbuffer = []
-        self._velocity = -1
-        self._current_position -= 0.3
-
-    def scroll_right(self) -> None:
-        """
-        Call when the user wants to scroll right by discrete action (eg. button
-        press).
-        """
-        # self._target_position += 1
-        self._speedbuffer = []
-        self._velocity = 1
-        self._current_position += 0.3
-
-    def think(self, ins: InputState, delta_ms: int) -> None:
-        # super().think(ins, delta_ms)
-
-        self._petal.think(ins, delta_ms)
-
-        if self._ignore:
-            self._ignore -= 1
-            return
-
-        dphi = self._petal._input._dphi
-        phase = self._petal._input.phase()
-
-        self._speedbuffer.append(self._velocity)
-
-        while len(self._speedbuffer) > 3:
-            self._speedbuffer.pop(0)
-
-        speed = sum(self._speedbuffer) / len(self._speedbuffer)
-
-        speed = min(speed, 0.008)
-        speed = max(speed, -0.008)
-
-        if phase == self._petal._input.ENDED:
-            # self._speedbuffer = [0 if abs(speed) < 0.005 else speed]
-            while len(self._speedbuffer) > 3:
-                print("sb:", self._speedbuffer)
-                self._speedbuffer.pop()
-        elif phase == self._petal._input.UP:
-            pass
-        elif phase == self._petal._input.BEGIN:
-            self._ignore = 5
-            # self._speedbuffer = [0.0]
-        elif phase == self._petal._input.RESTING:
-            self._speedbuffer.append(0.0)
-        elif phase == self._petal._input.MOVED:
-            impulse = -dphi / delta_ms
-            self._speedbuffer.append(impulse)
-
-        if abs(speed) < 0.0001:
-            speed = 0
-
-        self._velocity = speed
-
-        self._current_position = self._current_position + self._velocity * delta_ms
-
-        if self.wrap:
-            self._current_position = self._current_position % self._nitems
-        elif round(self._current_position) < 0:
-            self._current_position = 0
-        elif round(self._current_position) >= self._nitems:
-            self._current_position = self._nitems - 1
-
-        if phase != self._petal._input.UP:
-            return
-
-        pos = round(self._current_position)
-        microstep = round(self._current_position) - self._current_position
-        # print("micro:", microstep)
-        # print("v", self._velocity)
-        # print("pos", self._current_position)
-
-        if (
-            abs(microstep) < 0.1
-            and abs(self._velocity) < 0.001
-            # and abs(self._velocity)
-        ):
-            self._velocity = 0
-            self._speedbuffer.append(0)
-            self._current_position = round(self._current_position)
-            self._target_position = self._current_position
-            # print("LOCK")
-            return
-
-        if abs(self._velocity) > 0.001:
-            self._speedbuffer.append(-self._velocity)
-            # print("BREAKING")
-            return
-
-        if self._velocity >= 0 and microstep > 0:
-            self._speedbuffer.append(max(self._velocity, 0.01) * microstep * 10)
-            # print("1")
-        elif self._velocity < 0 and microstep > 0:
-            self._speedbuffer.append(-self._velocity)
-            # print("2")
-        elif self._velocity > 0 and microstep < 0:
-            self._speedbuffer.append(-self._velocity * 0.5)
-            # print("3")
-
-        elif self._velocity <= 0 and microstep < 0:
-            self._speedbuffer.append(min(self._velocity, -0.01) * abs(microstep) * 10)
-            # print("4")
+        self._current_position += self._velocity * delta
+
+
+# class GestureScrollController(ScrollController):
+#    """
+#    GestureScrollController extends ScrollController to also react to swipe gestures
+#    on a configurable petal.
+#
+#    #TODO (iggy): rewrite both to extend a e.g. "PhysicsController"
+#    """
+#
+#    def __init__(self, petal_index, wrap=False):
+#        super().__init__(wrap)
+#        self._petal = PetalController(petal_index)
+#        self._speedbuffer = [0.0]
+#        self._ignore = 0
+#
+#    def scroll_left(self) -> None:
+#        """
+#        Call when the user wants to scroll left by discrete action (eg. button
+#        press).
+#        """
+#        # self._target_position -= 1
+#        self._speedbuffer = []
+#        self._velocity = -1
+#        self._current_position -= 0.3
+#
+#    def scroll_right(self) -> None:
+#        """
+#        Call when the user wants to scroll right by discrete action (eg. button
+#        press).
+#        """
+#        # self._target_position += 1
+#        self._speedbuffer = []
+#        self._velocity = 1
+#        self._current_position += 0.3
+#
+#    def think(self, ins: InputState, delta_ms: int) -> None:
+#        # super().think(ins, delta_ms)
+#
+#        self._petal.think(ins, delta_ms)
+#
+#        if self._ignore:
+#            self._ignore -= 1
+#            return
+#
+#        dphi = self._petal._input._dphi
+#        phase = self._petal._input.phase()
+#
+#        self._speedbuffer.append(self._velocity)
+#
+#        while len(self._speedbuffer) > 3:
+#            self._speedbuffer.pop(0)
+#
+#        speed = sum(self._speedbuffer) / len(self._speedbuffer)
+#
+#        speed = min(speed, 0.008)
+#        speed = max(speed, -0.008)
+#
+#        if phase == self._petal._input.ENDED:
+#            # self._speedbuffer = [0 if abs(speed) < 0.005 else speed]
+#            while len(self._speedbuffer) > 3:
+#                print("sb:", self._speedbuffer)
+#                self._speedbuffer.pop()
+#        elif phase == self._petal._input.UP:
+#            pass
+#        elif phase == self._petal._input.BEGIN:
+#            self._ignore = 5
+#            # self._speedbuffer = [0.0]
+#        elif phase == self._petal._input.RESTING:
+#            self._speedbuffer.append(0.0)
+#        elif phase == self._petal._input.MOVED:
+#            impulse = -dphi / delta_ms
+#            self._speedbuffer.append(impulse)
+#
+#        if abs(speed) < 0.0001:
+#            speed = 0
+#
+#        self._velocity = speed
+#
+#        self._current_position = self._current_position + self._velocity * delta_ms
+#
+#        if self.wrap:
+#            self._current_position = self._current_position % self._nitems
+#        elif round(self._current_position) < 0:
+#            self._current_position = 0
+#        elif round(self._current_position) >= self._nitems:
+#            self._current_position = self._nitems - 1
+#
+#        if phase != self._petal._input.UP:
+#            return
+#
+#        pos = round(self._current_position)
+#        microstep = round(self._current_position) - self._current_position
+#        # print("micro:", microstep)
+#        # print("v", self._velocity)
+#        # print("pos", self._current_position)
+#
+#        if (
+#            abs(microstep) < 0.1
+#            and abs(self._velocity) < 0.001
+#            # and abs(self._velocity)
+#        ):
+#            self._velocity = 0
+#            self._speedbuffer.append(0)
+#            self._current_position = round(self._current_position)
+#            self._target_position = self._current_position
+#            # print("LOCK")
+#            return
+#
+#        if abs(self._velocity) > 0.001:
+#            self._speedbuffer.append(-self._velocity)
+#            # print("BREAKING")
+#            return
+#
+#        if self._velocity >= 0 and microstep > 0:
+#            self._speedbuffer.append(max(self._velocity, 0.01) * microstep * 10)
+#            # print("1")
+#        elif self._velocity < 0 and microstep > 0:
+#            self._speedbuffer.append(-self._velocity)
+#            # print("2")
+#        elif self._velocity > 0 and microstep < 0:
+#            self._speedbuffer.append(-self._velocity * 0.5)
+#            # print("3")
+#
+#        elif self._velocity <= 0 and microstep < 0:
+#            self._speedbuffer.append(min(self._velocity, -0.01) * abs(microstep) * 10)
+#            # print("4")
+#
diff --git a/python_payload/st4m/ui/menu.py b/python_payload/st4m/ui/menu.py
index 96b09c3c7bf8c287428a85c63626cbd8914fba38..e076dedd52dce11e28f6bb4ca76de0c89384d636 100644
--- a/python_payload/st4m/ui/menu.py
+++ b/python_payload/st4m/ui/menu.py
@@ -10,7 +10,7 @@ from st4m.ui.view import (
     ViewTransitionSwipeLeft,
     ViewTransitionSwipeRight,
 )
-from st4m.ui.interactions import ScrollController, GestureScrollController
+from st4m.ui.interactions import ScrollController
 from st4m.ui.ctx import Ctx
 
 
@@ -101,7 +101,7 @@ class MenuController(ViewWithInputState):
 
     def __init__(self, items: List[MenuItem], vm: Optional[ViewManager]) -> None:
         self._items = items
-        self._scroll_controller = GestureScrollController(2)
+        self._scroll_controller = ScrollController()
         self._scroll_controller.set_item_count(len(items))
         self._view_manager = vm
 
diff --git a/python_payload/st4m/utils.py b/python_payload/st4m/utils.py
index 0123d7f7566409e46bd283f2edfddc2cd0a5f226..cdcafc4b559e8bab7236a12c8f4299f038ed209e 100644
--- a/python_payload/st4m/utils.py
+++ b/python_payload/st4m/utils.py
@@ -9,5 +9,8 @@ def lerp(a: float, b: float, v: float) -> float:
     return a + (b - a) * v
 
 
-def xy_from_polar(r, phi):
+def xy_from_polar(r: float, phi: float) -> tuple[float, float]:
     return (r * math.sin(phi), r * math.cos(phi))  # x  # y
+
+
+tau = math.pi * 2