diff --git a/build b/build
index 53c3695..23f8280 100755
--- a/build
+++ b/build
@@ -8,7 +8,7 @@ readonly LILY_TEST_DIR="${LILY_TEST_DIR:-$LILY_SCRIPT_DIR/hbc-tests}"
 readonly LILY_BUILD_DIR="${LILY_BUILD_DIR:-$LILY_SCRIPT_DIR/out}"
 
 readonly HBLANG_GIT="https://git.ablecorp.us/mlokis/hblang"
-readonly HBLANG_COMMIT="4bd1d3a4e1040b36f9185cf3ebedb99b1ad17ac6"
+readonly HBLANG_COMMIT="dd46d8a2d505ea8611f6c5b44c08131bb3abf220"
 readonly HBC_FLAGS="--path-projection lily $LILY_SRC_DIR/lib.hb ${HBC_FLAGS:-}"
 readonly HBC_BINARY="hbc"
 readonly HBC_TESTS="${HBC_TESTS:-0}"
@@ -83,8 +83,8 @@ main() {
         result_file=$(mktemp)
         find "$LILY_TEST_DIR" -type f >"$tmpfile"
         while IFS= read -r file; do
-            # test_name=$(basename "$file" .hb)
-            test_name=$(echo "$(basename -- "$(dirname -- "$file")")/$(basename -- "$file" .hb)" | tr '/' '.')
+            test_name=$(realpath -s --relative-to="$LILY_TEST_DIR" "$file" | tr '/' '.')
+            test_name="${test_name%.*}"
             do_test 2>/dev/null &
         done <"$tmpfile"
         rm -f "$tmpfile"
diff --git a/docs/spec.md b/docs/spec.md
index 1a49d4e..85fc12b 100644
--- a/docs/spec.md
+++ b/docs/spec.md
@@ -7,3 +7,4 @@ a collection of guidelines for programmers to use to create lily-compatible impl
 the following files define the spec:
 - [allocators](./spec/alloc.md)
 - [iterators](./spec/iter.md)
+- [tests](./spec/tests.md)
diff --git a/docs/spec/tests.md b/docs/spec/tests.md
new file mode 100644
index 0000000..e024d69
--- /dev/null
+++ b/docs/spec/tests.md
@@ -0,0 +1,22 @@
+# tests
+
+1. tests should attempt to be meaningful in some way, patching targeting bugs, or behaviours.
+
+2. tests should be written as hblang vendored tests. all tests can be run with `HBC_TESTS=1 ./build`. status of failed tests should be emitted to `out/subdir.test-name.test`
+
+3. tests should be written as one of:
+    - part of an exhaustive folder covering all aspects of a datastructure / module. e.g:
+        ```
+        lily/
+            some-module/
+                func1.hb
+                struct/
+                    method1.hb
+                    method2.hb
+            other-module/
+                ...
+            ...
+        lang/
+            ...
+        ```
+    - one-and-done tests for locating bugs (which should be kept after the bug is patched, unless it is made obsolete by an exhaustive test)
diff --git a/hbc-tests/lang/inline-declaration-order.hb b/hbc-tests/lang/inline-declaration-order.hb
new file mode 100644
index 0000000..89c6e72
--- /dev/null
+++ b/hbc-tests/lang/inline-declaration-order.hb
@@ -0,0 +1,17 @@
+expectations := .{
+	return_value: 0,
+}
+
+secondary := fn(): uint {
+    return inlined()
+}
+
+// ! if $ is removed this works.
+// ! if this function is moved above `secondary`, this works.
+$inlined := fn(): uint {
+    return 0
+}
+
+main := fn(): uint {
+    return secondary()
+}
\ No newline at end of file
diff --git a/hbc-tests/lily/type-kindof.hb b/hbc-tests/lily/type-kindof.hb
new file mode 100644
index 0000000..5152717
--- /dev/null
+++ b/hbc-tests/lily/type-kindof.hb
@@ -0,0 +1,14 @@
+expectations := .{
+    return_value: 0,
+}
+
+lily.{Type} := @use("../../src/lib.hb")
+
+main := fn(): uint {
+    // ! (compiler) bug: "the functions types most likely depend on it being evaluated"
+    $match Type(uint).kind() {
+        .Builtin => {},
+        _ => return 1,
+    }
+    return 0
+}
\ No newline at end of file
diff --git a/hbc-tests/lily/type-of.hb b/hbc-tests/lily/type-of.hb
new file mode 100644
index 0000000..70ecc2b
--- /dev/null
+++ b/hbc-tests/lily/type-of.hb
@@ -0,0 +1,14 @@
+expectations := .{
+    return_value: 0,
+}
+
+lily.{TypeOf} := @use("../../src/lib.hb")
+
+main := fn(): uint {
+    // ! (compiler) bug: "the functions types most likely depend on it being evaluated"
+    $match TypeOf(@as(uint, 1)).kind() {
+        .Builtin => {},
+        _ => return 1,
+    }
+    return 0
+}
\ No newline at end of file
diff --git a/src/ipc.hb b/src/ipc.hb
new file mode 100644
index 0000000..57a81e0
--- /dev/null
+++ b/src/ipc.hb
@@ -0,0 +1,86 @@
+lily.{target, mem, Type} := @use("lib.hb")
+
+Buffer := struct {
+	.id: uint
+
+	Self := @CurrentScope()
+
+	new := fn(name: ?[]u8): ?Self {
+		id: uint = 0
+		if name == null {
+			id = target.buf_create()
+		} else {
+			id = target.buf_create_named(name.?)
+		}
+		if id == 0 return null
+		return Self.from_raw(id)
+	}
+	search := fn(name: []u8): ?Self {
+		id := target.buf_search(name)
+		if id == 0 return null
+		return Self.from_raw(id)
+	}
+	deinit := fn(self: ^Self): void {
+		target.buf_destroy(self.id)
+		self.* = idk
+	}
+	$from_raw := fn(id: uint): Self {
+		return .(id)
+	}
+	$await := fn(self: ^Self): void {
+		target.buf_await(self.id)
+	}
+	$write := fn(self: ^Self, val: @Any()): void {
+		$match Type(@TypeOf(val)).kind() {
+			.Pointer => target.buf_write(self.id, mem.as_bytes(val)),
+			.Slice => target.buf_write(self.id, mem.as_bytes(val)),
+			_ => target.buf_write(self.id, mem.as_bytes(&val)),
+		}
+	}
+	$read_into := fn(self: ^Self, slice: []u8): void {
+		target.buf_read(self.id, slice)
+	}
+	$read := fn(self: ^Self, $T: type): T {
+		buf: T = idk
+		target.buf_read(self.id, mem.as_bytes(&buf))
+		return buf
+	}
+}
+
+Channel := struct {
+	.local: Buffer;
+	.remote: Buffer
+
+	Self := @CurrentScope()
+
+	new := fn(local_name: ?[]u8, remote_name: ?[]u8): ?Self {
+		local := Buffer.new(local_name)
+		if local == null return null
+		remote := Buffer.new(remote_name)
+		if remote == null return null
+		return .(local.?, remote.?)
+	}
+	$deinit := fn(self: ^Self): void {
+		self.local.deinit()
+		self.remote.deinit()
+		self.* = idk
+	}
+	$to_remote := fn(self: Self): Self {
+		return .(self.remote, self.local)
+	}
+	$from_raw := fn(local_id: uint, remote_id: uint): Self {
+		return .(Buffer.from_raw(local_id), Buffer.from_raw(remote_id))
+	}
+	$await := fn(self: ^Self): void {
+		self.local.await()
+	}
+	$write := fn(self: ^Self, val: @Any()): void {
+		self.remote.write(val)
+	}
+	$read_into := fn(self: ^Self, slice: []u8): void {
+		self.local.read_into(slice)
+	}
+	$read := fn(self: ^Self, $T: type): T {
+		return self.local.read(T)
+	}
+}
diff --git a/src/lib.hb b/src/lib.hb
index 9d2249a..042c828 100644
--- a/src/lib.hb
+++ b/src/lib.hb
@@ -1,7 +1,9 @@
 .{Type, TypeOf} := @use("type.hb")
+process := @use("process.hb")
 target := @use("target/lib.hb")
 alloc := @use("alloc/lib.hb")
 iter := @use("iter.hb")
+ipc := @use("ipc.hb")
 mem := @use("mem.hb")
 log := @use("log.hb")
 fmt := @use("fmt.hb")
diff --git a/src/mem.hb b/src/mem.hb
index 54834a5..c4d20ca 100644
--- a/src/mem.hb
+++ b/src/mem.hb
@@ -34,6 +34,17 @@ $as_bytes := fn(v: @Any()): []u8 {
 	}
 }
 
+$to_owned := fn($T: type, slice: []u8): T {
+	$match Type(T).kind() {
+		.Array => {
+			ret: T = idk
+			copy(ret[..], slice)
+			return ret
+		},
+		_ => @error("todo: write this error"),
+	}
+}
+
 $overlaps := fn(lhs: []u8, rhs: []u8): bool {
 	return lhs.ptr < rhs.ptr + rhs.len & rhs.ptr < lhs.ptr + lhs.len
 }
diff --git a/src/process.hb b/src/process.hb
new file mode 100644
index 0000000..012e9d4
--- /dev/null
+++ b/src/process.hb
@@ -0,0 +1,24 @@
+lily.{target} := @use("lib.hb")
+
+ProcessID := fn(): type {
+	$match target.current() {
+		.AbleOS => return struct {
+			.host_id: uint;
+			.id: uint;
+		},
+	}
+}
+
+$HOST_ID_PLACEHOLDER := 0
+
+$spawn := fn(executable: []u8): ProcessID() {
+	raw := target.proc_spawn(executable)
+	// todo: this
+	return .(HOST_ID_PLACEHOLDER, raw)
+}
+
+$fork := fn(): ProcessID() {
+	raw := target.proc_fork()
+	// todo: this
+	return .(HOST_ID_PLACEHOLDER, raw)
+}
diff --git a/src/target/ableos.hb b/src/target/ableos.hb
index 5f51412..a95923c 100644
--- a/src/target/ableos.hb
+++ b/src/target/ableos.hb
@@ -45,9 +45,37 @@ $memfill := fn(dest: ^u8, src: ^u8, count: uint, len: uint): void {
 
 $exit := fn(code: u8): void {
 }
-$fill_rand := fn(dest: ^u8, len: uint): void return @ecall(3, 4, dest, len)
-$fork := fn(): uint return @ecall(3, 7)
+
+$rand_fill := fn(dest: ^u8, len: uint): void return @ecall(3, 4, dest, len)
+
+$proc_fork := fn(): uint return @ecall(3, 7)
+$proc_spawn := fn(executable: []u8): uint {
+	return @ecall(3, 6, executable.ptr, executable.len)
+}
 
 $dt_get := fn($T: type, query: []u8): T {
 	return @ecall(3, 5, query.ptr, query.len)
 }
+
+BufferEcall := struct align(1){.operation: u8; .str_ptr: ^u8; .str_len: uint}
+$buf_create_named := fn(name: []u8): uint {
+	return @ecall(3, 0, BufferEcall.(0, name.ptr, name.len), @size_of(BufferEcall))
+}
+$buf_create := fn(): uint {
+	return @ecall(1, 0)
+}
+$buf_destroy := fn(id: uint): void {
+	return @ecall(2, id)
+}
+$buf_search := fn(name: []u8): uint {
+	return @ecall(3, 0, BufferEcall.(3, name.ptr, name.len), @size_of(BufferEcall))
+}
+$buf_await := fn(id: uint): void {
+	return @ecall(7, id)
+}
+$buf_read := fn(id: uint, mmap: []u8): void {
+	return @ecall(4, id, mmap.ptr, mmap.len)
+}
+$buf_write := fn(id: uint, mmap: []u8): void {
+	return @ecall(3, id, mmap.ptr, mmap.len)
+}
diff --git a/src/target/lib.hb b/src/target/lib.hb
index e707f21..823d6a7 100644
--- a/src/target/lib.hb
+++ b/src/target/lib.hb
@@ -11,8 +11,16 @@ lib.{
 	memset,
 	memfill,
 	exit,
-	fill_rand,
-	fork,
+	rand_fill,
+	proc_fork,
+	proc_spawn,
+	buf_create_named,
+	buf_create,
+	buf_destroy,
+	buf_search,
+	buf_await,
+	buf_read,
+	buf_write,
 } := Lib(current())
 
 Target := enum {