diff --git a/exec/load/frame.js b/exec/load/frame.js
index fe1f93d2aea378a6a4b9df7621eaed7d3945598a..875dc75b39e69a30e316957bd2f199b258ce4f37 100644
--- a/exec/load/frame.js
+++ b/exec/load/frame.js
@@ -33,6 +33,7 @@ METHODS:
 	frame.scrollTo(x,y)			//scroll frame to absolute offset
 	frame.move(x,y)				//move frame n spaces in any direction
 	frame.moveTo(x,y)			//move frame to absolute position
+	frame.getoff()				//retrieve frame data offset (x,y) 
 	frame.clearline(attr)		//see http://synchro.net/docs/jsobjs.html#console
 	frame.cleartoeol(attr)
 	frame.putmsg(str)
@@ -53,6 +54,10 @@ PROPERTIES:
 	frame.height				//frame height
 	frame.attr					//default attributes for frame
 	frame.checkbounds			//toggle true/false to restrict/allow frame movement outside display
+	frame.lf_strict				//toggle true/false to force newline after a crlf-terminated string
+	frame.v_scroll				//toggle true/false to enable/disable vertical scrolling
+	frame.h_scroll				//toggle true/false to enable/disable horizontal scrolling
+	frame.scrollbars_visible	//toggle true/false to show/hide scrollbars
 
 USAGE:
 
@@ -125,7 +130,8 @@ function Frame(x,y,width,height,attr,frame) {
 			width:undefined,
 			height:undefined,
 			canvas:{},
-			update:{}
+			update:{},
+			buffer:{}
 		}
 
 		/* protected properties */
@@ -261,22 +267,34 @@ function Frame(x,y,width,height,attr,frame) {
 			for(var y in properties.update) {
 				for(var x in properties.update[y]) {
 					var c = getTopCanvas(x,y);
-					list.push(getData(c,x,y));
+					var d = getData(c,x,y);
+					if(!properties.buffer[x])
+						properties.buffer[x] = {};
+					if(!properties.buffer[x][y] ||
+						properties.buffer[x][y].ch != d.ch || 
+						properties.buffer[x][y].attr != d.attr) {
+						properties.buffer[x][y] = d;
+						list.push(d);
+					}
 				}
 			}
 			return list.sort(updateSort);
 		}
 		function getData(c,x,y) {
-			var d = {};
+			var cd = {
+				x:Number(x),
+				y:Number(y)
+			};
 			if(c) {
-				d = c.frame.getData(x-c.xoff,y-c.yoff);
-				if(!d.attr)
-					d.attr = c.frame.attr;
-				d.id = c.frame.id;
+				var d = c.frame.getData(x-c.xoff,y-c.yoff,true);
+				cd.id = c.frame.id;
+				cd.ch = d.ch;
+				if(d.attr)
+					cd.attr = d.attr;
+				else
+					cd.attr = c.frame.attr;
 			}
-			d.x = Number(x);
-			d.y = Number(y);
-			return d;
+			return cd;
 		}
 		function updateSort(a,b) {
 			if(a.y == b.y)
@@ -325,8 +343,16 @@ function Frame(x,y,width,height,attr,frame) {
 		attr:undefined,
 		display:undefined,
 		data:[],
+		open:false,
 		id:0
 	}
+	var settings = {
+		v_scroll:true,
+		h_scroll:false,
+		scrollbars_visible:true,
+		lf_strict:false,
+		checkbounds:true
+	}
 	var relations = {
 		parent:undefined,
 		child:[]
@@ -334,28 +360,16 @@ function Frame(x,y,width,height,attr,frame) {
 	var position = {
 		cursor:{x:0,y:0},
 		offset:{x:0,y:0},
-		stored:{x:0,y:0},
-		checkbounds:true
+		stored:{x:0,y:0}
 	}
 		
 	/* protected properties */
-	this.__defineGetter__("id", function() {
-		if(relations.parent)
-			return relations.parent.id+""+properties.id;
-		return properties.id;
-	});
-	this.__defineGetter__("parent", function() {
-		return relations.parent;
-	});
 	this.__defineGetter__("child", function() {
 		return relations.child;
 	});
 	this.__defineSetter__("child", function(frame) {
 		relations.child.push(frame);
 	});
-	this.__defineGetter__("display", function() {
-		return properties.display;
-	});
 	this.__defineGetter__("attr", function() {
 		return properties.attr;
 	});
@@ -416,48 +430,126 @@ function Frame(x,y,width,height,attr,frame) {
 			throw("invalid height: " + height);
 		properties.height = height;
 	});
+
+	/* read-only properties */
+	this.__defineGetter__("id", function() {
+		if(relations.parent)
+			return relations.parent.id+"."+properties.id;
+		return properties.id;
+	});
+	this.__defineGetter__("parent", function() {
+		return relations.parent;
+	});
+	this.__defineGetter__("display", function() {
+		return properties.display;
+	});
+	this.__defineGetter__("data_height", function() {
+		return properties.data[0].length;
+	});
+	this.__defineGetter__("data_width", function() {
+		return properties.data.length;
+	});
+	this.__defineGetter__("data", function() {
+		return properties.data;
+	});
+	
+	/* protected settings */
 	this.__defineGetter__("checkbounds", function() {
-		return position.checkbounds;
+		return settings.checkbounds;
 	});
 	this.__defineSetter__("checkbounds", function(bool) {
 		if(typeof bool == "boolean")
-			position.checkbounds=bool;
+			settings.checkbounds=bool;
+	});
+	this.__defineGetter__("lf_strict", function() {
+		return settings.lf_strict;
+	});
+	this.__defineSetter__("lf_strict", function(bool) {
+		if(typeof bool == "boolean")
+			settings.lf_strict=bool;
+	});
+	this.__defineGetter__("scrollbars_visible", function() {
+		return settings.scrollbar;
+	});
+	this.__defineSetter__("scrollbars_visible", function(bool) {
+		if(typeof bool == "boolean")
+			settings.scrollbar=bool;
+	});
+	this.__defineGetter__("v_scroll", function() {
+		return settings.v_scroll;
+	});
+	this.__defineSetter__("v_scroll", function(bool) {
+		if(typeof bool == "boolean")
+			settings.v_scroll=bool;
+	});
+	this.__defineGetter__("h_scroll", function() {
+		return settings.h_scroll;
+	});
+	this.__defineSetter__("h_scroll", function(bool) {
+		if(typeof bool == "boolean")
+			settings.h_scroll=bool;
 	});
 
 	/* public methods */
-	this.getData = function(x,y) {
-		return properties.data[x + position.offset.x][y + position.offset.y];
+	this.getData = function(x,y,use_offset) {
+		if(use_offset) 
+			return properties.data[x + position.offset.x][y + position.offset.y];
+		else
+			return properties.data[x][y];
 	}
-	this.setData = function(x,y,ch,attr) {
-		if(ch)
-			properties.data[x + position.offset.x][y + position.offset.y].ch = ch;
-		if(attr)
-			properties.data[x + position.offset.x][y + position.offset.y].attr = attr;
+	this.setData = function(x,y,ch,attr,use_offset) {
+		if(use_offset) {
+			if(ch)
+				properties.data[x + position.offset.x][y + position.offset.y].ch = ch;
+			if(attr)
+				properties.data[x + position.offset.x][y + position.offset.y].attr = attr;
+			if(properties.open) 
+				properties.display.updateChar(this,x,y);
+		}
+		else {
+			if(ch)
+				properties.data[x][y].ch = ch;
+			if(attr)
+				properties.data[x][y].attr = attr;
+			if(properties.open && (x >= position.offset.x && x < position.offset.x + this.width &&
+				y >= position.offset.y && y < position.offset.y + this.height))
+				properties.display.updateChar(this,x,y);
+		}
 	}
 	this.bottom = function() {
-		for each(var c in relations.child) 
-			c.bottom();
-		properties.display.bottom(this);
+		if(properties.open) {
+			for each(var c in relations.child) 
+				c.bottom();
+			properties.display.bottom(this);
+		}
 	}
 	this.top = function() {
-		properties.display.top(this);
-		for each(var c in relations.child) 
-			c.top();
+		if(properties.open) {
+			properties.display.top(this);
+			for each(var c in relations.child) 
+				c.top();
+		}
 	}
 	this.open = function() {
 		properties.display.open(this);
+		properties.open = true;
 		for each(var c in relations.child) 
 			c.open();
 	}
 	this.refresh = function() {
-		properties.display.updateFrame(this);
-		for each(var c in relations.child) 
-			c.refresh();
+		if(properties.open) {
+			properties.display.updateFrame(this);
+			for each(var c in relations.child) 
+				c.refresh();
+		}
 	}
 	this.close = function() {
-		for each(var c in relations.child) 
-			c.close();
-		properties.display.close(this);
+		if(properties.open) {
+			for each(var c in relations.child) 
+				c.close();
+			properties.display.close(this);
+			properties.open = false;
+		}
 	}
 	this.move = function(x,y) {
 		var nx = undefined;
@@ -496,7 +588,10 @@ function Frame(x,y,width,height,attr,frame) {
 			c.moveTo(x + (c.x - this.x), y + (c.y - this.y));
 	}
 	this.draw = function() {
-		this.refresh();
+		if(properties.open)
+			this.refresh();
+		else
+			this.open();
 		this.cycle();
 	}
 	this.cycle = function() {
@@ -615,7 +710,6 @@ function Frame(x,y,width,height,attr,frame) {
 				}
 				y++;
 			}
-			this.open();
 			break;
 		case "BIN":
 			if(width == undefined || height == undefined)
@@ -638,7 +732,6 @@ function Frame(x,y,width,height,attr,frame) {
 				}
 			}
 			f.close();
-			this.open();
 			break;
 		default:
 			throw("unsupported filetype");
@@ -657,18 +750,25 @@ function Frame(x,y,width,height,attr,frame) {
 		}
 		/* otherwise, adjust the x/y offset */
 		else {
-			if(typeof x == "number")
+			var update = false;
+			if(typeof x == "number") {
 				position.offset.x += x;
-			if(typeof y == "number")
+				if(position.offset.x < 0)
+					position.offset.x = 0;
+				else if(position.offset.x + this.width > this.data_width)
+					position.offset.x = this.data_width - this.width;
+				update = true;
+			}
+			if(typeof y == "number") {
 				position.offset.y += y;
-			if(position.offset.x < 0)
-				position.offset.x = 0;
-			else if(position.offset.x + this.width > properties.data.length)
-				position.offset.x = properties.data.length - this.width;
-			if(position.offset.y < 0)
-				position.offset.y = 0;
-			else if(position.offset.y + this.height > properties.data[0].length)
-				position.offset.y = properties.data[0].length - this.height;
+				if(position.offset.y < 0)
+					position.offset.y = 0;
+				else if(position.offset.y + this.height > this.data_height)
+					position.offset.y = this.data_height - this.height;
+				update = true;
+			}
+			if(update)
+				this.refresh();
 		}
 	}
 	this.scrollTo = function(x,y) {
@@ -814,6 +914,10 @@ function Frame(x,y,width,height,attr,frame) {
 					break;
 				case ']':	/* LF */
 					pos.y++;
+					if(settings.lf_strict && pos.y >= this.height) {	
+						this.scroll();
+						pos.y--;
+					}
 					break;
 				default:	/* Other stuff... specifically, check for right movement */
 					if(ch.charCodeAt(0)>127) {
@@ -837,6 +941,10 @@ function Frame(x,y,width,height,attr,frame) {
 					break;
 				case '\n':
 					pos.y++;
+					if(settings.lf_strict && pos.y >= this.height) {	
+						this.scroll();
+						pos.y--;
+					}
 					break;
 				default:
 					putChar.call(this,ch,curattr);
@@ -870,6 +978,13 @@ function Frame(x,y,width,height,attr,frame) {
 		}
 		return xy;
 	}
+	this.getoff = function() {
+		var xy = {
+			x:position.offset.x,
+			y:position.offset.y
+		};
+		return xy;
+	}
 	this.pushxy = function() {
 		position.stored.x = position.cursor.x;
 		position.stored.y = position.cursor.y;
@@ -878,30 +993,70 @@ function Frame(x,y,width,height,attr,frame) {
 		position.cursor.x = position.stored.x;
 		position.cursor.y = position.stored.y;
 	}
+	this.up = function(n) {
+		if(isNaN(n))
+			n = 1;
+		position.cursor.y-=n;
+		if(position.cursor.y < 0) {
+			if(position.offset.y > 0)
+				this.scroll(0,-(n));
+			position.cursor.y = 0;
+		}
+	}
+	this.down = function(n) {
+		if(isNaN(n))
+			n = 1;
+		position.cursor.y+=n;
+		if(position.cursor.y > this.height-1) {
+			if(position.offset.y < this.data_height - this.height)
+				this.scroll(0,n);
+			position.cursor.y = this.height-1;
+		}
+	}
+	this.left = function(n) {
+		if(isNaN(n))
+			n = 1;
+		position.cursor.x-=n;
+		if(position.cursor.x < 0) {
+			if(position.offset.x > 0)
+				this.scroll(-(n),0);
+			position.cursor.x=0;
+		}
+	}
+	this.right = function(n) {
+		if(isNaN(n))
+			n = 1;
+		position.cursor.x+=n;
+		if(position.cursor.x > this.width-1) {
+			if(position.offset.x < this.data_width - this.width)
+				this.scroll(n,0);
+			position.cursor.x=this.width-1;
+		}
+	}
 	
 	/* private functions */
 	function checkX(x) {
-		if(	isNaN(x) || (position.checkbounds &&  
+		if(	isNaN(x) || (settings.checkbounds &&  
 			(x > properties.display.x + properties.display.width || 
 			x < properties.display.x)))
 			return false;
 		return true;
 	}
 	function checkY(y) {
-		if( isNaN(y) || (position.checkbounds && 
+		if( isNaN(y) || (settings.checkbounds && 
 			(y > properties.display.y + properties.display.height || 
 			y < properties.display.y)))
 			return false;
 		return true;
 	}
 	function checkWidth(x,width) {
-		if(	width < 1 || isNaN(width) || (position.checkbounds && 
+		if(	width < 1 || isNaN(width) || (settings.checkbounds && 
 			x + width > properties.display.x + properties.display.width))
 			return false;
 		return true;
 	}
 	function checkHeight(y,height) {
-		if( height < 1 || isNaN(height) || (position.checkbounds && 
+		if( height < 1 || isNaN(height) || (settings.checkbounds && 
 			y + height > properties.display.y + properties.display.height))
 			return false;
 		return true;
@@ -915,21 +1070,13 @@ function Frame(x,y,width,height,attr,frame) {
 			this.scroll();
 			position.cursor.y--;
 		}
-		if(ch)
-			properties.data
-			[position.cursor.x + position.offset.x]
-			[position.cursor.y + position.offset.y].ch = ch;
-		if(attr)
-			properties.data
-			[position.cursor.x + position.offset.x]
-			[position.cursor.y + position.offset.y].attr = attr;
-		properties.display.updateChar(this,position.cursor.x,position.cursor.y);
+		this.setData(position.cursor.x,position.cursor.y,ch,attr,true);
 	}
 	function init(x,y,width,height,attr,frame) {
 		if(frame instanceof Frame) {
 			properties.id = frame.child.length;
 			properties.display = frame.display;
-			position.checkbounds = frame.checkbounds;
+			settings.checkbounds = frame.checkbounds;
 			relations.parent = frame;
 			frame.child = this;
 		}
@@ -949,6 +1096,7 @@ function Frame(x,y,width,height,attr,frame) {
 				properties.data[w][h] = new Char();
 			}
 		}
+		
 		//log(LOG_DEBUG,format("new frame initialized: %sx%s at %s,%s",this.width,this.height,this.x,this.y));
 	}
 	init.apply(this,arguments);