diff --git a/ISA.txt b/ISA.txt deleted file mode 100644 index b4669a7..0000000 --- a/ISA.txt +++ /dev/null @@ -1,29 +0,0 @@ -INSTRUCTIONS ---- Control Flow --- -0x00: NoOp - 0 Parameter -0x10: Goto - 1 Parameter - moves the program counter to (P1) in program memory -0x11: Goto if low bit - 2 Parameter - Moves the program counter to (P1) if register (P2)'s least significant bit is 1. ---- Memory and register management --- -0x20: Load to register - 2 Parameter - Loads into register (P1) the byte at processing memory location (P2) -0x21: Write register to memory - 2 Parameter - Writes the byte in register (P1) to the processing memory location (P2) -0x28: Copy Register -> Register - 2 Parameter - Copies byte from register (P1) to register (P2) -0x2F: Assign value to register - 2 Parameter - Assigns register (P1) to value (P2) ---- Operations --- -0x30: increment register - 1 Parameter - Increments register (P1) by 1. -0x31: decrement register - 1 Parameter - Decrements register (P1) by 1. -0x40: Add registers - 2 Parameter - Adds the contents of (P1) and (P2) and stores result to register (P1). (Overflow will be taken mod 256) -0x41: Reserved ---- Bit Operations --- -0x48: Bitwise and - 2 Parameter - Ands & each bit of register (P1) and register (P2) and stores result to register (P1) -0x49: Bitwise or - you get the point -0x4A: Bitwise not - 1 Parameter - Inverts each bit of register (P1) -0x4B: Left bit shift - 2 Parameter - Shifts bits in register (P1) to the left by (P2) and stores result to register (P1) -0x4C: right bit shift- 2 Parameter - same as left ---- Comparison Operations --- -0x50: Equals - 3 Parameter - If byte in register (P1) equals byte in register (P2), set byte in register (P3) to 0x01 -0x51: Less than - 3 Parameter - If byte in register (P1) less than byte in register (P2), set byte in register (P3) to 0x01 -0x52: Greater than - 3 Parameter - If byte in register (P1) greater than byte in register (P2), set byte in register (P3) to 0x01 ---- Development --- -0xFE: Print byte as ASCII from register - 1 Parameter - Prints the ASCII byte in register (P1) to console -0xFF: Print byte from register - 1 Parameter - Prints the byte in register (P1) to console -0x66: Halt and Catch Fire - 0 Parameter - Fire! FIRE EVERYWHERE!!!!! \ No newline at end of file diff --git a/TODO b/TODO deleted file mode 100644 index 19a35ee..0000000 --- a/TODO +++ /dev/null @@ -1,14 +0,0 @@ -Live memory and register editing (Probably should pause autostep when it reaches the cell you're modifying) -HCF flames -add hcf -Move start/stop/auto logic into computer -Speed control slider behavior -Speed control slider styling - -UI for screen (toggling (click an icon?)) -UI for togging other UI elements - -UI for showing which Memory bank is selected -VRAM select instruction - -Error log diff --git a/index.html b/index.html deleted file mode 100644 index b7e2086..0000000 --- a/index.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - Virtual 8-Bit Computer - - -
-
VIRTUAL 8-BIT COMPUTER
-
-
-
←REGISTERS
-
MEMORY↯
-
-
-
-
-
- - - - - - -
- -
- -

-	
-
diff --git a/src/computer.ts b/src/computer.ts
index aeb3572..b446b0b 100644
--- a/src/computer.ts
+++ b/src/computer.ts
@@ -1,7 +1,7 @@
 import { CpuEvent, CpuEventHandler, UiEvent, UiEventHandler } from "./events";
 import { byte_array_to_js_source, format_hex } from "./etc";
 import { Instruction, ISA } from "./instructionSet";
-import { m256, u1, u2, u3, u8 } from "./num";
+import { m256, u2, u3, u8 } from "./num";
 
 export type TempInstrState = {
 	pos: u8;
@@ -178,6 +178,7 @@ export class Computer {
 			for (let i = 0; i < cycle_count; i++) this.cycle();
 		});
 		ui.listen(UiEvent.RequestMemoryChange, ({ address, value }) => this.setMemory(address, value));
+		ui.listen(UiEvent.RequestRegisterChange, ({ register_no, value }) => this.setRegister(register_no, value));
 	}
 
 	load_memory(program: Array): void {
diff --git a/src/etc.ts b/src/etc.ts
index 8cc0a10..1cb865e 100644
--- a/src/etc.ts
+++ b/src/etc.ts
@@ -31,13 +31,20 @@ export const byte_array_to_js_source = (bytes: Array): string => {
  * @param type
  * @param id id attribute to set
  */
-export function el(type: E, id?: string): HTMLElementTagNameMap[E];
-export function el(type: string, id?: string): HTMLElement | undefined {
+export function el(
+	type: E,
+	id?: string,
+	class_list?: string
+): HTMLElementTagNameMap[E];
+export function el(type: string, id?: string, class_list?: string): HTMLElement | undefined {
 	const element = document.createElement(type);
-	if (id === undefined) {
-		return element;
+	if (id !== undefined) {
+		element.id = id;
 	}
-	element.id = id;
+	if (class_list !== undefined) {
+		element.className = class_list;
+	}
+
 	return element;
 }
 
@@ -49,3 +56,13 @@ export function in_range(check: number, start: number, end: number): boolean {
 	if (check >= start && check <= end) return true;
 	return false;
 }
+
+export function at(l: Array, i: number): T | null {
+	if (i < 0) {
+		return null;
+	}
+	if (i >= l.length) {
+		return null;
+	}
+	return l[i];
+}
diff --git a/src/events.ts b/src/events.ts
index 64c838b..50e8113 100644
--- a/src/events.ts
+++ b/src/events.ts
@@ -5,7 +5,7 @@
  */
 import { EventHandler } from "./eventHandler";
 import { Instruction, ParameterType } from "./instructionSet";
-import { u1, u2, u3, u8 } from "./num";
+import { u2, u3, u8 } from "./num";
 
 //
 // CPU Event Handler Definition
@@ -65,18 +65,28 @@ export const CpuEventHandler = EventHandler as CpuEventHandlerConstruc
 //
 
 export enum UiEvent {
+	// Maybe move these into a UI -> CPU signal system?
 	RequestCpuCycle,
 	RequestMemoryChange,
+	RequestRegisterChange,
+	// Ui Events
+	EditOn,
+	EditOff,
 }
 
 interface UiEventMap {
 	[UiEvent.RequestCpuCycle]: number;
 	[UiEvent.RequestMemoryChange]: { address: u8; value: u8 };
+	[UiEvent.RequestRegisterChange]: { register_no: u3; value: u8 };
 }
 
+type VoidDataUiEventList = UiEvent.EditOn | UiEvent.EditOff;
+
 export interface UiEventHandler extends EventHandler {
 	listen(type: E, listener: (ev: UiEventMap[E]) => void): void;
 	dispatch(type: E, data: UiEventMap[E]): void;
+	listen(type: E, listener: () => void): void;
+	dispatch(type: E): void;
 }
 
 interface UiEventHandlerConstructor {
diff --git a/src/index.ts b/src/index.ts
index 8ea7f35..01e474a 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -22,26 +22,9 @@ declare global {
 }
 
 function main(): void {
-	// const program: Array = [
-	// 	0x2f, 0x00, 0xf0, 0x20, 0x07, 0x00, 0x50, 0x05, 0x06, 0x07, 0x11, 0x00, 0x05, 0xfe, 0x07, 0x30, 0x00, 0x10, 0x03,
-	// 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	// 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	// 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	// 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	// 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	// 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	// 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	// 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	// 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	// 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	// 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	// 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57,
-	// 	0x6f, 0x72, 0x6c, 0x64, 0x21, 0x0a, 0x00, 0x00, 0x00,
-	// ];
-
 	const program: Array = [
-		0x2f, 0x00, 0x00, 0x2f, 0x01, 0xff, 0x21, 0x01, 0x0d, 0xb1, 0x01, 0x21, 0x01, 0x00, 0x31, 0x01, 0xb1, 0x00, 0x10,
-		0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		0x19, 0x00, 0xf0, 0x14, 0x00, 0x01, 0x5e, 0x00, 0xf0, 0x01, 0x21, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
 		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -55,24 +38,6 @@ function main(): void {
 		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57,
 		0x6f, 0x72, 0x6c, 0x64, 0x21, 0x0a, 0x00, 0x00, 0x00,
 	];
-
-	// const program = [
-	// 	0x01, 0x00, 0x01, 0x00, 0xa0, 0x10, 0xff, 0x00, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0xa1,
-	// 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	// 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	// 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	// 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	// 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	// 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	// 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	// 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	// 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	// 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	// 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-	// 	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57,
-	// 	0x6f, 0x72, 0x6c, 0x64, 0x21, 0x0a, 0x00, 0x00, 0x00,
-	// ];
-
 	const computer = new Computer();
 
 	const ui = new UI();
@@ -137,10 +102,5 @@ function main(): void {
 }
 
 document.addEventListener("DOMContentLoaded", () => {
-	// at least you know it's bad
-	try {
-		main();
-	} catch (e) {
-		alert(e);
-	}
+	main();
 });
diff --git a/src/instructionSet.ts b/src/instructionSet.ts
index ab1d3d0..3c6c5a2 100644
--- a/src/instructionSet.ts
+++ b/src/instructionSet.ts
@@ -170,6 +170,34 @@ ISA.insertInstruction(0x13, {
 		c.setRegister(register_no_2, c.getRegister(register_no_1));
 	},
 });
+ISA.insertInstruction(0x14, {
+	name: "Load RM -> R",
+	desc: "Copy the byte in memory addressed by register (P1) to register (P2)",
+	params: [
+		new RegisParam("Copy the byte in the memory cell addressed in this register"),
+		new RegisParam("To this register"),
+	],
+	execute(c, p) {
+		const [register_no_1, register_no_2] = p;
+		if (!isU3(register_no_1)) throw new Error("todo");
+		if (!isU3(register_no_2)) throw new Error("todo");
+		c.setRegister(register_no_2, c.getMemory(c.getRegister(register_no_1)));
+	},
+});
+ISA.insertInstruction(0x15, {
+	name: "Save R -> RM",
+	desc: "Copy the byte in register (P1) to the memory cell addressed in register (P2)",
+	params: [
+		new RegisParam("Copy the value in this register"),
+		new RegisParam("To the memory cell addressed in this register"),
+	],
+	execute(c, p) {
+		const [register_no_1, register_no_2] = p;
+		if (!isU3(register_no_1)) throw new Error("todo");
+		if (!isU3(register_no_2)) throw new Error("todo");
+		c.setMemory(c.getRegister(register_no_2), c.getRegister(register_no_1));
+	},
+});
 
 ISA.insertInstruction(0x17, {
 	name: "Zero Register",
@@ -608,6 +636,7 @@ ISA.insertInstruction(0x50, {
 		c.setRegister(register_no_1, m256(sum));
 	},
 });
+
 ISA.insertInstruction(0x51, {
 	name: "Add",
 	desc: "Adds to the byte in register (P1) with the value in register (P2)",
@@ -620,8 +649,9 @@ ISA.insertInstruction(0x51, {
 		c.setRegister(register_no_1, m256(sum));
 	},
 });
+
 ISA.insertInstruction(0x52, {
-	name: "Add",
+	name: "Subtract",
 	desc: "Subtracts from the value in register (P1) by the value in register (P2)",
 	params: [new RegisParam("set this register to"), new RegisParam("it's difference with the value in this register")],
 	execute(c, p) {
@@ -635,8 +665,9 @@ ISA.insertInstruction(0x52, {
 		c.setRegister(register_no_1, m256(difference));
 	},
 });
+
 ISA.insertInstruction(0x53, {
-	name: "Add",
+	name: "Subtract",
 	desc: "Subtracts from the value in register (P1) by the constant value (P2)",
 	params: [new RegisParam("set this register to"), new ConstParam("it's difference with this constant")],
 	execute(c, p) {
diff --git a/src/style.scss b/src/style.scss
index 35f8077..347237e 100644
--- a/src/style.scss
+++ b/src/style.scss
@@ -8,16 +8,6 @@ pre {
 
 :root {
 	--Border: #ffff00;
-	// --mem-instruction: #adff2f;
-	// --mem-register: #800080;
-	// --mem-constant: #d3d3d3;
-	// --mem-memory: #d3d3d3;
-	// --mem-invalid: #ff0000;
-	// --mem-instruction: #2f962a;
-	// --mem-register: #dc21d1;
-	// --mem-constant: #d3d3d3;
-	// --mem-memory: #4d86f0;
-	// --mem-invalid: #bf2e2e;
 	--mem-instruction: #3af78f;
 	--mem-memory: #ff26a8;
 	--mem-register: #9e0ef7;
@@ -25,6 +15,10 @@ pre {
 	--mem-invalid: #bf2e2e;
 }
 
+img {
+	image-rendering: pixelated;
+}
+
 body {
 	color: #808080;
 	background-color: black;
@@ -44,13 +38,13 @@ body {
 #main {
 	justify-content: center;
 	display: grid;
-	grid-template-columns: min-content max-content max-content 10px 500px;
+	grid-template-columns: min-content max-content max-content min-content 500px;
 	grid-template-rows: min-content 1.5fr 10px 2fr 2fr min-content;
 	grid-template-areas:
 		"cycles registers regmemlabel . explainer "
-		"title .         .           . explainer "
-		"title .         .           . ."
-		"title .         .           . printout "
+		"title .         .           ribbon explainer "
+		"title .         .           ribbon ."
+		"title .         .           ribbon printout "
 		"title .         .           . printout "
 		".     buttons   buttons     . .";
 	#memory {
@@ -70,6 +64,9 @@ body {
 		user-select: none;
 		transform: scale(-1, -1);
 	}
+	#ribbon_menu {
+		grid-area: ribbon;
+	}
 }
 
 #printout {
@@ -124,6 +121,17 @@ body {
 	--color: var(--mem-instruction);
 }
 
+div[contenteditable] {
+	&.caret_selected {
+		box-sizing: border-box;
+		outline: 2px solid red;
+	}
+	outline: none;
+}
+
+.pending_edit {
+	color: green;
+}
 #memory {
 	grid-area: memory;
 	display: grid;
@@ -131,21 +139,29 @@ body {
 	gap: 5px;
 	padding: 10px;
 	border: 5px solid yellow;
+
 	div {
+		user-select: none;
+		caret-color: transparent;
 		text-align: center;
 		color: var(--color);
 	}
+
 	.program_counter {
 		outline: 3px solid orange;
 	}
+
 	.instruction_argument,
 	.current_instruction {
 		outline: 3px dashed var(--color);
 	}
-
+	.recent_edit {
+		color: lime;
+	}
 	div.last_access {
 		color: orange;
 	}
+
 	.invalid {
 		&::after {
 			user-select: none;
@@ -159,10 +175,31 @@ body {
 	}
 }
 
+div#main.editor {
+	#memory,
+	#registers {
+		border-style: dashed;
+		div {
+			cursor: text;
+		}
+	}
+}
+
+#ribbon_menu {
+	margin-inline: 8px;
+	.editor_toggle {
+		//TODO CHANGE COLORS WHen
+		&.off {
+		}
+		&.on {
+		}
+	}
+}
+
 #registers {
 	grid-area: registers;
 	border: 5px solid yellow;
-	border-bottom: none;
+	border-bottom: none !important;
 
 	grid-template-columns: repeat(8, min-content);
 	max-width: fit-content;
@@ -191,6 +228,25 @@ label.button {
 	user-select: none;
 }
 
+button:disabled {
+	border: 4px solid rgb(255, 255, 128);
+	color: orange;
+	cursor: default;
+}
+
+input[type="range"]:disabled {
+	cursor: default;
+}
+
+button.no_style {
+	border: none;
+	color: inherit;
+	margin: 0;
+	padding: 0;
+	font-size: inherit;
+	background-color: inherit;
+}
+
 button:hover,
 label.button:hover {
 	color: white;
@@ -201,6 +257,10 @@ label.button:hover {
 
 	display: flex;
 	gap: 10px;
+	#controls_buttons {
+		display: flex;
+		gap: inherit;
+	}
 }
 
 input[type="range"] {
@@ -208,8 +268,8 @@ input[type="range"] {
 	-webkit-appearance: none;
 	appearance: none;
 	margin: 18px 0;
-	// width: 100%;
 }
+
 input[type="range"]:focus {
 	outline: none;
 }
diff --git a/src/ui.ts b/src/ui.ts
index fd80428..84450b0 100644
--- a/src/ui.ts
+++ b/src/ui.ts
@@ -1,16 +1,13 @@
 import { CpuEvent, CpuEventHandler, UiEvent, UiEventHandler } from "./events";
-import { $, el, format_hex } from "./etc";
+import { $ } from "./etc";
 import { InstructionExplainer } from "./ui/instructionExplainer";
 import { MemoryView } from "./ui/memoryView";
 import { frequencyIndicator } from "./ui/frequencyIndicator";
 import { RegisterView } from "./ui/registerView";
 import { Screen } from "./ui/screen";
-import { UiComponent, UiComponentConstructor } from "./ui/uiComponent.js";
-// Certainly the messiest portion of this program
-// Needs to be broken into components
-// Breaking up into components has started but has yet to conclude
-
-let delay = 100;
+import { Ribbon } from "./ui/ribbon";
+import { UiComponent, UiComponentConstructor } from "./ui/uiComponent";
+import { pausePlay } from "./ui/pausePlay";
 
 export class UI {
 	printout: HTMLElement;
@@ -34,32 +31,12 @@ export class UI {
 		this.register_component(InstructionExplainer, $("instruction_explainer"));
 		this.register_component(RegisterView, $("registers"));
 		this.register_component(Screen, $("screen") as HTMLCanvasElement);
+		this.register_component(Ribbon, $("ribbon_menu"));
+		this.register_component(pausePlay, $("controls_buttons"));
 		this.printout = $("printout");
 
 		this.auto_running = false;
 		const pp_button = $("pause_play_button");
-
-		pp_button.addEventListener("click", () => {
-			if (this.auto_running) {
-				this.stop_auto();
-				pp_button.textContent = "Starp";
-			} else {
-				this.start_auto();
-				pp_button.textContent = "Storp";
-			}
-		});
-		$("step_button").addEventListener("click", () => {
-			if (this.auto_running) {
-				this.stop_auto();
-			}
-
-			this.events.dispatch(UiEvent.RequestCpuCycle, 1);
-		});
-
-		$("delay_range").addEventListener("input", (e) => {
-			delay = parseInt((e.target as HTMLInputElement).value, 10);
-			// console.log(delay);
-		});
 	}
 	private register_component(c: UiComponentConstructor, e: HTMLElement): void {
 		if (e === undefined) {
@@ -79,34 +56,14 @@ export class UI {
 		});
 
 		for (const c of this.components) {
-			c.init_cpu_events(cpu_events);
+			if (c.init_cpu_events) c.init_cpu_events(cpu_events);
 		}
 	}
 
 	reset(): void {
-		this.stop_auto();
 		for (const c of this.components) {
 			c.reset();
 		}
 		this.printout.textContent = "";
 	}
-
-	start_auto(speed: number = 200): void {
-		if (this.auto_running) {
-			return;
-		}
-		this.auto_running = true;
-		const loop = (): void => {
-			if (this.auto_running === false) {
-				return;
-			}
-			this.events.dispatch(UiEvent.RequestCpuCycle, 1);
-			setTimeout(loop, delay);
-		};
-		loop();
-	}
-
-	stop_auto(): void {
-		this.auto_running = false;
-	}
 }
diff --git a/src/ui/celledViewer.ts b/src/ui/celledViewer.ts
index 672ddd4..b75520a 100644
--- a/src/ui/celledViewer.ts
+++ b/src/ui/celledViewer.ts
@@ -6,7 +6,6 @@
 import { NonEmptyArray, el, format_hex } from "../etc";
 import { u8 } from "../num";
 
-// TODO, make generic
 interface GenericCell {
 	el: HTMLElement;
 }
@@ -22,7 +21,7 @@ export abstract class CelledViewer {
 		this.height = height;
 		for (let i = 0; i < this.width * this.height; i++) {
 			const mem_cell_el = el("div");
-			mem_cell_el.textContent = "00";
+			mem_cell_el.append("0", "0");
 			this.element.appendChild(mem_cell_el);
 			const mem_cell = { el: mem_cell_el };
 			this.cells.push(mem_cell);
@@ -31,7 +30,7 @@ export abstract class CelledViewer {
 
 	reset(): void {
 		for (let i = 0; i < this.height * this.width; i++) {
-			this.cells[i].el.textContent = "00";
+			this.set_cell_value(i as u8, 0);
 			this.cells[i].el.className = "";
 		}
 	}
@@ -59,6 +58,10 @@ export abstract class CelledViewer {
 	}
 
 	set_cell_value(address: u8, value: u8): void {
-		this.cells[address].el.textContent = format_hex(value);
+		const str = format_hex(value);
+		const a = str[0];
+		const b = str[1];
+		this.cells[address].el.textContent = "";
+		this.cells[address].el.append(a, b);
 	}
 }
diff --git a/src/ui/editableHex.ts b/src/ui/editableHex.ts
index f3eafbe..3c83ead 100644
--- a/src/ui/editableHex.ts
+++ b/src/ui/editableHex.ts
@@ -1,122 +1,112 @@
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-function set_caret(el: any, pos: number): boolean {
-	const selection = window.getSelection();
-	const range = document.createRange();
-	if (selection === null) {
-		return false;
-	}
+// This file was cobbled together and is the messiest part of this project
 
-	selection.removeAllRanges();
-	range.selectNode(el);
+import { at } from "../etc";
+import { u8 } from "../num";
 
-	range.setStart(el, pos);
-	range.setEnd(el, pos);
-	range.collapse(true);
-	selection.removeAllRanges();
-	selection.addRange(range);
-	el.focus();
-	return true;
-}
+const HEX_CHARACTERS = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"];
 
-function get_caret(el: HTMLElement): null | number {
-	const sel = window.getSelection();
-	if (sel === null) {
-		return null;
-	}
-	const pos = sel.getRangeAt(0).startOffset;
-	const endPos = pos + Array.from(el.innerHTML.slice(0, pos)).length - el.innerHTML.slice(0, pos).split("").length;
-	return endPos;
-}
+export class EditorContext {
+	private list: Array;
+	private width: number;
+	private height: number;
+	private enabled: boolean = false;
+	private current_cell_info: { left?: string; right?: string; old?: string };
+	private edit_callback: (n: number, value: u8) => void;
+	constructor(list: Array, width: number, height: number, callback: (n: number, value: u8) => void) {
+		this.list = list;
+		this.width = width;
+		this.height = height;
+		this.edit_callback = callback;
+		this.current_cell_info = {};
 
-const hex_characters = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"];
-function replace_non_hex(c: string): string {
-	if (hex_characters.includes(c)) {
-		return c;
-	}
-	return "0";
-}
+		for (const [i, cell] of this.list.entries()) {
+			cell.setAttribute("spellcheck", "false");
+			cell.addEventListener("keydown", (e) => {
+				this.keydown(e, i);
+			});
+			cell.addEventListener("focus", () => {
+				if (!this.enabled) return;
+				this.current_cell_info.old = cell.textContent ?? "00";
+				this.current_cell_info.left = undefined;
+				this.current_cell_info.right = undefined;
+				cell.classList.add("caret_selected");
 
-function editable_constraints(e: Event): boolean {
-	const target = e.target as HTMLDivElement;
-	const text = target.innerHTML ?? "";
-	if (text.length !== 2) {
-		const pos = get_caret(target);
-		const new_str = [...(target.textContent ?? "").substring(0, 2).padStart(2, "0").toUpperCase()]
-			.map(replace_non_hex)
-			.join("");
-		target.innerHTML = "";
-		// For the caret selection to work right, each character must be its own node, complicating this greatly
-		target.append(new_str.substring(0, 1), new_str.substring(1));
+				// Reset cursor position (I know there's an API for this, but this is a simpler, more robust solution)
+				cell.textContent = cell.textContent ?? "00";
+			});
 
-		if (pos !== null) {
-			if (pos >= 2) {
-				return true;
-			}
-			set_caret(target, pos);
+			cell.addEventListener("blur", () => {
+				const left = this.current_cell_info.left;
+				const right = this.current_cell_info.right;
+				cell.classList.remove("caret_selected");
+				if (left === undefined || right === undefined) {
+					cell.textContent = this.current_cell_info.old ?? "00";
+				} else if (left !== undefined && right !== undefined) {
+					const text = `${left}${right}`;
+					cell.textContent = text;
+					const val = Number.parseInt(text, 16) as u8;
+					this.edit_callback(i, val);
+					cell.classList.add("recent_edit");
+				}
+			});
 		}
 	}
-	return false;
-}
 
-function at(l: Array, i: number): T | null {
-	if (i < 0) {
-		return null;
+	enable(): void {
+		this.enabled = true;
+		for (const cell of this.list) {
+			cell.setAttribute("contenteditable", "true");
+		}
 	}
-	if (i >= l.length) {
-		return null;
+	disable(): void {
+		this.enabled = false;
+		for (const cell of this.list) {
+			cell.removeAttribute("contenteditable");
+			cell.blur();
+		}
+		this.current_cell_info = {};
 	}
-	return l[i];
-}
 
-export function make_editable(
-	list: Array,
-	width: number,
-	height: number,
-	on_edit: (n: number, value: string) => void
-): void {
-	for (const [i, cell] of list.entries()) {
-		cell.setAttribute("contenteditable", "true");
-		cell.setAttribute("spellcheck", "false");
-		const next: null | HTMLElement = at(list, i + 1);
-		const prev: null | HTMLElement = at(list, i - 1);
-		const up: null | HTMLElement = at(list, i - width);
-		const down: null | HTMLElement = at(list, i + width);
-		cell.addEventListener("keydown", (e) => {
-			const caret_position = get_caret(cell);
-			const k = e.key;
-			if (k === "ArrowUp") {
-				(up ?? prev)?.focus();
-				cell.blur();
-			} else if (k === "ArrowDown") {
-				(down ?? next)?.focus();
-				cell.blur();
-			} else if ((k === "ArrowLeft" || k === "Backspace") && caret_position === 0) {
-				prev?.focus();
-				cell.blur();
-			} else if (k === "ArrowRight" && caret_position === 1) {
-				next?.focus();
-				cell.blur();
-			} else if (k === "Enter") {
-				cell.blur();
-			} else if (k === "Escape") {
-				cell.blur();
-				return;
-			} else {
-				return;
-			}
-			e.preventDefault();
-		});
-		let previous_text = cell.textContent ?? "";
-		cell.addEventListener("input", (e) => {
-			const current_text = cell.textContent ?? "";
-			if (current_text !== previous_text) {
-				previous_text = cell.textContent ?? "";
-				on_edit(i, current_text);
-			}
-			if (editable_constraints(e) === true) {
+	private keydown(e: KeyboardEvent, cell_index: number): void {
+		if (!this.enabled) return;
+		const cell = e.target as HTMLElement;
+
+		const next: null | HTMLElement = at(this.list, cell_index + 1);
+		const prev: null | HTMLElement = at(this.list, cell_index - 1);
+		const up: null | HTMLElement = at(this.list, cell_index - this.width);
+		const down: null | HTMLElement = at(this.list, cell_index + this.width);
+
+		const k = e.key;
+		if (k === "ArrowUp") {
+			(up ?? prev)?.focus();
+			cell.blur();
+		} else if (k === "ArrowDown") {
+			(down ?? next)?.focus();
+			cell.blur();
+		} else if (k === "ArrowLeft" || k === "Backspace") {
+			prev?.focus();
+			cell.blur();
+		} else if (k === "ArrowRight") {
+			next?.focus();
+			cell.blur();
+		} else if (k === "Enter") {
+			cell.blur();
+		} else if (k === "Escape") {
+			cell.blur();
+			return;
+		} else if (HEX_CHARACTERS.includes(k.toUpperCase())) {
+			if (this.current_cell_info.left === undefined) {
+				this.current_cell_info.left = k.toUpperCase();
+				cell.innerHTML = `${this.current_cell_info.left}0`;
+			} else if (this.current_cell_info.right === undefined) {
+				this.current_cell_info.right = k.toUpperCase();
+				cell.textContent = `${this.current_cell_info.left}${this.current_cell_info.right}`;
 				next?.focus();
 				cell.blur();
 			}
-		});
+		} else if (k === "Tab") {
+			return;
+		}
+		e.preventDefault();
 	}
 }
diff --git a/src/ui/memoryView.ts b/src/ui/memoryView.ts
index e08dc71..9db61dd 100644
--- a/src/ui/memoryView.ts
+++ b/src/ui/memoryView.ts
@@ -1,8 +1,9 @@
-import { CpuEvent, CpuEventHandler, UiEventHandler } from "../events";
+import { CpuEvent, CpuEventHandler, UiEvent, UiEventHandler } from "../events";
 import { ParamType } from "../instructionSet";
 import { u8 } from "../num.js";
 import { UiComponent } from "./uiComponent";
 import { CelledViewer } from "./celledViewer";
+import { EditorContext } from "./editableHex";
 
 type MemoryCell = {
 	el: HTMLDivElement;
@@ -16,6 +17,23 @@ export class MemoryView extends CelledViewer implements UiComponent {
 		super(16, 16, element);
 		this.program_counter = 0;
 		this.events = e;
+
+		const list = this.cells.map((c) => c.el);
+		const editor = new EditorContext(list, this.width, this.height, (i, value) => {
+			this.events.dispatch(UiEvent.RequestMemoryChange, { address: i as u8, value });
+		});
+		this.events.listen(UiEvent.EditOn, () => {
+			editor.enable();
+			for (const cell of this.cells) {
+				cell.el.className = "";
+			}
+		});
+		this.events.listen(UiEvent.EditOff, () => {
+			editor.disable();
+			for (const cell of this.cells) {
+				cell.el.className = "";
+			}
+		});
 	}
 
 	set_program_counter(position: u8): void {
diff --git a/src/ui/pausePlay.ts b/src/ui/pausePlay.ts
new file mode 100644
index 0000000..7973a25
--- /dev/null
+++ b/src/ui/pausePlay.ts
@@ -0,0 +1,104 @@
+import { el } from "../etc";
+import { UiEventHandler, CpuEventHandler, UiEvent } from "../events";
+import { UiComponent } from "./uiComponent";
+
+const MAX_SLIDER = 1000;
+
+export class pausePlay implements UiComponent {
+	element: HTMLElement;
+	start_button: HTMLButtonElement;
+	step_button: HTMLButtonElement;
+	range: HTMLInputElement;
+	events: UiEventHandler;
+	on: boolean = false;
+	cycle_delay: number;
+	constructor(element: HTMLElement, events: UiEventHandler) {
+		this.element = element;
+		this.events = events;
+		this.start_button = el("button", "pause_play_button");
+		this.step_button = el("button", "step_button");
+		this.range = el("input", "speed_range");
+		this.range.max = MAX_SLIDER.toString();
+		this.range.min = "0";
+		this.range.type = "range";
+		this.start_button.addEventListener("click", () => this.toggle());
+		this.step_button.addEventListener("click", () => this.step());
+		this.range.addEventListener("input", (e) => {
+			const delay = MAX_SLIDER - parseInt((e.target as HTMLInputElement).value, 10) + 10;
+			this.cycle_delay = delay;
+		});
+		this.start_button.textContent = "Start";
+		this.step_button.textContent = "Step";
+		this.element.appendChild(this.start_button);
+		this.element.appendChild(this.step_button);
+		this.element.appendChild(this.range);
+		this.cycle_delay = 1000;
+		this.range.value = "0";
+
+		this.events.listen(UiEvent.EditOn, () => {
+			this.disable();
+		});
+		this.events.listen(UiEvent.EditOff, () => {
+			this.enable();
+		});
+	}
+
+	disable(): void {
+		this.stop();
+		this.start_button.setAttribute("disabled", "true");
+		this.step_button.setAttribute("disabled", "true");
+		this.range.setAttribute("disabled", "true");
+	}
+
+	enable(): void {
+		this.start_button.removeAttribute("disabled");
+		this.step_button.removeAttribute("disabled");
+		this.range.removeAttribute("disabled");
+	}
+
+	toggle(): void {
+		if (this.on) {
+			this.start_button.textContent = "Start";
+			this.on = false;
+		} else {
+			this.on = true;
+			this.cycle();
+			this.start_button.textContent = "Storp";
+		}
+	}
+
+	private cycle(): void {
+		const loop = (): void => {
+			if (this.on === false) {
+				return;
+			}
+			this.events.dispatch(UiEvent.RequestCpuCycle, 1);
+			setTimeout(loop, this.cycle_delay);
+		};
+		loop();
+	}
+	private step(): void {
+		if (this.on) {
+			this.stop();
+		} else {
+			this.events.dispatch(UiEvent.RequestCpuCycle, 1);
+		}
+	}
+
+	start(): void {
+		if (!this.on) {
+			this.toggle();
+		}
+	}
+
+	stop(): void {
+		if (this.on) {
+			this.toggle();
+		}
+	}
+
+	reset(): void {
+		this.stop();
+		this.enable();
+	}
+}
diff --git a/src/ui/registerView.ts b/src/ui/registerView.ts
index 8bd5fa3..024079b 100644
--- a/src/ui/registerView.ts
+++ b/src/ui/registerView.ts
@@ -1,5 +1,7 @@
-import { CpuEvent, CpuEventHandler, UiEventHandler } from "../events";
+import { CpuEvent, CpuEventHandler, UiEvent, UiEventHandler } from "../events";
+import { u3 } from "../num";
 import { CelledViewer } from "./celledViewer";
+import { EditorContext } from "./editableHex";
 import { UiComponent } from "./uiComponent";
 
 export class RegisterView extends CelledViewer implements UiComponent {
@@ -7,6 +9,23 @@ export class RegisterView extends CelledViewer implements UiComponent {
 	constructor(element: HTMLElement, e: UiEventHandler) {
 		super(8, 1, element);
 		this.events = e;
+
+		const list = this.cells.map((c) => c.el);
+		const editor = new EditorContext(list, this.width, this.height, (i, value) => {
+			this.events.dispatch(UiEvent.RequestRegisterChange, { register_no: i as u3, value });
+		});
+		this.events.listen(UiEvent.EditOn, () => {
+			editor.enable();
+			for (const cell of this.cells) {
+				cell.el.className = "";
+			}
+		});
+		this.events.listen(UiEvent.EditOff, () => {
+			editor.disable();
+			for (const cell of this.cells) {
+				cell.el.className = "";
+			}
+		});
 	}
 
 	init_cpu_events(c: CpuEventHandler): void {
diff --git a/src/ui/ribbon.ts b/src/ui/ribbon.ts
new file mode 100644
index 0000000..9c04d76
--- /dev/null
+++ b/src/ui/ribbon.ts
@@ -0,0 +1,60 @@
+import { el, $ } from "../etc";
+import { UiEventHandler, UiEvent } from "../events";
+import { UiComponent } from "./uiComponent";
+
+function new_button(name: string, img_path: string, additional_class?: string): HTMLButtonElement {
+	const button = el("button", "", "no_style ribbon_button");
+	const image = el("img");
+	image.src = img_path;
+	image.width = 64;
+	image.height = 64;
+	if (additional_class !== undefined) {
+		button.classList.add(additional_class);
+	}
+	button.appendChild(image);
+	return button;
+}
+
+export class Ribbon implements UiComponent {
+	element: HTMLElement;
+	events: UiEventHandler;
+	edit_button: HTMLButtonElement;
+	console_button: HTMLButtonElement;
+	display_button: HTMLButtonElement;
+	explainer_button: HTMLButtonElement;
+	constructor(element: HTMLElement, event: UiEventHandler) {
+		this.element = element;
+		this.events = event;
+
+		this.edit_button = new_button("Edit", "pencil.png", "editor_toggle");
+		this.console_button = new_button("Console", "texout.png");
+		this.display_button = new_button("Video", "tv.png");
+		this.explainer_button = new_button("Explainer", "explainer.png");
+		this.edit_button.addEventListener("click", () => this.edit_toggle());
+		this.element.appendChild(this.edit_button);
+		this.element.appendChild(this.console_button);
+		this.element.appendChild(this.display_button);
+		this.element.appendChild(this.explainer_button);
+	}
+	reset(): void {
+		const is_on = this.edit_button.classList.contains("on");
+		if (is_on) {
+			this.edit_toggle();
+		}
+	}
+
+	edit_toggle(): void {
+		const is_on = this.edit_button.classList.contains("on");
+		if (is_on) {
+			this.edit_button.classList.remove("on");
+			$("main").classList.remove("editor");
+			this.edit_button.classList.add("off");
+			this.events.dispatch(UiEvent.EditOff);
+		} else {
+			this.events.dispatch(UiEvent.EditOn);
+			$("main").classList.add("editor");
+			this.edit_button.classList.add("on");
+			this.edit_button.classList.remove("off");
+		}
+	}
+}
diff --git a/src/ui/uiComponent.ts b/src/ui/uiComponent.ts
index 841bb91..9b2a16a 100644
--- a/src/ui/uiComponent.ts
+++ b/src/ui/uiComponent.ts
@@ -7,11 +7,11 @@ import { CpuEventHandler, UiEventHandler } from "../events";
 
 export interface UiComponent {
 	element: HTMLElement;
-	/** Allows listening and emitting UiEvent's*/
+	/** Allows listening and emitting UiEvents*/
 	events: UiEventHandler;
 	reset: () => void;
-	/**  Allows listening CPUEvent's*/
-	init_cpu_events: (c: CpuEventHandler) => void;
+	/**  Allows listening CPUEvents*/
+	init_cpu_events?: (c: CpuEventHandler) => void;
 }
 
 export interface UiComponentConstructor {
diff --git a/todo.md b/todo.md
new file mode 100644
index 0000000..75ce1d9
--- /dev/null
+++ b/todo.md
@@ -0,0 +1,36 @@
+Edit Mode
+
+- Select where program counter is
+
+Implement HCF
+Move start/stop/auto logic into computer
+
+Speed control slider behavior
+Speed control slider styling
+Overclock Box
+
+error in instruction when number out of range (fix new Error("todo"))
+
+UI for screen (toggling (click an icon?))
+UI for togging other UI elements
+
+UI for showing which Memory bank is selected
+VRAM select instruction
+
+Improve instruction explainer. Clearly show what is an instruction and what is a parameter
+
+Verify mod256 behavior on negatives
+
+UI showing CPU flag(s) (Carry)
+
+Error log
+
+Responsive layout
+
+standardize names of all things
+
+Documentation with standard names
+
+Example Programs
+
+Ui for togging your mother.