diff --git a/README.md b/README.md
index 509b6be1759fa1e32a6af3d280d1d63d07a2689d..d2f4db3dd510a0278222f346f716463596772e7c 100644
--- a/README.md
+++ b/README.md
@@ -316,6 +316,7 @@ View.assignProgram(program)
 - module deletion seems unclean 
  - input / output objects should be able to unhook themselves: 
  - keep references of what-is-attached ? 
+ - would like to be able to default prevent state variables from being changed by users, i.e. in 'collector' the 'count' variable 
 
 ## WRT Representations
 
diff --git a/client/client.js b/client/client.js
index 851796ac6bf4c18ce73b7708ab302daba78cd751..4ca0cd2086559216f6ee591737c4ad38e2b8569e 100644
--- a/client/client.js
+++ b/client/client.js
@@ -510,6 +510,8 @@ document.onkeydown = function(evt) {
         case 'd':
             console.log(program)
             break
+        case 'k':
+            socketSend('save program', 'temp')
         default:
             break
     }
diff --git a/modules/util/array.js b/modules/util/array.js
index ade6932ad357882ef4a232e5e9496993a7ea2e5d..9de3e74512ad64096593ef5f0d043092d5ea8e81 100644
--- a/modules/util/array.js
+++ b/modules/util/array.js
@@ -49,12 +49,12 @@ function UIArray() {
 
     // here's our input callback, specified in the input constructor 
     function onThruInput(input){
-        if(typeof input == 'number'){
-            state.number = input
+        if(Array.isArray(input)){
+            state.array = input
+             onArrayDesire()
         } else {
-            state.number = parseFloat(input)
+            console.log("ERR input to array module is non-array")
         }
-        onArrayDesire()
     }
 
     function onArrayDesire(){
diff --git a/modules/util/collector.js b/modules/util/collector.js
new file mode 100644
index 0000000000000000000000000000000000000000..d34e8aba2664b7d80628ef2ad97d2daf2880416d
--- /dev/null
+++ b/modules/util/collector.js
@@ -0,0 +1,77 @@
+// boilerplate rndmc header
+const JSUnit = require('../../src/jsunit.js')
+let Input = JSUnit.Input
+let Output = JSUnit.Output
+let State = JSUnit.State
+
+// interface elements
+const JSUI = require('../../src/jsui.js')
+let UI = JSUI.UI 
+
+/*
+collector
+
+takes samples from what-ever output, stores in an array until a 'dump' input,
+outputs that array, clears internals 
+
+*/
+
+// a constructor, a fn, a javascript mess
+function Collector() {
+
+    // this is the tiny program-as-and-object that we'll load into rundmc 
+    // description / name is required to load successfully 
+    var collector = {
+        description: {
+            name: 'collector',
+            alt: 'collect and dump'
+        }
+    }
+
+    // the State() object is what the system scrapes for ui variables / updates from the UI
+    // this includes things like Button('title', callback), which are unique state variables
+    // they can also be found in jsunit.js 
+    collector.state = State()
+    // alias !
+    var state = collector.state 
+
+    state.count = 0
+
+    var collection = new Array()
+
+    collector.ui = UI() 
+    var ui = collector.ui 
+    ui.addElement('onDumpButton', './ui/uiButton.js', onDumpDesire)
+    ui.onDumpButton.onload = function() {
+        ui.onDumpButton.setText('array out ->')
+    }
+
+    // inputs are required, and must be Input('type', callback) 
+    collector.inputs = {
+        collect: Input('any', onNewItem), // makes anything into num event 
+        dump: Input('evt', onDumpDesire)
+    }
+
+    // outputs: Output('type')
+    collector.outputs = {
+        pass: Output('any')
+    }
+
+    // here's our input callback, specified in the input constructor 
+    function onNewItem(input){
+        collection.push(input)
+        state.count = collection.length 
+    }
+
+    function onDumpDesire(){
+        collector.outputs.pass.emit(collection)
+        collection = new Array()
+        state.count = collection.length 
+    }
+
+    // gotta give the program this thing we made 
+    return collector
+}
+
+// this for node.js's require() function 
+module.exports = Collector
\ No newline at end of file
diff --git a/modules/util/gate.js b/modules/util/gate.js
index 737861566d5a89e1b2b05674ed24acc298f91443..7a24c4bdb7da9d7fa8cc08c9f2ea0d16fdd6cb2e 100644
--- a/modules/util/gate.js
+++ b/modules/util/gate.js
@@ -29,7 +29,7 @@ function Gate() {
     var ui = gate.ui
     ui.addElement('openButton', './ui/uiButton.js', onButtonPress)
     ui.openButton.onload = function() {
-        ui.openButton.setText('toggle gate')
+        ui.openButton.setText('click to open gate')
     }
 
     // yikes 
@@ -44,14 +44,14 @@ function Gate() {
     }
 
     function onButtonPress(evt) {
-        console.log("GATE BUTTON")
         if (gate.isOpen) {
             gate.isOpen = false
             state.message = 'closed'
+            ui.openButton.setText('click to open gate')
         } else {
             gate.isOpen = true
             state.message = 'open'
-            //gate.outputs.out.emit('go')
+            ui.openButton.setText('click to close gate')
         }
     }
 
diff --git a/modules/util/gateCounter.js b/modules/util/gateCounter.js
new file mode 100644
index 0000000000000000000000000000000000000000..b367b56f2ca2e53518c15fbf525b6e16428fb55f
--- /dev/null
+++ b/modules/util/gateCounter.js
@@ -0,0 +1,79 @@
+// boilerplate header
+const JSUnit = require('../../src/jsunit.js')
+let Input = JSUnit.Input
+let Output = JSUnit.Output
+let State = JSUnit.State
+
+// interface elements
+const JSUI = require('../../src/jsui.js')
+let UI = JSUI.UI
+
+// a constructor, a fn, a javascript mess
+function GateCounter() {
+
+    var gateCounter = {
+        // descriptions are used in UI
+        description: {
+            name: 'gateCounter',
+            alt: 'in ... out'
+        }
+    }
+
+    gateCounter.state = State()
+    // alias ! 
+    var state = gateCounter.state
+
+    state.addThis = 10 
+    state.count = 0
+
+    gateCounter.ui = UI()
+    var ui = gateCounter.ui
+    ui.addElement('openButton', './ui/uiButton.js', onButtonPress)
+    ui.openButton.onload = function() {
+        ui.openButton.setText('click to add ' +  state.addThis.toString() + ' to count')
+    }
+
+    state.onUiChange('addThis', function(){
+        ui.openButton.setText('click to add ' +  state.addThis.toString() + ' to count')
+    })
+
+    // yikes 
+    gateCounter.isOpen = false
+
+    gateCounter.inputs = {
+        thru: Input('any', gateCounterKeeper), // makes anything into '1' event
+        setCount: Input('number', onSetCount),
+        addEvent: Input('any', onAddEvent)
+    }
+
+    gateCounter.outputs = {
+        out: Output('any')
+    }
+
+    function onButtonPress(evt) {
+        console.log("gateCounter BUTTON")
+        state.count += state.addThis
+    }
+
+    function onSetCount(num){
+        state.addThis = num 
+        state.count = num 
+    }
+
+    function onAddEvent(evt){
+        state.count = state.addThis 
+    }
+
+    function gateCounterKeeper(input) {
+        if (state.count > 0) {
+            var outVar = JSON.parse(JSON.stringify(input))
+            gateCounter.outputs.out.emit(outVar)
+            state.count -- 
+        }
+    }
+
+    return gateCounter
+}
+
+// exports 
+module.exports = GateCounter
\ No newline at end of file
diff --git a/modules/util/parallelContencator.js b/modules/util/parallelContencator.js
new file mode 100644
index 0000000000000000000000000000000000000000..14647e24535670bf67045e29d272f4e1fea34519
--- /dev/null
+++ b/modules/util/parallelContencator.js
@@ -0,0 +1,93 @@
+// boilerplate rndmc header
+const JSUnit = require('../../src/jsunit.js')
+let Input = JSUnit.Input
+let Output = JSUnit.Output
+let State = JSUnit.State
+
+// interface elements
+const JSUI = require('../../src/jsui.js')
+let UI = JSUI.UI
+
+// a constructor, a fn, a *hot* javascript mess
+function ParallelContencator() {
+
+    // this is the tiny program-as-and-object that we'll load into rundmc 
+    // description / name is required to load successfully 
+    var parallelContencator = {
+        description: {
+            name: 'parallelContencator',
+            alt: 'inputs -> arrays'
+        }
+    }
+
+    // the State() object is what the system scrapes for ui variables / updates from the UI
+    // this includes things like Button('title', callback), which are unique state variables
+    // they can also be found in jsunit.js 
+    parallelContencator.state = State()
+    // alias !
+    var state = parallelContencator.state
+
+    state.gateKeep = true
+
+    parallelContencator.ui = UI()
+    var ui = parallelContencator.ui
+    ui.addElement('onOutputButton', './ui/uiButton.js', doThroughput)
+    ui.onOutputButton.onload = function() {
+        ui.onOutputButton.setText('array out ->')
+    }
+
+    // inputs are required, and must be Input('type', callback) 
+    parallelContencator.inputs = {
+        in0: Input('any', onIn0),
+        in1: Input('any', onIn1),
+        push: Input('evt', doThroughput) // makes anything into num event 
+    }
+
+    // outputs: Output('type')
+    parallelContencator.outputs = {
+        out: Output('array')
+    }
+
+    var contencated = [0, 0]
+    var recent = [false, false]
+
+    function onIn0(inp) {
+        contencated[0] = inp
+        recent[0] = true
+        doGateCheck()
+    }
+
+    function onIn1(inp) {
+        contencated[1] = inp
+        recent[1] = true
+        doGateCheck()
+    }
+
+    function doGateCheck() {
+        if (state.gateKeep) {
+            var count = 0
+            recent.forEach(function(element) {
+                if (element) {
+                    count++
+                }
+            })
+            if (count == recent.length) {
+                doThroughput()
+            }
+        } else {
+            doThroughput()
+        }
+    }
+
+    function doThroughput() {
+        // here's how we fire an output. 
+        parallelContencator.outputs.out.emit(contencated)
+        recent.fill(false)
+    }
+
+    // gotta give the program this thing we made 
+    return parallelContencator
+}
+
+// this for node.js's require() function 
+module.exports = ParallelContencator
\ No newline at end of file
diff --git a/programs/temp.json b/programs/temp.json
new file mode 100644
index 0000000000000000000000000000000000000000..904ccd346ed6226fa286624debb5077a1122fda3
--- /dev/null
+++ b/programs/temp.json
@@ -0,0 +1,564 @@
+{
+  "description": {
+    "name": "new program",
+    "counter": 14
+  },
+  "modules": {
+    "SerialportATKLink-0": {
+      "description": {
+        "isHardware": true,
+        "isLink": true,
+        "name": "SerialportATKLink",
+        "alt": "window into hardware world",
+        "id": "SerialportATKLink-0",
+        "path": "./modules/hardware/atkseriallink.js",
+        "position": {
+          "left": 734,
+          "top": -191
+        }
+      },
+      "inputs": {},
+      "outputs": {},
+      "state": {
+        "portName": "---",
+        "portStatus": "closed",
+        "log": true
+      },
+      "ui": {
+        "kickButton": {
+          "type": "button",
+          "clientPath": "ui/uiButton.js"
+        }
+      }
+    },
+    "atk-math-robot-joint-1": {
+      "description": {
+        "name": "atk-math-robot-joint",
+        "alt": "software representation of networked hardware object",
+        "isHardware": true,
+        "id": "atk-math-robot-joint-1",
+        "path": "./modules/hardware/atkmrobot.js",
+        "position": {
+          "left": 600,
+          "top": 50
+        }
+      },
+      "inputs": {
+        "tick": {
+          "accepts": "event"
+        },
+        "set_pc_t": {
+          "accepts": "number"
+        },
+        "get_pos": {
+          "accepts": "event"
+        }
+      },
+      "outputs": {
+        "ok": {
+          "emits": "nothing-yet",
+          "calls": []
+        },
+        "pos": {
+          "emits": "uint16_t",
+          "calls": [
+            {
+              "parentId": "logger-6",
+              "key": "thru"
+            },
+            {
+              "parentId": "gateCounter-9",
+              "key": "thru"
+            }
+          ]
+        }
+      },
+      "state": {
+        "message": "no packet yet",
+        "route": "0,0",
+        "enc_cnt": 16384,
+        "pc_t": 2048,
+        "pKp": 4.5,
+        "pKi": 0,
+        "pKd": 0,
+        "cKp": 4,
+        "cKi": 0,
+        "walk": 1024
+      },
+      "ui": {
+        "walkValButton": {
+          "type": "button",
+          "clientPath": "ui/uiButton.js"
+        }
+      }
+    },
+    "atk-math-robot-joint-2": {
+      "description": {
+        "name": "atk-math-robot-joint",
+        "alt": "software representation of networked hardware object",
+        "isHardware": true,
+        "id": "atk-math-robot-joint-2",
+        "path": "./modules/hardware/atkmrobot.js",
+        "position": {
+          "left": 600,
+          "top": 450
+        }
+      },
+      "inputs": {
+        "tick": {
+          "accepts": "event"
+        },
+        "set_pc_t": {
+          "accepts": "number"
+        },
+        "get_pos": {
+          "accepts": "event"
+        }
+      },
+      "outputs": {
+        "ok": {
+          "emits": "nothing-yet",
+          "calls": []
+        },
+        "pos": {
+          "emits": "uint16_t",
+          "calls": [
+            {
+              "parentId": "gateCounter-12",
+              "key": "thru"
+            },
+            {
+              "parentId": "logger-14",
+              "key": "thru"
+            }
+          ]
+        }
+      },
+      "state": {
+        "message": "no packet yet",
+        "route": "0,1",
+        "enc_cnt": 16384,
+        "pc_t": 2048,
+        "pKp": 4.5,
+        "pKi": 0,
+        "pKd": 0,
+        "cKp": 4,
+        "cKi": 0,
+        "walk": 1024
+      },
+      "ui": {
+        "walkValButton": {
+          "type": "button",
+          "clientPath": "ui/uiButton.js"
+        }
+      }
+    },
+    "Button-3": {
+      "description": {
+        "name": "Button",
+        "alt": "for clicking",
+        "id": "Button-3",
+        "path": "./modules/ui/button.js",
+        "position": {
+          "left": 90,
+          "top": 50
+        }
+      },
+      "inputs": {
+        "thru": {
+          "accepts": "any"
+        }
+      },
+      "outputs": {
+        "whammy": {
+          "emits": "number",
+          "calls": [
+            {
+              "parentId": "atk-math-robot-joint-1",
+              "key": "get_pos"
+            },
+            {
+              "parentId": "delay-4",
+              "key": "thru"
+            },
+            {
+              "parentId": "atk-math-robot-joint-2",
+              "key": "get_pos"
+            }
+          ]
+        }
+      },
+      "state": {},
+      "ui": {
+        "btn": {
+          "type": "button",
+          "clientPath": "ui/uiButton.js"
+        }
+      }
+    },
+    "delay-4": {
+      "description": {
+        "name": "delay",
+        "alt": "in ... out",
+        "id": "delay-4",
+        "path": "./modules/util/delay.js",
+        "position": {
+          "left": 90,
+          "top": 250
+        }
+      },
+      "inputs": {
+        "thru": {
+          "accepts": "any"
+        }
+      },
+      "outputs": {
+        "out": {
+          "emits": "any",
+          "calls": [
+            {
+              "parentId": "gate-5",
+              "key": "thru"
+            }
+          ]
+        }
+      },
+      "state": {
+        "ms": 100
+      },
+      "ui": {}
+    },
+    "gate-5": {
+      "description": {
+        "name": "gate",
+        "alt": "in ... out",
+        "id": "gate-5",
+        "path": "./modules/util/gate.js",
+        "position": {
+          "left": 90,
+          "top": 400
+        }
+      },
+      "inputs": {
+        "thru": {
+          "accepts": "any"
+        }
+      },
+      "outputs": {
+        "out": {
+          "emits": "any",
+          "calls": [
+            {
+              "parentId": "Button-3",
+              "key": "thru"
+            }
+          ]
+        }
+      },
+      "state": {
+        "message": "closed"
+      },
+      "ui": {
+        "openButton": {
+          "type": "button",
+          "clientPath": "ui/uiButton.js"
+        }
+      }
+    },
+    "logger-6": {
+      "description": {
+        "name": "logger",
+        "alt": "in ... out to console",
+        "id": "logger-6",
+        "path": "./modules/util/log.js",
+        "position": {
+          "left": 1318,
+          "top": -170
+        }
+      },
+      "inputs": {
+        "thru": {
+          "accepts": "any"
+        }
+      },
+      "outputs": {
+        "throughput": {
+          "emits": "any",
+          "calls": []
+        }
+      },
+      "state": {
+        "prefix": "JTN1:",
+        "message": "---"
+      },
+      "ui": {}
+    },
+    "ThreeJS-Canvas-7": {
+      "description": {
+        "name": "ThreeJS-Canvas",
+        "alt": "graphix",
+        "id": "ThreeJS-Canvas-7",
+        "path": "./modules/ui/threeCanvas.js",
+        "position": {
+          "left": 2263,
+          "top": 326
+        }
+      },
+      "inputs": {
+        "xy1": {
+          "accepts": "array"
+        },
+        "xy2": {
+          "accepts": "array"
+        }
+      },
+      "outputs": {},
+      "state": {},
+      "ui": {
+        "threeCanvas": {
+          "type": "threeCanvas",
+          "clientPath": "ui/threeCanvas.js",
+          "libPath": "ui/libs/three.js"
+        }
+      }
+    },
+    "collector-8": {
+      "description": {
+        "name": "collector",
+        "alt": "collect and dump",
+        "id": "collector-8",
+        "path": "./modules/util/collector.js",
+        "position": {
+          "left": 1728,
+          "top": 1057
+        }
+      },
+      "inputs": {
+        "collect": {
+          "accepts": "any"
+        },
+        "dump": {
+          "accepts": "evt"
+        }
+      },
+      "outputs": {
+        "pass": {
+          "emits": "any",
+          "calls": []
+        }
+      },
+      "state": {
+        "count": 0
+      },
+      "ui": {
+        "onDumpButton": {
+          "type": "button",
+          "clientPath": "ui/uiButton.js"
+        }
+      }
+    },
+    "gateCounter-9": {
+      "description": {
+        "name": "gateCounter",
+        "alt": "in ... out",
+        "id": "gateCounter-9",
+        "path": "./modules/util/gateCounter.js",
+        "position": {
+          "left": 732,
+          "top": 976
+        }
+      },
+      "inputs": {
+        "thru": {
+          "accepts": "any"
+        },
+        "setCount": {
+          "accepts": "number"
+        },
+        "addEvent": {
+          "accepts": "any"
+        }
+      },
+      "outputs": {
+        "out": {
+          "emits": "any",
+          "calls": [
+            {
+              "parentId": "parallelContencator-11",
+              "key": "in0"
+            }
+          ]
+        }
+      },
+      "state": {
+        "addThis": 10,
+        "count": 0
+      },
+      "ui": {
+        "openButton": {
+          "type": "button",
+          "clientPath": "ui/uiButton.js"
+        }
+      }
+    },
+    "parallelContencator-11": {
+      "description": {
+        "name": "parallelContencator",
+        "alt": "inputs -> arrays",
+        "id": "parallelContencator-11",
+        "path": "./modules/util/parallelContencator.js",
+        "position": {
+          "left": 1232,
+          "top": 1053
+        }
+      },
+      "inputs": {
+        "in0": {
+          "accepts": "any"
+        },
+        "in1": {
+          "accepts": "any"
+        },
+        "push": {
+          "accepts": "evt"
+        }
+      },
+      "outputs": {
+        "out": {
+          "emits": "array",
+          "calls": [
+            {
+              "parentId": "collector-8",
+              "key": "collect"
+            }
+          ]
+        }
+      },
+      "state": {
+        "gateKeep": true
+      },
+      "ui": {
+        "onOutputButton": {
+          "type": "button",
+          "clientPath": "ui/uiButton.js"
+        }
+      }
+    },
+    "gateCounter-12": {
+      "description": {
+        "name": "gateCounter",
+        "alt": "in ... out",
+        "id": "gateCounter-12",
+        "path": "./modules/util/gateCounter.js",
+        "position": {
+          "left": 726,
+          "top": 1174
+        }
+      },
+      "inputs": {
+        "thru": {
+          "accepts": "any"
+        },
+        "setCount": {
+          "accepts": "number"
+        },
+        "addEvent": {
+          "accepts": "any"
+        }
+      },
+      "outputs": {
+        "out": {
+          "emits": "any",
+          "calls": [
+            {
+              "parentId": "parallelContencator-11",
+              "key": "in1"
+            }
+          ]
+        }
+      },
+      "state": {
+        "addThis": 10,
+        "count": 0
+      },
+      "ui": {
+        "openButton": {
+          "type": "button",
+          "clientPath": "ui/uiButton.js"
+        }
+      }
+    },
+    "number-output-13": {
+      "description": {
+        "name": "number-output",
+        "alt": "for clicking",
+        "id": "number-output-13",
+        "path": "./modules/util/number.js",
+        "position": {
+          "left": 168,
+          "top": 1045
+        }
+      },
+      "inputs": {
+        "thru": {
+          "accepts": "any"
+        },
+        "evt": {
+          "accepts": "any"
+        }
+      },
+      "outputs": {
+        "out": {
+          "emits": "number",
+          "calls": [
+            {
+              "parentId": "gateCounter-9",
+              "key": "setCount"
+            },
+            {
+              "parentId": "gateCounter-12",
+              "key": "setCount"
+            }
+          ]
+        }
+      },
+      "state": {
+        "number": 1
+      },
+      "ui": {
+        "onNumberButton": {
+          "type": "button",
+          "clientPath": "ui/uiButton.js"
+        }
+      }
+    },
+    "logger-14": {
+      "description": {
+        "name": "logger",
+        "alt": "in ... out to console",
+        "id": "logger-14",
+        "path": "./modules/util/log.js",
+        "position": {
+          "left": 1319,
+          "top": -44
+        }
+      },
+      "inputs": {
+        "thru": {
+          "accepts": "any"
+        }
+      },
+      "outputs": {
+        "throughput": {
+          "emits": "any",
+          "calls": []
+        }
+      },
+      "state": {
+        "prefix": "JTN2:",
+        "message": "---"
+      },
+      "ui": {}
+    }
+  }
+}
\ No newline at end of file
diff --git a/robot.js b/robot.js
index ae66d5681cc31ec213e39278c4fa1bb7a6dddf09..6ec40ee6d16400a3008c099e4eb57ad3fdea7d58 100644
--- a/robot.js
+++ b/robot.js
@@ -16,11 +16,6 @@ var program = Programs.new('new program')
 - robot does forward transform with live [t1, t2]
 - robot displays forward transform with [t1, t2]
 
-- modules needed
- - object collector (i.e. collects inputs into a list, has 'output' and 'reset' input triggers / buttons as well)
- - two-up contencator (i.e. takes two inputs, puts them into arrays) 
- - gate opens, lets a count thru, shuts 
-
 */
 
 var link = Programs.loadModuleFromSource(program, './modules/hardware/atkseriallink.js')
@@ -55,6 +50,12 @@ mrbotone.outputs.pos.attach(log.inputs.thru)
 var canvas = Programs.loadModuleFromSource(program, './modules/ui/threeCanvas.js')
 Programs.setUI(canvas, 1500, 650)
 
+var collector = Programs.loadModuleFromSource(program, './modules/util/collector.js')
+Programs.setUI(collector, 1050, 800)
+
+var gateCounter = Programs.loadModuleFromSource(program, './modules/util/gateCounter.js')
+Programs.setUI(gateCounter, 600, 850)
+
 /*
 var stest = Programs.loadModuleFromSource(program, './modules/ui/stest.js')
 
diff --git a/rundmc.js b/rundmc.js
index 0e9922f09af5127ece32d54ec1151574bb0d99b2..189ac6897a24d3c8942e73d82cab851d5b5af59b 100644
--- a/rundmc.js
+++ b/rundmc.js
@@ -29,7 +29,9 @@ const Reps = require('./reps.js')
 const Programs = require('./programs.js')
 
 // the program object: real simple, just has a description, and a 'modules' 
-var program = Programs.new('new program')
+// var program = Programs.new('new program')
+
+var program = Programs.open('./programs/temp.json')
 
 /*
 var stest = Programs.loadModuleFromSource(program, './modules/ui/stest.js')