diff --git a/hunks/interface/jogpad.js b/hunks/interface/jogpad.js
new file mode 100644
index 0000000000000000000000000000000000000000..8a5e74dd557d2d50c3679d2e9baf4b8dbc6add95
--- /dev/null
+++ b/hunks/interface/jogpad.js
@@ -0,0 +1,139 @@
+/*
+hunks/interface/arrowpad.js
+
+arrowpad pressure
+
+Jake Read at the Center for Bits and Atoms
+(c) Massachusetts Institute of Technology 2019
+
+This work may be reproduced, modified, distributed, performed, and
+displayed for any purpose, but must acknowledge the squidworks and cuttlefish projects.
+Copyright is retained and must be preserved. The work is provided as is;
+no warranty is provided, and users accept all liability.
+*/
+
+import {
+  Hunkify,
+  Input,
+  Output,
+  State
+} from '../hunks.js'
+
+export default function JogPad() {
+  Hunkify(this)
+
+  let pass = this.output('array', 'keysdown')
+
+  const pairs = [{
+    name: 'left',
+    code: 37
+  }, {
+    name: 'right',
+    code: 39
+  }, {
+    name: 'up',
+    code: 38
+  }, {
+    name: 'down',
+    code: 40
+  }, {
+    name: 'pgup',
+    code: 33
+  }, {
+    name: 'pgdown',
+    code: 34
+  }]
+
+  for (let pair of pairs) {
+    // current state,
+    pair.down = false
+  }
+
+  let dom = this.document()
+
+  let activeStatus = false;
+  let activeColor = '#d981eb'
+  let idleColor = '#82aef5'
+
+  let removeKeyBindings = () => {
+    document.removeEventListener('keydown', keyDownListen)
+    document.removeEventListener('keyup', keyUpListen)
+    // no sticky keys!
+    clearAllKeys()
+  }
+
+  let setKeyBindings = () => {
+    document.addEventListener('keydown', keyDownListen)
+    document.addEventListener('keyup', keyUpListen)
+  }
+
+  let keyDownListen = (evt) => {
+    if (evt.repeat) return
+    let key = pairs.find((cand) => {
+      return cand.code === evt.keyCode
+    })
+    if (key) {
+      key.down = true
+    }
+  }
+
+  let keyUpListen = (evt) => {
+    let key = pairs.find((cand) => {
+      return cand.code === evt.keyCode
+    })
+    if (key) {
+      key.down = false
+    }
+  }
+
+  let clearAllKeys = () => {
+    for (let key of pairs) {
+      key.down = false
+    }
+  }
+
+  let container = $('<div>').get(0)
+  let msg = $('<div>').text('~ click in to activate ~').get(0)
+  $(msg).css('padding-top', '35px')
+  $(container).append(msg)
+  $(container).css('background-color', idleColor)
+  $(container).width(285).height(185)
+  $(container).css('text-align', 'center')
+  $(container).css('font-size', '18px')
+  $(container).css('color', 'white')
+  $(dom).append(container)
+  dom.addEventListener('click', (evt) => {
+    if (activeStatus) {
+      activeStatus = false
+      $(msg).text('~ click in to activate ~')
+      $(container).css('background-color', idleColor)
+      removeKeyBindings()
+    } else {
+      activeStatus = true
+      $(msg).text('~ push push push ~')
+      $(container).css('background-color', activeColor)
+      setKeyBindings()
+    }
+  })
+
+
+  // puts true when transitioning to down, puts false when lifting up
+  // downstream evts can do whatever they like, either track state or
+  // also run safety timeouts
+
+  // this is actually kind of a nontrivial statemachine, bc we have to
+  // potentially flow-control buffer output events in the order that they
+  // happened ... use pairs, setup each one as a statemachine
+  this.loop = () => {
+    if(!pass.io()){
+      // collect states,
+      let state = []
+      for(let key of pairs){
+        if(key.down){
+          state.push(key.name)
+        }
+      }
+      pass.put(JSON.parse(JSON.stringify(state)))
+    }
+  }
+}
diff --git a/hunks/statemachines/saturn.js b/hunks/statemachines/saturn.js
index c45293272e161dfcea3057456f5e790448ccc6fb..ce366a597de969b5cfc0d64034795da66ba2a5b7 100644
--- a/hunks/statemachines/saturn.js
+++ b/hunks/statemachines/saturn.js
@@ -53,32 +53,43 @@ export default function Saturn() {
   Hunkify(this)
 
   let inpts = this.input('array', 'posn')
+  // jogging is a whole other thing,
+  let keysdown = this.input('array', 'jogkeys')
+  let isJogMode = this.state('boolean', 'jog mode', false)
+  let jogSize = this.state('number', 'jog size', 1)
+  jogSize.onChange = (value) => {
+    if (value < 0.01) value = 0.01
+    jogSize.set(value)
+  }
 
   let outp = this.output('array', 'posn')
 
   // here's a case where we might want some type of enum ish thing
   let isIncrementalMode = this.state('boolean', 'incremental mode', false)
-  let reset = this.state('boolean', 'reset', false)
-  reset.onChange = (val) => {
-    // clip anything larger than p[0]
-    /*
-    if(positions.length > 1){
-      while(positions.length > 1) positions.pop()
+  // these are mutually exclusive,
+  isIncrementalMode.onChange = (value) => {
+    if (value) {
+      isJogMode.set(false)
+      isIncrementalMode.set(true)
+    } else {
+      isIncrementalMode.set(false)
+    }
+  }
+  isJogMode.onChange = (value) => {
+    if (value) {
+      isIncrementalMode.set(true)
+      isJogMode.set(true)
+    } else {
+      isIncrementalMode.set(false)
+      isJogMode.set(false)
     }
-    */
-    // or, just zero:
-    positions = [[0,0,0]]
-    ramps = []
-    speed = minSpeed
-    reset.set(false)
-    updatePositionDisplay()
-    updateBufDisplay()
   }
+  let reset = this.state('boolean', 'reset', false)
   let accelState = this.state('number', 'acceleration (u/s/s)', 2)
   accelState.onChange = (val) => {
-    if(val > 30){
+    if (val > 30) {
       val = 30
-    } else if (val < 0.5){
+    } else if (val < 0.5) {
       val = 0.5
     }
     accelState.set(val)
@@ -86,15 +97,39 @@ export default function Saturn() {
   }
   let speedState = this.state('number', 'speed (u/s)', 20)
   speedState.onChange = (val) => {
-    if (val > 200){
+    if (val > 200) {
       val = 200
-    } else if (val < 5){
+    } else if (val < 5) {
       val = 5
     }
     speedState.set(val)
     cruise = speedState.value
   }
 
+  let resetAllState = () => {
+    // clip anything larger than p[0]
+    /*
+    if(positions.length > 1){
+      while(positions.length > 1) positions.pop()
+    }
+    */
+    // or, just zero:
+    positions = [
+      [0, 0, 0]
+    ]
+    ramps = []
+    speed = minSpeed
+    reset.set(false)
+    updatePositionDisplay()
+    updateBufDisplay()
+    // and, modal,
+    isJogMode.set(false)
+    isIncrementalMode.set(false)
+  }
+  reset.onChange = (val) => {
+    resetAllState()
+  }
+
   let mdmsegOut = this.output('MDmseg', 'motionSegment')
 
   // settings,
@@ -430,11 +465,12 @@ export default function Saturn() {
     }
   }
 
+  let jognow = true
+
   this.loop = () => {
     // handle output to stepper, if we have any existing blocks to issue:
     if (ramps.length > 0 && !mdmsegOut.io()) {
       posUpdated = true
-      updatePositionDisplay()
       // multi-dimensional mseg,
       mdmsegOut.put(writeMDmsegFromRamp(ramps[0]))
       // this means we are either inside of two old positions,
@@ -454,6 +490,7 @@ export default function Saturn() {
         }
         */
         positions.shift()
+        updatePositionDisplay()
         speed = ramps[0].vf
         ramps.shift()
         updateBufDisplay()
@@ -462,6 +499,7 @@ export default function Saturn() {
       } else {
         if (debugRuntime) console.log(`> updating p[0]`)
         positions[0] = ramps[0].pf
+        updatePositionDisplay()
         speed = ramps[0].vf
         ramps.shift()
         updateBufDisplay()
@@ -477,6 +515,36 @@ export default function Saturn() {
 
     let logTimes = false
 
+    // jog?
+    if (isJogMode.value) {
+      // if we have a key to execute, and are not currently aiming at anything
+      // i.e. our only position is the 0th, our current,
+      if (keysdown.io() && positions.length == 1 && jognow) {
+        let keys = keysdown.get()
+        let jt = [0, 0, 0]
+        let inc = jogSize.value
+        if (keys.includes('right')) jt[0] += inc
+        if (keys.includes('left')) jt[0] -= inc
+        if (keys.includes('up')) jt[1] += inc
+        if (keys.includes('down')) jt[1] -= inc
+        if (keys.includes('pgup')) jt[2] += inc
+        if (keys.includes('pgdown')) jt[2] -= inc
+        if (Math.abs(vLen(jt)) > 0.009) {
+          // we only want to do this every once and while...
+          jognow = false
+          setTimeout(() => {
+            jognow = true
+          }, 1000)
+          positions.push([
+            positions[0][0] + jt[0],
+            positions[0][1] + jt[1],
+            positions[0][2] + jt[2],
+          ])
+        }
+      } else if (keysdown.io()) {
+        keysdown.get()
+      }
+    }
     // load new pts into the array,
     if (evalLoad()) {
       // get the new pt, adding it if it is of any appreciable distance
@@ -494,45 +562,45 @@ export default function Saturn() {
       } catch (err) {
         console.warn('error caught at saturn input', err)
       }
+    }
 
-      // only run lookahead -> shipments if we have ah very-full buffer
-      if (evalLookahead()) {
-        if (debugRuntime) console.log('lookahead')
-        // LOOKAHEAD BEGIN
-        if (logTimes) console.time('lookahead')
-        // positions[] is global, speeds is generated now
-        // speed[0], matching positions[0], are our current situations
-        let speeds = new Array(positions.length)
-        speeds[0] = speed
-        speeds[speeds.length - 1] = minSpeed
-        // first, set speeds such that moves can be made within single periods,
-        periodPass(speeds)
-        if (logTimes) console.timeLog('lookahead')
-        // jd runs an algorithm that calculates maximum allowable
-        // instantaneous accelerations at corners
-        jd(speeds) // at ~ 38ms, this is the beef of it
-        if (logTimes) console.timeLog('lookahead')
-        // revpass to link by max. accel:
-        reversePass(speeds)
-        forwardPass(speeds)
-        // rough check speeds after initial passes,
-        // TODO: if we find these throwing errs, interrupt control when we
-        // hit some geometry we can't deal with?
-        positionsCheck(speeds)
-        if (logTimes) console.timeLog('lookahead')
-        // ok, ramps:
-        ramps.length = 0
-        ramps = rampPass(speeds)
-        // is this still conn. to our head?
-        if (ramps.length > 0 && debugRuntime) console.log('new ramps', vDist(ramps[0].pi, positions[0]).toFixed(5))
-        // and a check,
-        rampCheck(ramps)
-        if (logTimes) console.timeLog('lookahead')
-        if (logTimes) console.timeEnd('lookahead')
-        //console.log('new pt\t', positions.length, np)
-        // LOOKAHEAD END
-        let rp = JSON.parse(JSON.stringify(ramps))
-      }
+    // only run lookahead -> shipments if we have ah very-full buffer
+    if (evalLookahead()) {
+      if (debugRuntime) console.log('lookahead')
+      // LOOKAHEAD BEGIN
+      if (logTimes) console.time('lookahead')
+      // positions[] is global, speeds is generated now
+      // speed[0], matching positions[0], are our current situations
+      let speeds = new Array(positions.length)
+      speeds[0] = speed
+      speeds[speeds.length - 1] = minSpeed
+      // first, set speeds such that moves can be made within single periods,
+      periodPass(speeds)
+      if (logTimes) console.timeLog('lookahead')
+      // jd runs an algorithm that calculates maximum allowable
+      // instantaneous accelerations at corners
+      jd(speeds) // at ~ 38ms, this is the beef of it
+      if (logTimes) console.timeLog('lookahead')
+      // revpass to link by max. accel:
+      reversePass(speeds)
+      forwardPass(speeds)
+      // rough check speeds after initial passes,
+      // TODO: if we find these throwing errs, interrupt control when we
+      // hit some geometry we can't deal with?
+      positionsCheck(speeds)
+      if (logTimes) console.timeLog('lookahead')
+      // ok, ramps:
+      ramps.length = 0
+      ramps = rampPass(speeds)
+      // is this still conn. to our head?
+      if (ramps.length > 0 && debugRuntime) console.log('new ramps', vDist(ramps[0].pi, positions[0]).toFixed(5))
+      // and a check,
+      rampCheck(ramps)
+      if (logTimes) console.timeLog('lookahead')
+      if (logTimes) console.timeEnd('lookahead')
+      //console.log('new pt\t', positions.length, np)
+      // LOOKAHEAD END
+      let rp = JSON.parse(JSON.stringify(ramps))
     }
   }
 }