Parcourir la source

Initial open-sourcing

GitHub Actions Bot il y a 9 mois
Parent
commit
0399db879c
100 fichiers modifiés avec 29951 ajouts et 1 suppressions
  1. 201 0
      Cargo.toml
  2. 68 0
      Makefile
  3. 36 1
      README.md
  4. 8 0
      crates/equix-latency/Cargo.toml
  5. 30 0
      crates/equix-latency/src/main.rs
  6. 14 0
      crates/kernels/Cargo.toml
  7. 8 0
      crates/kernels/src/dumb.rs
  8. 5 0
      crates/kernels/src/lib.rs
  9. 6 0
      crates/kernels/src/wallet.rs
  10. 72 0
      crates/nockapp/README.md
  11. 21 0
      crates/nockapp/apps/choo/Cargo.toml
  12. 69 0
      crates/nockapp/apps/choo/README.md
  13. 7 0
      crates/nockapp/apps/choo/bootstrap.rs
  14. BIN
      crates/nockapp/apps/choo/bootstrap/choo.jam
  15. 867 0
      crates/nockapp/apps/choo/bootstrap/kernel.hoon
  16. 380 0
      crates/nockapp/apps/choo/src/lib.rs
  17. 38 0
      crates/nockapp/apps/choo/src/main.rs
  18. 54 0
      crates/nockapp/apps/choo/tests/build.rs
  19. 33 0
      crates/nockapp/apps/hoon-deps/lib/builder.hoon
  20. 96 0
      crates/nockapp/apps/hoon-deps/lib/wrapper.hoon
  21. 14075 0
      crates/nockapp/apps/hoon/hoon-138.hoon
  22. 16 0
      crates/nockapp/apps/http-app/Cargo.toml
  23. BIN
      crates/nockapp/apps/http-app/bootstrap/http.jam
  24. 98 0
      crates/nockapp/apps/http-app/bootstrap/kernel.hoon
  25. 26 0
      crates/nockapp/apps/http-app/main.rs
  26. 16 0
      crates/nockapp/apps/test-app/Cargo.toml
  27. 71 0
      crates/nockapp/apps/test-app/bootstrap/kernel.hoon
  28. BIN
      crates/nockapp/apps/test-app/bootstrap/test-ker.jam
  29. 47 0
      crates/nockapp/apps/test-app/main.rs
  30. 55 0
      crates/nockapp/crown/Cargo.toml
  31. 58 0
      crates/nockapp/crown/src/drivers/exit.rs
  32. 177 0
      crates/nockapp/crown/src/drivers/file.rs
  33. 365 0
      crates/nockapp/crown/src/drivers/http.rs
  34. 39 0
      crates/nockapp/crown/src/drivers/markdown.rs
  35. 15 0
      crates/nockapp/crown/src/drivers/mod.rs
  36. 668 0
      crates/nockapp/crown/src/drivers/npc.rs
  37. 157 0
      crates/nockapp/crown/src/drivers/one_punch.rs
  38. 29 0
      crates/nockapp/crown/src/drivers/timer.rs
  39. 301 0
      crates/nockapp/crown/src/kernel/boot.rs
  40. 254 0
      crates/nockapp/crown/src/kernel/checkpoint.rs
  41. 1200 0
      crates/nockapp/crown/src/kernel/form.rs
  42. 3 0
      crates/nockapp/crown/src/kernel/mod.rs
  43. 56 0
      crates/nockapp/crown/src/lib.rs
  44. 0 0
      crates/nockapp/crown/src/nockapp/actors/kernel.rs
  45. 2 0
      crates/nockapp/crown/src/nockapp/actors/mod.rs
  46. 102 0
      crates/nockapp/crown/src/nockapp/actors/save.rs
  47. 188 0
      crates/nockapp/crown/src/nockapp/driver.rs
  48. 67 0
      crates/nockapp/crown/src/nockapp/error.rs
  49. 11 0
      crates/nockapp/crown/src/nockapp/metrics.rs
  50. 518 0
      crates/nockapp/crown/src/nockapp/mod.rs
  51. 546 0
      crates/nockapp/crown/src/nockapp/test.rs
  52. 307 0
      crates/nockapp/crown/src/nockapp/wire.rs
  53. 280 0
      crates/nockapp/crown/src/noun/extensions.rs
  54. 5 0
      crates/nockapp/crown/src/noun/mod.rs
  55. 28 0
      crates/nockapp/crown/src/noun/ops.rs
  56. 1020 0
      crates/nockapp/crown/src/noun/slab.rs
  57. 291 0
      crates/nockapp/crown/src/observability.rs
  58. 261 0
      crates/nockapp/crown/src/utils/bytes.rs
  59. 156 0
      crates/nockapp/crown/src/utils/error.rs
  60. 162 0
      crates/nockapp/crown/src/utils/mod.rs
  61. 50 0
      crates/nockapp/crown/src/utils/scry.rs
  62. 180 0
      crates/nockapp/crown/src/utils/slogger.rs
  63. BIN
      crates/nockapp/crown/test-jams/cue-test.jam
  64. BIN
      crates/nockapp/crown/test-jams/test-ker.jam
  65. 126 0
      crates/nockapp/crown/tests/integration.rs
  66. 72 0
      crates/nockapp/nested-workspace.toml
  67. 4 0
      crates/nockapp/rustfmt.toml
  68. 21 0
      crates/nockchain-bitcoin-sync/Cargo.toml
  69. 406 0
      crates/nockchain-bitcoin-sync/src/lib.rs
  70. 23 0
      crates/nockchain-bitcoin-sync/src/main.rs
  71. 37 0
      crates/nockchain-libp2p-io/Cargo.toml
  72. 5 0
      crates/nockchain-libp2p-io/src/lib.rs
  73. 25 0
      crates/nockchain-libp2p-io/src/metrics.rs
  74. 1995 0
      crates/nockchain-libp2p-io/src/nc.rs
  75. 266 0
      crates/nockchain-libp2p-io/src/p2p.rs
  76. 589 0
      crates/nockchain-libp2p-io/src/p2p_util.rs
  77. 120 0
      crates/nockchain-libp2p-io/src/tip5_util.rs
  78. 49 0
      crates/nockchain/Cargo.toml
  79. 14 0
      crates/nockchain/build.rs
  80. 231 0
      crates/nockchain/src/colors.rs
  81. 635 0
      crates/nockchain/src/lib.rs
  82. 19 0
      crates/nockchain/src/main.rs
  83. 71 0
      crates/sword/DEVELOPERS.md
  84. 21 0
      crates/sword/LICENSE
  85. 7 0
      crates/sword/README.md
  86. 46 0
      crates/sword/docs/b-trees.md
  87. 76 0
      crates/sword/docs/codegen-bootstrap.md
  88. 62 0
      crates/sword/docs/heap.md
  89. 191 0
      crates/sword/docs/llvm.md
  90. 13 0
      crates/sword/docs/moving-memory.md
  91. 390 0
      crates/sword/docs/noun-rep.svg
  92. 103 0
      crates/sword/docs/persistence.md
  93. 22 0
      crates/sword/docs/pills.md
  94. 54 0
      crates/sword/docs/priors.bib
  95. 45 0
      crates/sword/docs/proposal/hypotheses.md
  96. 60 0
      crates/sword/docs/proposal/milestones.md
  97. 37 0
      crates/sword/docs/proposal/nock.txt
  98. 21 0
      crates/sword/docs/proposal/notes-~2021.9.23.md
  99. 23 0
      crates/sword/docs/proposal/notes-~2021.9.24.md
  100. 159 0
      crates/sword/docs/proposal/noun-representation.md

+ 201 - 0
Cargo.toml

@@ -0,0 +1,201 @@
+[workspace]
+members = [
+    "crates/equix-latency",
+    "crates/kernels",
+    "crates/nockapp/apps/choo",
+    "crates/nockapp/crown",
+    "crates/nockchain-bitcoin-sync",
+    "crates/nockchain-libp2p-io",
+    "crates/nockchain",
+    "crates/sword/rust/assert_no_alloc",
+    "crates/sword/rust/ibig",
+    "crates/sword/rust/murmur3",
+    "crates/sword/rust/sword_macros",
+    "crates/sword/rust/sword",
+    "crates/zkvm-jetpack",
+]
+
+resolver = "2"
+
+[workspace.package]
+version = "0.1.0"
+edition = "2021"
+
+[workspace.dependencies.assert_no_alloc]
+path = "crates/sword/rust/assert_no_alloc"
+
+[workspace.dependencies.choo]
+path = "crates/nockapp/apps/choo"
+
+[workspace.dependencies.crown]
+path = "crates/nockapp/crown"
+
+[workspace.dependencies.ibig]
+path = "crates/sword/rust/ibig"
+
+[workspace.dependencies.kernels]
+path = "crates/kernels"
+
+[workspace.dependencies.murmur3]
+path = "crates/sword/rust/murmur3"
+
+[workspace.dependencies.nockchain]
+path = "crates/nockchain"
+
+[workspace.dependencies.nockchain-bitcoin-sync]
+path = "crates/nockchain-bitcoin-sync"
+
+[workspace.dependencies.nockchain-libp2p-io]
+path = "crates/nockchain-libp2p-io"
+
+[workspace.dependencies.sword]
+path = "crates/sword/rust/sword"
+
+[workspace.dependencies.sword_crypto]
+path = "crates/sword/rust/sword_crypto"
+
+[workspace.dependencies.sword_macros]
+path = "crates/sword/rust/sword_macros"
+
+[workspace.dependencies.zkvm-jetpack]
+path = "crates/zkvm-jetpack"
+
+[workspace.dependencies]
+json = "0.12.4"
+lazy_static = "1.4.0"
+libc = "0.2.171"
+memmap2 = "^0.9.5"
+num-derive = "0.4.2"
+signal-hook = "0.3"
+static_assertions = "1.1.0"
+
+## Sword crypto dependencies
+# ed25519
+curve25519-dalek = { version = "4.1.1", default-features = false }
+ed25519-dalek = { version = "2.1.0", default-features = false }
+x25519-dalek = { version = "2.0.0", features = [
+    "static_secrets",
+], default-features = false }
+
+# aes_siv
+aes = { version = "0.8.3", default-features = false }
+aes-siv = { version = "0.7.0", default-features = false }
+
+# sha
+sha1 = { version = "0.10.6", default-features = false }
+sha2 = { version = "0.10.8", default-features = false }
+
+# nockapp-specific workspace dependencies
+anyhow = "1.0"
+async-trait = "0.1"
+axum = "0.8.1"
+bincode = "2.0.0-rc.3"
+blake3 = { version = "1.5.1", features = ["serde"] }
+bs58 = "0.5.1"
+byteorder = "1.5.0"
+crossterm = "0.28.1"
+chrono = "0.4.40"
+intmap = "3.1.0"
+opentelemetry = { version = "0.27.1", features = [
+    "trace",
+    "logs",
+    "metrics",
+    "internal-logs",
+] }
+opentelemetry-otlp = { version = "0.27.0", features = [
+    "tonic",
+    "http-proto",
+    "reqwest-client",
+] }
+opentelemetry_sdk = { version = "0.27.1", features = ["rt-tokio"] }
+serde_bytes = { version = "0.11.15", features = ["alloc"] }
+tempfile = "3.3"
+termimad = "0.31.0"
+tonic = "0.12.3"
+tracing-opentelemetry = { version = "0.28.0", features = ["metrics"] }
+tracing-test = "0.2.5"
+yaque = "0.6.6"
+
+
+# External dependencies
+arrayref = "0.3.7"
+argon2 = "0.5.3"
+bardecoder = "0.5.0"
+bitcoincore-rpc = "0.19.0"
+bitvec = "1.0.1"
+bytes = "1.5.0"
+cfg-if = "1.0.0"
+clap = "4.4.4"
+num-traits = "0.2"
+pin-project-lite = "0.2.16"
+criterion = { git = "https://github.com/vlovich/criterion.rs.git", rev = "9b485aece85a3546126b06cc25d33e14aba829b3", features = [
+    "html_reports",
+] }
+dirs = "6.0.0"
+either = "1.9.0"
+equix = "0.2.2"
+futures = "0.3.31"
+getrandom = { version = "0.2.15", features = ["std"] }
+gnort = "0.1.1"
+hex-literal = "1.0.0"
+hickory-resolver = { version = "0.25.0-alpha.4", features = ["system-config"] }
+hickory-proto = "0.25.0-alpha.4"
+image = "0.24.7"
+libp2p = "0.55.0"
+qrcode = "0.13"
+quickcheck = "1.0.3"
+quickcheck_macros = "1.0"
+rand = "0.8.5"
+ratatui = "0.29.0"
+rayon = "1.8.0"
+reqwest = { version = "0.12", default-features = false, features = [
+    "rustls-tls",
+    "http2",
+    "charset",
+] }
+serde = "1.0.217"
+serde_json = "1.0.104"
+sha3 = "0.10.8"
+slotmap = "1.0.7"
+smallvec = "1.13.2"
+termcolor = "1.4"
+testcontainers = { git = "https://github.com/bitemyapp/testcontainers-rs.git", rev = "54851fd9faf9b9cded9d681b46f87c056880d870" }
+
+thiserror = "2.0.11"
+tokio = { version = "1.32", features = [
+    "fs",
+    "io-util",
+    "macros",
+    "net",
+    "rt-multi-thread",
+    "rt",
+    "signal",
+] }
+tokio-util = "0.7.11"
+tracing = "0.1.41"
+tracing-subscriber = { version = "0.3.18", features = [
+    "ansi",
+    "env-filter",
+    "registry",
+] }
+vergen = "8.3.2"
+void = "1.0.2"
+num_cpus = "1.16.0"
+
+[profile.dev]
+opt-level = 0
+debug = 2
+
+[profile.test]
+inherits = "release"
+
+[profile.release]
+opt-level = 3
+lto = "thin"
+codegen-units = 1
+debug = 1
+
+[profile.release.package."*"]
+opt-level = 3
+codegen-units = 1
+debug = 1

+ 68 - 0
Makefile

@@ -0,0 +1,68 @@
+# Set env variables
+export RUST_BACKTRACE := full
+export RUST_LOG := info,nockchain=debug,nockchain_libp2p_io=info,libp2p=info,libp2p_quic=info
+export MINIMAL_LOG_FORMAT := true
+
+
+## Build everything
+.PHONY: build
+build:
+	cargo build --release
+
+## Run all tests
+.PHONY: test
+test:
+	cargo test --release
+
+.PHONY: install-choo
+install-choo: nuke-choo-data ## Install choo from this repo
+	$(call show_env_vars)
+	cargo install --locked --force --path crates/nockapp/apps/choo --bin choo
+
+.PHONY: ensure-dirs
+ensure-dirs:
+	mkdir -p hoon
+	mkdir -p assets
+
+.PHONY: build-trivial-new
+build-trivial-new: ensure-dirs
+	$(call show_env_vars)
+	echo '%trivial' > hoon/trivial.hoon
+	choo --new --arbitrary hoon/trivial.hoon
+
+HOON_TARGETS=assets/dumb.jam assets/wal.jam
+
+.PHONY: nuke-choo-data
+nuke-choo-data:
+	rm -rf .data.choo
+	rm -rf ~/.nockapp/choo
+
+.PHONY: nuke-assets
+nuke-assets:
+	rm -f assets/*.jam
+
+.PHONY: build-hoon-fresh
+build-hoon-fresh: nuke-assets nuke-choo-data install-choo ensure-dirs build-trivial-new $(HOON_TARGETS)
+	$(call show_env_vars)
+
+.PHONY: build-hoon-new
+build-hoon-all: ensure-dirs build-trivial-new $(HOON_TARGETS)
+	$(call show_env_vars)
+
+.PHONY: build-hoon
+build-hoon: ensure-dirs $(HOON_TARGETS)
+	$(call show_env_vars)
+
+HOON_SRCS := $(find hoon -type file -name '*.hoon')
+
+## Build dumb.jam with choo
+assets/dumb.jam: hoon/apps/dumbnet/outer.hoon $(HOON_SRCS)
+	$(call show_env_vars)
+	RUST_LOG=trace choo hoon/apps/dumbnet/outer.hoon hoon
+	mv out.jam assets/dumb.jam
+
+## Build wal.jam with choo
+assets/wal.jam: hoon/apps/wallet/wallet.hoon $(HOON_SRCS)
+	$(call show_env_vars)
+	RUST_LOG=trace choo hoon/apps/wallet/wallet.hoon hoon
+	mv out.jam assets/wal.jam

+ 36 - 1
README.md

@@ -1 +1,36 @@
-Staging repository
+# Nockchain
+
+**Nockchain is a lightweight blockchain for heavyweight verifiable applications.**
+
+
+We believe the future of blockchains is lightweight trustless settlement of heavyweight verifiable computation. The only way to get there is by replacing verifiability-via-public-replication with verifiability-via-private-proving. Proving happens off-chain; verification is on-chain.
+
+
+## Setup
+
+Install `rustup` by following their instructions at: [https://rustup.rs/](https://rustup.rs/)
+
+Install `choo`, the Hoon compiler:
+
+```
+make install-choo
+```
+
+
+## Build
+
+To build Nockchain:
+
+```
+make hoon-all
+make build
+```
+
+To run the test suite:
+
+```
+make test
+```
+
+
+

+ 8 - 0
crates/equix-latency/Cargo.toml

@@ -0,0 +1,8 @@
+[package]
+name = "equix-latency"
+version.workspace = true
+edition.workspace = true
+
+[dependencies]
+equix.workspace = true
+rand.workspace = true

+ 30 - 0
crates/equix-latency/src/main.rs

@@ -0,0 +1,30 @@
+use std::time::Instant;
+
+use rand::rngs::OsRng;
+use rand::RngCore;
+
+fn main() {
+    let mut msg = [0u8; 65536];
+    OsRng.fill_bytes(&mut msg);
+    let mut builder = equix::EquiXBuilder::new();
+    builder.runtime(equix::RuntimeOption::CompileOnly);
+
+    let start = Instant::now();
+    let sol_array_res = builder.solve(&msg[..]);
+    let dur = start.elapsed();
+    println!("solve() took {dur:?}");
+
+    for ref sol in sol_array_res.unwrap_or_else(|err| {
+        panic!(
+            "Panicked with {err:?} at {}:{} (git sha: {:?})",
+            file!(),
+            line!(),
+            option_env!("GIT_SHA")
+        )
+    }) {
+        let start_v = Instant::now();
+        let _ = builder.verify(&msg[..], sol);
+        let dur_v = start_v.elapsed();
+        println!("verify() took {dur_v:?}");
+    }
+}

+ 14 - 0
crates/kernels/Cargo.toml

@@ -0,0 +1,14 @@
+[package]
+name = "kernels"
+version.workspace = true
+edition.workspace = true
+
+[dependencies]
+
+[features]
+default = []
+bazel_build = []
+
+# Specific kernels
+dumb = []
+wallet = []

+ 8 - 0
crates/kernels/src/dumb.rs

@@ -0,0 +1,8 @@
+#[cfg(feature = "bazel_build")]
+pub static KERNEL: &[u8] = include_bytes!(env!("DUMB_JAM_PATH"));
+
+#[cfg(not(feature = "bazel_build"))]
+pub const KERNEL: &[u8] = include_bytes!(concat!(
+    env!("CARGO_MANIFEST_DIR"),
+    "/../../assets/dumb.jam"
+));

+ 5 - 0
crates/kernels/src/lib.rs

@@ -0,0 +1,5 @@
+#[cfg(feature = "wallet")]
+pub mod wallet;
+
+#[cfg(feature = "dumb")]
+pub mod dumb;

+ 6 - 0
crates/kernels/src/wallet.rs

@@ -0,0 +1,6 @@
+#[cfg(not(feature = "bazel_build"))]
+pub static KERNEL: &[u8] =
+    include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../assets/wal.jam"));
+
+#[cfg(feature = "bazel_build")]
+pub static KERNEL: &[u8] = include_bytes!(env!("WALLET_JAM_PATH"));

+ 72 - 0
crates/nockapp/README.md

@@ -0,0 +1,72 @@
+# NockApp
+
+***DEVELOPER ALPHA***
+
+<img align="right" src="https://zorp.io/img/nockapp.png" height="150px" alt="NockApp">
+
+NockApps are pure-functional state machines with automatic persistence and modular IO.
+
+The NockApp framework provides two modules, Crown and Sword:
+1. Crown provides a minimal Rust interface to a Nock kernel.
+2. [Sword](https://github.com/zorp-corp/sword) is a modern Nock runtime that achieves durable execution.
+
+<br>
+
+## Get Started
+
+To test compiling a Nock kernel using the `choo` command-line Hoon compiler, run the following commands:
+
+```
+cargo build
+cd apps/choo
+cargo run --release bootstrap/kernel.hoon ../hoon-deps
+yes | mv out.jam bootstrap/choo.jam
+cargo run --release bootstrap/kernel.hoon ../hoon-deps
+```
+
+For large builds, the rust stack might overflow. To get around this, increase the stack size by setting: `RUST_MIN_STACK=838860`.
+
+## Building NockApps
+
+The `crown` library is the primary framework for building NockApps. It provides a simple interface to a `Kernel`: a Nock core which can make state transitions with effects (via the `poke()` method) and allow inspection of its state via the `peek()` method.
+
+For compiling Hoon to Nock, we're also including a pre-release of `choo`: a NockApp for the Hoon compiler. `choo` can compile Hoon to Nock as a batch-mode command-line process, without the need to spin up an interactive Urbit ship. It is intended both for developer workflows and for CI. `choo` is also our first example NockApp. More are coming!
+
+## Logging Configuration
+
+### Basic Usage
+
+```bash
+# Run with default settings (production mode)
+cargo run
+
+# Use minimal log format
+MINIMAL_LOG_FORMAT=true cargo run
+```
+
+### TLDR
+
+Use `MINIMAL_LOG_FORMAT=true` for compact logging format
+
+### Minimal Log Format Features
+
+The minimal log format (`MINIMAL_LOG_FORMAT=true`) provides:
+- Single-letter colored log levels (T, D, I, W, E)
+- Simplified timestamps in HH:MM:SS format
+- Abbreviated module paths (e.g., 'crown::kernel::boot' becomes '[cr] kernel::boot')
+- Special handling for slogger messages (colored by log level)
+
+### Environment Variables
+
+The following environment variables can be used to configure logging:
+
+```bash
+# Set log level
+RUST_LOG="crown::kernel=trace" cargo run
+
+# Enable minimal log format
+MINIMAL_LOG_FORMAT=true cargo run
+
+# Combine environment variables
+RUST_LOG="crown::kernel=trace" MINIMAL_LOG_FORMAT=true cargo run
+```

+ 21 - 0
crates/nockapp/apps/choo/Cargo.toml

@@ -0,0 +1,21 @@
+[package]
+name = "choo"
+version = "0.1.9"
+edition.workspace = true
+
+[dependencies]
+crown = { path = "../../crown" }
+dirs = { workspace = true }
+sword = { workspace = true }
+sword_macros = { workspace = true }
+clap = { workspace = true, features = ["derive", "cargo", "color", "env"] }
+futures = { workspace = true }
+tempfile = { workspace = true }
+tokio = { workspace = true }
+tracing = { workspace = true }
+bytes = { workspace = true }
+walkdir = "2.5.0"
+
+[[bin]]
+name = "choo"
+path = "src/main.rs"

+ 69 - 0
crates/nockapp/apps/choo/README.md

@@ -0,0 +1,69 @@
+# Choo: compile hoon
+
+To self-bootstrap:
+
+```bash
+cargo run --release bootstrap/kernel.hoon ../hoon-deps
+```
+
+This will save the built kernel as `out.jam` in the current directory. This should be moved to the bootstrap directory so the `choo` binary can pick it up:
+
+```bash
+mv out.jam bootstrap/choo.jam
+```
+
+Once this is done, you should be able to run
+
+``` bash
+cargo build --release
+```
+
+and use the resulting binary in `target/release/choo` (in the `nockapp` directory) to build NockApp kernels or arbitrary hoon files as detailed in the following section.
+
+## Usage
+
+The following assumes you have the `choo` binary in your path, which can be built with `cargo build --release` and found in `target/release/choo`.
+
+For `choo`, the first argument is the entrypoint to the program, while the second argument is the root directory for source files.
+
+```bash
+choo main.hoon hoon/
+```
+
+### Building Arbitrary Hoon
+
+To build arbitrary Hoon files, use the `--arbitrary` flag:
+
+```bash
+# Create a directory for your Hoon files
+mkdir hoon
+
+# Create a simple Hoon file
+echo '%trivial' > hoon/trivial.hoon
+
+# Build the Hoon file (exclude --new if you want to use the build cache)
+choo --new --arbitrary hoon/trivial.hoon
+```
+
+## Hoon
+
+Choo supports the Hoon language as defined in `/sys/hoon`.  However, the build system does not replicate Urbit's `+ford`
+functionality exactly, as that is closely tied to the Urbit Arvo operating system.  `choo` supports the following build
+runes:
+
+- `/+` load from `/lib`
+- `/-` load from `/sur` (currently discouraged in NockApps)
+- `/=` load from specified path (required `%hoon` mark)
+- `/*` load from specified path via specified mark (presumptively `%hoon` or `%jock`)
+- `/?` version pinning (ignored)
+
+## Developer Troubleshooting
+
+If you make changes to the `poke` arm in `bootstrap/kernel.hoon` or in `hoon-deps/wrapper.hoon`, you'll need to update the `choo.jam` file by running:
+
+```bash
+cargo run --release bootstrap/kernel.hoon ../hoon-deps
+mv out.jam bootstrap/choo.jam
+```
+
+and committing the changes to `choo.jam` so that the CI can properly bootstrap the `choo` kernel.

+ 7 - 0
crates/nockapp/apps/choo/bootstrap.rs

@@ -0,0 +1,7 @@
+// Bazel-specific bootstrap file for choo
+// This is used to provide the correct paths for include_bytes! in the Bazel build
+
+#[cfg(bazel_build)]
+pub const KERNEL_JAM: &[u8] = include_bytes!("bootstrap/choo.jam");
+#[cfg(bazel_build)]
+pub const HOON_TXT: &[u8] = include_bytes!("../hoon/hoon-138.hoon");

BIN
crates/nockapp/apps/choo/bootstrap/choo.jam


+ 867 - 0
crates/nockapp/apps/choo/bootstrap/kernel.hoon

@@ -0,0 +1,867 @@
+/+  *wrapper
+=>
+|%
++$  state-0  [%0 cached-hoon=(unit (trap vase)) ~]
++$  state-1  [%1 cached-hoon=(unit (trap vase)) bc=build-cache pc=parse-cache]
++$  versioned-state
+  $%  state-0
+      state-1
+  ==
++$  choo-state  state-1
+::
+++  moat  (keep choo-state)
++$  cause
+  $%  $:  %build
+          pat=cord
+          tex=cord
+          directory=(list [cord cord])
+          arbitrary=?
+          out=cord
+      ==
+      [%file %write path=@t contents=@ success=?]
+      [%boot hoon-txt=cord]
+  ==
++$  effect
+  $%  [%file %write path=@t contents=@]
+      [%exit id=@]
+  ==
+::
+::  $entry: path of a file along with unit of its contents.
+::
+::    If unit is null, the path must exist inside of the dir map.
+::
++$  entry  [pat=path tex=(unit cord)]
+::
++$  hash  @
++$  build-cache  (map hash (trap vase))
+::
+::  $build-result: result of a build
+::
+::    either a (trap vase) or an error trace.
+::
++$  build-result  (each (trap vase) tang)
+::
+::  $taut: file import from /lib or /sur
+::
++$  taut  [face=(unit term) pax=term]
+::
+::  $pile:  preprocessed hoon file
+::
++$  pile
+  $:  sur=(list taut)  ::  /-
+      lib=(list taut)  ::  /+
+      raw=(list [face=(unit term) pax=path])  ::  /=
+      bar=(list [face=term mark=@tas =path])
+      =hoon
+  ==
+::
+::  $parse-cache: content addressed cache of preprocessed hoon files.
+::
++$  parse-cache  (map hash pile)
+--
+::
+=<
+~&  >>  %choo-choo
+%-  (moat &)
+^-  fort:moat
+|_  k=choo-state
++*  builder  +>
+::
+::  +load: upgrade from previous state
+::
+::
+++  load
+  |=  old=versioned-state
+  ^-  choo-state
+  ::
+  ::  We do not use the result of the soft because
+  ::  clamming (trap vase) overwrites the contents
+  ::  with the bunt resulting in the honc and the build
+  ::  artifacts being replaced with *(trap vase).
+  ::
+  ?~  ((soft versioned-state) old)
+    ~&  "choo: +load old state does not nest under versioned-state"
+    !!
+  ?-    -.old
+      %0
+    ~&  >>  %upgrade-0-to-1
+    :*  %1
+        cached-hoon.old
+        *build-cache
+        *parse-cache
+    ==
+  ::
+      %1
+    ~&  >>  %no-upgrade
+    old
+  ==
+::
+::  +peek: external inspect
+::
+++  peek
+  |=  =path
+  ^-  (unit (unit *))
+  ``?=(^ cached-hoon.k)
+::
+::  +poke: external apply
+::
+++  poke
+  |=  [=wire eny=@ our=@ux now=@da dat=*]
+  ^-  [(list effect) choo-state]
+  =/  cause=(unit cause)  ((soft cause) dat)
+  ?~  cause
+    ~&  >>>  "input is not a proper cause"
+    !!
+  =/  cause  u.cause
+  ?-    -.cause
+      %file
+    ?:  success.cause
+      ~&  >  "choo: output written successfully to {<path.cause>}"
+      [[%exit 0]~ k]
+    ~&  >  "choo: failed to write output to {<path.cause>}"
+    [[%exit 1]~ k]
+  ::
+      %boot
+    ~&  >>  hoon-version+hoon-version
+    ?:  ?=(^ cached-hoon.k)
+      [~ k]
+   [~ k(cached-hoon `(build-honc hoon-txt.cause))]
+  ::
+      %build
+    ~&  >>  "building path: {<pat.cause>}"
+    =/  =entry
+      ~|  "path did not parse: {<pat.cause>}"
+      [(parse-file-path pat.cause) `tex.cause]
+    =/  dir
+      %-  ~(gas by *(map path cord))
+      (turn directory.cause |=((pair @t @t) [(stab p) q]))
+    ?>  ?=(^ cached-hoon.k)
+    =/  [compiled=* new-bc=build-cache new-pc=parse-cache]
+      ?:  arbitrary.cause
+        %-  ~(create-arbitrary builder u.cached-hoon.k bc.k pc.k)
+        [entry dir]
+      %-  ~(create builder u.cached-hoon.k bc.k pc.k)
+      [entry dir]
+    :_  k(bc new-bc, pc new-pc)
+    =/  write-effect
+      :*  %file
+          %write
+          path=out.cause
+          contents=(jam compiled)
+      ==
+    =/  success  !=(compiled *(trap vase))
+    ?:  success
+      ~&  >>>  "choo: build succeeded, sending out write effect"
+      [write-effect]~
+    ~&  >>>  "choo: build failed, skipping write and exiting"
+    [%exit 1]~
+  ==
+--
+::
+::  build system
+::
+=>
+::
+::  dependency system
+::
+|%
++$  raut
+  ::  resolved taut - pax contains real path to file after running taut through +get-fit
+  [face=(unit @tas) pax=path]
+++  rile
+  ::  resolved pile
+  $:  sur=(list raut)
+      lib=(list raut)
+      raw=(list raut)
+      bar=(list raut)
+      =hoon
+  ==
+::
+::  +parse-file-path: parse cord of earth file path to path
+++  parse-file-path
+  |=  pat=cord
+  (rash pat gawp)
+::
+::  +gawp: parse an absolute earth file path
+++  gawp
+  %+  sear
+    |=  p=path
+    ^-  (unit path)
+    ?:  ?=([~ ~] p)  `~
+    ?.  =(~ (rear p))  `p
+    ~
+  ;~(pfix fas (most fas bic))
+::
+::  +bic: parse file/dir name in earth file path
+++  bic
+  %+  cook
+  |=(a=tape (rap 3 ^-((list @) a)))
+  (star ;~(pose nud low hig hep dot sig cab))
+::
+++  to-wain                                           ::  cord to line list
+  |=  txt=cord
+  ^-  wain
+  ?~  txt  ~
+  =/  len=@  (met 3 txt)
+  =/  cut  =+(cut -(a 3, c 1, d txt))
+  =/  sub  sub
+  =|  [i=@ out=wain]
+  |-  ^+  out
+  =+  |-  ^-  j=@
+      ?:  ?|  =(i len)
+              =(10 (cut(b i)))
+          ==
+        i
+      $(i +(i))
+    =.  out  :_  out
+    (cut(b i, c (sub j i)))
+  ?:  =(j len)
+    (flop out)
+  $(i +(j))
+::
+++  parse-pile
+  |=  [pax=path tex=tape]
+  ^-  pile
+  =/  [=hair res=(unit [=pile =nail])]
+    %-  road  |.
+    ((pile-rule pax) [1 1] tex)
+  ?^  res  pile.u.res
+  %-  mean
+  =/  lyn  p.hair
+  =/  col  q.hair
+  ^-  (list tank)
+  :~  leaf+"syntax error at [{<lyn>} {<col>}] in {<pax>}"
+    ::
+      =/  =wain  (to-wain (crip tex))
+      ?:  (gth lyn (lent wain))
+        '<<end of file>>'
+      (snag (dec lyn) wain)
+    ::
+      leaf+(runt [(dec col) '-'] "^")
+  ==
+::
+++  pile-rule
+  |=  pax=path
+  %-  full
+  %+  ifix
+    :_  gay
+    ::  parse optional /? and ignore
+    ::
+    ;~(plug gay (punt ;~(plug fas wut gap dem gap)))
+  |^
+  ;~  plug
+    %+  cook  (bake zing (list (list taut)))
+    %+  rune  hep
+    (most ;~(plug com gaw) taut-rule)
+  ::
+    %+  cook  (bake zing (list (list taut)))
+    %+  rune  lus
+    (most ;~(plug com gaw) taut-rule)
+  ::
+    %+  rune  tis
+    ;~(plug ;~(pose (cold ~ tar) (stag ~ sym)) ;~(pfix gap stap))
+  ::
+    %+  rune  tar
+    ;~  (glue gap)
+      sym
+      ;~(pfix cen sym)
+      ;~(pfix stap)
+    ==
+  ::
+    %+  stag  %tssg
+    (most gap tall:(vang & pax))
+  ==
+  ::
+  ++  pant
+    |*  fel=rule
+    ;~(pose fel (easy ~))
+  ::
+  ++  mast
+    |*  [bus=rule fel=rule]
+    ;~(sfix (more bus fel) bus)
+  ::
+  ++  rune
+    |*  [bus=rule fel=rule]
+    %-  pant
+    %+  mast  gap
+    ;~(pfix fas bus gap fel)
+  --
+::
+++  taut-rule
+  %+  cook  |=(taut +<)
+  ;~  pose
+    (stag ~ ;~(pfix tar sym))               ::  *foo -> [~ %foo]
+    ;~(plug (stag ~ sym) ;~(pfix tis sym))  ::  bar=foo -> [[~ %bar] %foo]
+    (cook |=(a=term [`a a]) sym)            ::  foo    -> [[~ %foo] %foo]
+  ==
+::
+++  segments
+  |=  suffix=@tas
+  ^-  (list path)
+  =/  parser
+    (most hep (cook crip ;~(plug ;~(pose low nud) (star ;~(pose low nud)))))
+  =/  torn=(list @tas)  (fall (rush suffix parser) ~[suffix])
+  %-  flop
+  |-  ^-  (list (list @tas))
+  ?<  ?=(~ torn)
+  ?:  ?=([@ ~] torn)
+    ~[torn]
+  %-  zing
+  %+  turn  $(torn t.torn)
+  |=  s=(list @tas)
+  ^-  (list (list @tas))
+  ?>  ?=(^ s)
+  ~[[i.torn s] [(crip "{(trip i.torn)}-{(trip i.s)}") t.s]]
+::
+++  get-fit
+  |=  [pre=@ta pax=@tas dir=(map path cord)]
+  ^-  (unit path)
+  =/  paz=(list path)  (segments pax)
+  |-
+  ?~  paz
+    ~&  >>  "{<pax>}-not-found"  ~
+  =/  last=term  (rear i.paz)
+  =.  i.paz   `path`(snip i.paz)
+  =/  puz
+    ^-  path
+    %+  snoc
+      `path`[pre i.paz]
+    `@ta`(rap 3 ~[last %'.' %hoon])
+  ?^  (~(get by dir) puz)
+    `puz
+  $(paz t.paz)
+::
+++  resolve-pile
+  ::  turn fits into resolved path suffixes
+  |=  [=pile dir=(map path cord)]
+  ^-  (list raut)
+  ;:  weld
+    (turn sur.pile |=(taut ^-(raut [face (need (get-fit %sur pax dir))])))
+    (turn lib.pile |=(taut ^-(raut [face (need (get-fit %lib pax dir))])))
+  ::
+    %+  turn  raw.pile
+    |=  [face=(unit term) pax=path]
+    =/  pax-snip  (snip pax)
+    =/  pax-rear  (rear pax)
+    ^-  raut
+    [face `path`(snoc pax-snip `@ta`(rap 3 ~[pax-rear %'.' %hoon]))]
+  ::
+    %+  turn  bar.pile
+    |=  [face=term mark=@tas pax=path]
+    =/  pax-snip  (snip pax)
+    =/  pax-hind  (rear pax-snip)
+    =/  pax-rear  (rear pax)
+    ^-  raut
+    [`face `path`(snoc (snip pax-snip) `@ta`(rap 3 ~[pax-hind %'.' pax-rear]))]
+  ==
+--
+::
+::  builder core
+::
+|_  [honc=(trap vase) bc=build-cache pc=parse-cache]
+::
+++  build-honc
+  |=  hoon-txt=cord
+  ^-  (trap vase)
+  (swet *(trap vase) (ream hoon-txt))
+::
++$  octs  [p=@ud q=@]
+::
+::  $node: entry of adjacency matrix with metadata
+::
++$  node
+  $:  =path
+      hash=@
+      ::  holds only outgoing edges
+      deps=(list raut)
+      leaf=graph-leaf
+  ==
+::
++$  graph-leaf
+  $%  [%hoon =hoon]
+      [%octs =octs]
+  ==
+::
+::  $create: build a trap from a hoon/jock file with dependencies
+::
+::    .entry: the entry to build
+::    .dir: the directory to get dependencies from
+::
+::    this is meant to build a kernel gate that takes a hash of a the
+::    dependency directory.
+::
+::    returns a trap, a build-cache, and a parse-cache
+++  create
+  |=  [=entry dir=(map path cord)]
+  ^-  [(trap) build-cache parse-cache]
+  =/  dir-hash  `@uvI`(mug dir)
+  ~&  >>  dir-hash+dir-hash
+  =/  compile
+    (create-target entry dir)
+  =/  ker-gen  (head compile)
+  =/  [=build-cache =parse-cache]  (tail compile)
+  ::  +shot calls the kernel gate to tell it the hash of the dependency directory
+  :_  [build-cache parse-cache]
+  ::  build failure, just return the bunted trap
+  ?:  =(ker-gen *(trap vase))  ker-gen
+  =>  %+  shot  ker-gen
+    =>  d=!>(dir-hash)
+    |.(d)
+  |.(+:^$)
+::
+::  $create-arbitrary: builds a hoon/jock file with dependencies without file hash injection
+::
+::    .entry: the entry to build
+::    .dir: the directory to get dependencies from
+::
+::    returns a trap, a build-cache, and a parse-cache
+++  create-arbitrary
+   |=  [=entry dir=(map path cord)]
+   ^-  [(trap) build-cache parse-cache]
+   =/  [tase=(trap) =build-cache =parse-cache]
+     (create-target entry dir)
+   :_  [build-cache parse-cache]
+   ?:  =(tase *(trap vase))
+     tase
+   =>  tase
+   |.(+:^$)
+::
+::  $create-target: builds a hoon/jock file with dependencies
+::
+::    .entry: the entry to build
+::    .dir: the directory to get dependencies from
+::
+::    returns a trap with the compiled hoon/jock file and the updated caches
+++  create-target
+  |=  [=entry dir=(map path cord)]
+  ^-  [(trap vase) build-cache parse-cache]
+  =/  [parsed-dir=(map path node) pc=parse-cache]  (parse-dir entry dir)
+  =/  all-nodes=(map path node)  parsed-dir
+  =/  [dep-dag=merk-dag =path-dag]  (build-merk-dag all-nodes)
+  ::
+  ::  delete invalid cache entries in bc
+  =.  bc
+    %+  roll
+      ~(tap by bc)
+    |=  [[hash=@ *] bc=_bc]
+    ?:  (~(has by dep-dag) hash)
+      bc
+    (~(del by bc) hash)
+  ::
+  =/  compile
+    %:  compile-target
+      pat.entry
+      path-dag
+      all-nodes
+      bc
+    ==
+  ::
+  [(head compile) (tail compile) pc]
+::
+::  $parse-dir: parse $entry and get dependencies from $dir
+::
+::    .entry: entry to parse
+::    .dir: directory to get dependencies from
+::
+::    returns a map of nodes and a parse cache
+++  parse-dir
+  |=  [suf=entry dir=(map path cord)]
+  ^-  [(map path node) parse-cache]
+  ~&  >  parsing+pat.suf
+  |^
+  =/  file=cord  (get-file suf dir)                   ::  get target file
+  =/  hash=@  (shax file)                             ::  hash target file
+  =/  target=node
+    ?.  (is-hoon pat.suf)
+      :*  pat.suf                                       ::  path
+          hash                                          ::  hash
+          ~                                             ::  deps
+          [%octs [(met 3 file) file]]                   ::  octs
+      ==
+    =/  =pile  (parse-pile pat.suf (trip file))         ::  parse target file
+    =/  deps=(list raut)  (resolve-pile pile dir)       ::  resolve deps
+    :*  pat.suf                                         ::  path
+        hash                                            ::  hash
+        deps                                            ::  deps
+        [%hoon hoon.pile]                               ::  hoon
+    ==
+  =|  nodes=(map path node)                             ::  init empty node map
+  =.  nodes  (~(put by nodes) pat.suf target)           ::  add target node
+  =/  seen=(set path)  (~(put in *(set path)) pat.suf)
+  (resolve-all nodes seen deps.target)
+  ::
+  ++  resolve-all
+    |=  [nodes=(map path node) seen=(set path) deps=(list raut)]
+    ^-  [(map path node) parse-cache]
+    ?~  deps  [nodes pc]                                ::  done if no deps
+    ?.  (~(has in seen) pax.i.deps)                     ::  skip if seen
+      ~&  >>  parsing+pax.i.deps
+      =/  dep-file  (get-file [pax.i.deps ~] dir)       ::  get dep file
+      =/  dep-hash  (shax dep-file)                     ::  hash dep file
+      =^  dep-node=node  pc
+        ?.  (is-hoon pax.i.deps)
+          :_  pc
+          :*  pax.i.deps                                  ::  path
+              dep-hash                                    ::  hash
+              ~                                           ::  deps
+              [%octs [(met 3 dep-file) dep-file]]         ::  octs
+          ==
+        =/  dep-pile
+          ?:  (~(has by pc) dep-hash)                     ::  check cache
+            (~(got by pc) dep-hash)
+          (parse-pile pax.i.deps (trip dep-file))         ::  parse dep file
+        ~&  >>  parsed+pax.i.deps
+        =/  dep-deps  (resolve-pile dep-pile dir)         ::  resolve dep deps
+        ~&  >>  resolved+pax.i.deps
+        :_  (~(put by pc) dep-hash dep-pile)              ::  cache parse
+        :*  pax.i.deps
+            dep-hash
+            dep-deps
+            [%hoon hoon.dep-pile]
+        ==
+      =.  nodes  (~(put by nodes) pax.i.deps dep-node)  ::  add dep node
+      =.  seen  (~(put in seen) pax.i.deps)             ::  mark as seen
+      %=  $
+        nodes  nodes
+        seen   seen
+        deps   (weld t.deps deps.dep-node)                   ::  add new deps
+      ==
+    $(deps t.deps)                                      ::  next dep
+  ::
+  --
+::
+::  $merk-dag: content-addressed map of nodes
+::
+::    maps content hashes to nodes. each hash is computed from the node's
+::    content and the hashes of its dependencies, forming a merkle tree.
+::    used to detect changes in the dependency graph and enable caching.
+::
++$  merk-dag  (map @ node)
+::
+::  $path-dag: path-addressed map of nodes with their content hashes
+::
+::    maps file paths to [hash node] pairs. provides a way to look up nodes
+::    by path while maintaining the connection to their content hash in the
+::    merk-dag. used during traversal to find dependencies by path.
+::
++$  path-dag  (map path [@ node])
+::
+::  $graph-view: adjacency matrix with easier access to neighbors
+::
+::    used to keep track of traversal when building the merkle DAG
+::
++$  graph-view  (map path (set path))
+::
+::  $build-merk-dag: builds a merkle DAG out of the dependency folder
+::
+::    .nodes: the nodes of the dependency graph
+::
+::    returns a merkle DAG and a path-dag
+++  build-merk-dag
+  |^
+  ::
+  ::  node set of entire dir + target
+  |=  nodes=(map path node)
+  ^-  [merk-dag path-dag]
+  ~&  >>  building-merk-dag-for+~(key by nodes)
+  ::
+  ::  need a way to uniquely identify dep directories
+  =|  dep-dag=merk-dag
+  =|  =path-dag
+  =/  graph  (build-graph-view nodes)
+  =/  next=(map path node)  (update-next nodes graph)
+  ::
+  ::  traverse via a topological sorting of the DAG using Kahn's algorithm
+  |-
+  ?:  .=(~ next)
+    ?.  .=(~ graph)
+      ~|(cycle-detected+~(key by graph) !!)
+    [dep-dag path-dag]
+  =-
+    %=  $
+      next   (update-next nodes graph)
+      graph  graph
+      dep-dag  dd
+      path-dag  pd
+    ==
+  ^-  [graph=(map path (set path)) dd=(map @ node) pd=^path-dag]
+  ::
+  ::  every node in next is put into path-dag and dep-dag along with
+  ::  its hash
+  %+  roll
+    ~(tap by next)
+  |=  [[p=path n=node] graph=_graph dep-dag=_dep-dag path-dag=_path-dag]
+  =/  hash  (calculate-hash n dep-dag path-dag)
+  :+  (update-graph-view graph p)
+    (~(put by dep-dag) hash n)
+  (~(put by path-dag) p [hash n])
+  ::
+  ::  $calculate-hash: calculate the hash of a node
+  ::
+  ::    .n: the node to calculate the hash of
+  ::    .dep-dag: the merkle DAG of the dependency graph
+  ::    .path-dag: the path-dag of the dependency graph
+  ::
+  ::    returns the hash of the node
+  ++  calculate-hash
+    |=  [n=node dep-dag=merk-dag =path-dag]
+    ^-  @
+    %+  roll
+      deps.n
+    |=  [raut hash=_hash.n]
+    ?.  (~(has by path-dag) pax)
+      ~&  >>>  "calculate-hash: Missing {<pax>}"  !!
+    =/  [dep-hash=@ *]
+      (~(got by path-dag) pax)
+    (shax (rep 8 ~[hash dep-hash]))
+  --
+::
+::  $compile-target: compile a target hoon file
+::
+::    .pat: path to the target hoon file
+::    .path-dag: the path-dag of the dependency graph
+::    .nodes: the nodes of the dependency graph
+::    .bc: the build cache
+::
+::    returns a trap vase with the compiled hoon file and the updated build
+::    cache. if a build failure is detected, a bunted (trap vase) is returned
+::    instead.
+++  compile-target
+  |^
+  |=  [pat=path =path-dag nodes=(map path node) bc=build-cache]
+  ^-  [(trap vase) build-cache]
+  ~&  >>  compiling-target+pat
+  =/  n=node
+    ~|  """
+        couldn't find node {<pat>} in path-dag.
+        nodes: {<~(key by nodes)>}
+        path-dag: {<~(key by path-dag)>}
+        """
+    +:(~(got by path-dag) pat)
+  =/  graph  (build-graph-view nodes)
+  =/  next=(map path node)  (update-next nodes graph)
+  =|  failed=_|
+  |-  ^-  [(trap vase) build-cache]
+  ?:  failed  [*(trap vase) bc]
+  ?:  .=(~ next)
+    =/  [=build-result new-bc=build-cache]
+      (compile-node n path-dag bc)
+    ?-  -.build-result
+      ::
+      %|  ~&  >>>  "compile-target: failed: {<pat>}"
+          [*(trap vase) new-bc]
+      ::
+      %&  [p.build-result new-bc]
+    ==
+  =/  [err=? bc=build-cache]
+    %+  roll  ~(tap by next)
+    |=  [[p=path n=node] [err=_| bc=_bc]]
+    =/  [=build-result new-bc=build-cache]
+      (compile-node n path-dag bc)
+    ?-  -.build-result
+      ::
+      %|  ~&  >>>  "compile-target: failed: {<p>}"
+          [& new-bc]
+      ::
+      %&  [err new-bc]
+    ==
+  =.  graph
+    (roll ~(tap by next) |=([[p=path *] g=_graph] (update-graph-view g p)))
+  %=  $
+    next       (update-next nodes graph)
+    graph      graph
+    bc         bc
+    failed     err
+  ==
+  ::
+  ::  $compile-node: compile a single node
+  ::
+  ::    .n: the node to compile
+  ::    .path-dag: the path-dag of the dependency graph
+  ::    .bc: the build cache
+  ::
+  ::    looks up the node in the build cache and compiles it if it's not already
+  ::    cached.
+  ::
+  ::    returns a $build-result and the updated build cache
+  ++  compile-node
+    |=  [n=node =path-dag bc=build-cache]
+    ^-  [build-result build-cache]
+    =/  [dep-hash=@ *]  (~(got by path-dag) path.n)
+    ?:  (~(has by bc) dep-hash)
+      ~&  >  build-cache-hit+path.n
+      :_  bc
+      [%.y (~(got by bc) dep-hash)]
+    ~&  >  build-cache-miss+path.n
+    =/  =build-result  (mule |.((build-node n path-dag bc)))
+    =?  bc  ?=(%& -.build-result)
+      (~(put by bc) dep-hash p.build-result)
+    =-  ?.  ?=(%| -.build-result)  -
+        ((slog p.build-result) -)
+    [build-result bc]
+  ::
+  ::  $build-node: build a single node and its dependencies
+  ::
+  ::    .n: the node to compile
+  ::    .path-dag: the path-dag of the dependency graph
+  ::    .bc: the build cache
+  ::
+  ::    returns a trap vase with the compiled hoon
+  ++  build-node
+    |=  [n=node =path-dag bc=build-cache]
+    ^-  (trap vase)
+    ~>  %bout
+    =;  dep-vaz=(trap vase)
+      ?:  ?=(%hoon -.leaf.n)
+        ::
+        ::  Faces are resolved via depth-first search into the subject.
+        ::  We append the honc (hoon.hoon) to the end of the vase
+        ::  because imports have higher precedence when resolving faces.
+        ::  To avoid shadowing issues with hoon.hoon, attach faces to your
+        ::  imports or avoid shadowed names altogether.
+        (swet (slat dep-vaz honc) hoon.leaf.n)
+      =>  octs=!>(octs.leaf.n)
+      |.(octs)
+    %+  roll  deps.n
+    |=  [r=raut vaz=(trap vase)]
+    ~&  >  grabbing-dep+pax.r
+    =/  [dep-hash=@ dep-node=node]
+      ~|  "couldn't find dep hash for {<pax.r>}"
+      (~(got by path-dag) pax.r)
+    =/  dep-vaz=(trap vase)
+      ~|  "couldn't find artifact for {<pax.r>} in build cache"
+      (~(got by bc) dep-hash)
+    ~&  >  attaching-face+face.r
+    ::
+    ::  Ford imports are included in the order that they appear in the deps.
+    (slat vaz (label-vase dep-vaz face.r))
+  ::
+  ::  $label-vase: label a (trap vase) with a face
+  ::
+  ::    .vaz: the (trap vase) to label
+  ::    .face: the face to label the (trap vase) with
+  ::
+  ::    returns a (trap vase) labeled with the given face
+  ++  label-vase
+    |=  [vaz=(trap vase) face=(unit @tas)]
+    ^-  (trap vase)
+    ?~  face  vaz
+    =>  [vaz=vaz face=u.face]
+    |.
+    =/  vas  $:vaz
+    [[%face face p.vas] q.vas]
+  --
+::
+::  $update-next: returns nodes from a $graph-view that have no outgoing edges
+::
+::    .nodes: the nodes of the dependency graph
+::    .gv: the graph-view of the dependency graph
+::
+::    assumes that entries in $nodes that are not in the $graph-view have
+::    already been visited.
+::
+++  update-next
+  |=  [nodes=(map path node) gv=graph-view]
+  ^-  (map path node)
+  ::
+  ::  if we don't have the entry in gv, already visited
+  %+  roll
+    ~(tap by gv)
+  |=  [[pax=path edges=(set path)] next=(map path node)]
+  ::
+  :: if a node has no out edges, add it to next
+  ?.  =(*(set path) edges)
+    next
+  %+  ~(put by next)
+    pax
+  (~(got by nodes) pax)
+::
+::  $update-graph-view: updates a $graph-view by removing a $path
+::
+::    .gv: the graph-view to update
+::    .p: the path to remove from the graph-view
+::
+::    deletes the $path from the $graph-view and removes it from all edge sets
+::
+++  update-graph-view
+  |=  [gv=graph-view p=path]
+  ^-  graph-view
+  =.  gv  (~(del by gv) p)
+  %-  ~(urn by gv)
+  |=  [pax=path edges=(set path)]
+  (~(del in edges) p)
+::
+::  $build-graph-view: build a graph-view from a node map
+::
+::    .nodes: the nodes of the dependency graph
+::
+::    returns a graph-view of the dependency graph
+::
+++  build-graph-view
+  |=  nodes=(map path node)
+  ^-  graph-view
+  %-  ~(urn by nodes)
+  |=  [* n=node]
+  %-  silt
+  (turn deps.n |=(raut pax))
+::
+::  $slat: merge two (trap vase)s
+::
+::    .hed: the first (trap vase)
+::    .tal: the second (trap vase)
+::
+::    returns a merged (trap vase)
+++  slat
+  |=  [hed=(trap vase) tal=(trap vase)]
+  ^-  (trap vase)
+  =>  +<
+  |.
+  =+  [bed bal]=[$:hed $:tal]
+  [[%cell p:bed p:bal] [q:bed q:bal]]
+::  +shot: deferred slam
+::
+::    .gat: the gate to slam with the sample as a (trap vase)
+::    .sam: the sample to slam with the gate
+::
+::    NOTE: this should never run inside of a trap. if it does, the builder
+::    dependencies will leak into the result.
+::
+++  shot
+  |=  [gat=(trap vase) sam=(trap vase)]
+  ^-  (trap vase)
+  =/  [typ=type gen=hoon]
+    :-  [%cell p:$:gat p:$:sam]
+    [%cnsg [%$ ~] [%$ 2] [%$ 3] ~]
+  =+  gun=(~(mint ut typ) %noun gen)
+  =>  [typ=p.gun +<.$]
+  |.
+  [typ .*([q:$:gat q:$:sam] [%9 2 %10 [6 %0 3] %0 2])]
+::
+::  +swet: deferred +slap
+::
+::  NOTE: this is +swat but with a bug fixed that caused a space leak in
+::  the resulting trap vases.
+::
+++  swet
+  |=  [tap=(trap vase) gen=hoon]
+  ^-  (trap vase)
+  =/  gun  (~(mint ut p:$:tap) %noun gen)
+  =>  [gun=gun tap=tap]
+  |.  ~+
+  [p.gun .*(q:$:tap q.gun)]
+::
+++  get-file                                          ::  get file contents
+  |=  [suf=entry dir=(map path cord)]
+  ^-  cord
+  ?~  tex.suf
+    ~|  "file not found: {<pat.suf>}"
+    (~(got by dir) pat.suf)
+  u.tex.suf
+::
+++  is-hoon
+  |=  pax=path
+  ^-  ?
+  =/  end  (rear pax)
+  !=(~ (find ".hoon" (trip end)))
+::
+--

+ 380 - 0
crates/nockapp/apps/choo/src/lib.rs

@@ -0,0 +1,380 @@
+use clap::{arg, command, ColorChoice, Parser};
+use std::env::current_dir;
+use std::ffi::OsStr;
+use std::path::{Path, PathBuf};
+use sword::interpreter::{self, Context};
+use tokio::fs::{self, File};
+use tokio::io::AsyncReadExt;
+use tracing::{debug, info, instrument, trace};
+use walkdir::{DirEntry, WalkDir};
+
+use crown::kernel::boot::{self, default_boot_cli, Cli as BootCli};
+use crown::nockapp::driver::Operation;
+use crown::noun::slab::NounSlab;
+use crown::{system_data_dir, AtomExt, Noun, NounExt};
+use sword::noun::{Atom, D, T};
+use sword_macros::tas;
+
+pub const OUT_JAM_NAME: &str = "out.jam";
+
+pub type Error = Box<dyn std::error::Error>;
+
+static KERNEL_JAM: &[u8] = include_bytes!("../bootstrap/choo.jam");
+static HOON_TXT: &[u8] = include_bytes!("../../hoon/hoon-138.hoon");
+
+#[derive(Parser, Debug)]
+#[command(about = "Tests various poke types for the kernel", author = "zorp", version, color = ColorChoice::Auto)]
+pub struct ChooCli {
+    #[command(flatten)]
+    pub boot: BootCli,
+
+    #[arg(help = "Path to file to compile")]
+    pub entry: std::path::PathBuf,
+
+    #[arg(help = "Path to root of dependency directory", default_value = "hoon")]
+    pub directory: std::path::PathBuf,
+
+    #[arg(
+        long,
+        help = "Build raw, without file hash injection",
+        default_value = "false"
+    )]
+    pub arbitrary: bool,
+
+    #[arg(long, help = "Output file path", default_value = None)]
+    pub output: Option<std::path::PathBuf>,
+}
+
+pub async fn choo_data_dir() -> PathBuf {
+    let choo_data_dir = system_data_dir().join("choo");
+    if !choo_data_dir.exists() {
+        fs::create_dir_all(&choo_data_dir)
+            .await
+            .unwrap_or_else(|_| {
+                panic!(
+                    "Panicked at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            });
+    }
+    choo_data_dir
+}
+
+/// Builds and interprets a Hoon generator to produce a list of pokes
+///
+/// This function:
+/// 1. Builds the specified Hoon generator into a jam
+/// 2. Decodes the jam into a Nock noun
+/// 3. Interprets the noun with a kick operation to run the generator
+///
+/// # Parameters
+/// - `context`: The Nock interpreter context
+/// - `path`: Path to the Hoon generator file
+///
+/// # Returns
+/// - A noun
+pub async fn build_and_kick_jam(
+    context: &mut Context,
+    path: &str,
+    deps_dir: PathBuf,
+    out_dir: Option<PathBuf>,
+) -> Noun {
+    let jam = build_jam(path, deps_dir, out_dir, true, false)
+        .await
+        .expect("failed to build page");
+    debug!("Built jam");
+    let generator_trap =
+        Noun::cue_bytes_slice(&mut context.stack, &jam).expect("invalid generator jam");
+
+    let kick = T(&mut context.stack, &[D(9), D(2), D(0), D(1)]);
+    debug!("kicking trap");
+    interpreter::interpret(context, generator_trap, kick).unwrap_or_else(|_| {
+        panic!(
+            "Panicked at {}:{} (git sha: {:?})",
+            file!(),
+            line!(),
+            option_env!("GIT_SHA")
+        )
+    })
+}
+
+pub async fn save_generator(
+    context: &mut Context,
+    path: &str,
+    deps_dir: PathBuf,
+    out_dir: Option<PathBuf>,
+) -> Result<(), Error> {
+    let cli = default_boot_cli(true);
+    boot::init_default_tracing(&cli);
+    let kicked = build_and_kick_jam(context, path, deps_dir, out_dir.clone()).await;
+    let jammed = kicked.jam_self(&mut context.stack);
+
+    let file_name = Path::new(path)
+        .file_stem()
+        .unwrap_or_else(|| OsStr::new("generator"))
+        .to_string_lossy()
+        .to_string();
+    let output_file = out_dir
+        .clone()
+        .unwrap_or_else(|| current_dir().expect("Failed to get current directory"))
+        .join(format!("{}.jam", file_name));
+
+    if let Some(parent) = output_file.parent() {
+        fs::create_dir_all(parent).await?;
+    }
+
+    fs::write(&output_file, jammed).await?;
+
+    println!("Generator saved to: {}", output_file.display());
+    Ok(())
+}
+/// Builds a jam (serialized Nock noun) from a Hoon source file
+///
+/// This function:
+/// 1. Locates the source file relative to the hoon directory
+/// 2. Creates a temporary directory for build artifacts
+/// 3. Initializes a Nock app with the choo build system
+/// 4. Builds the source file and returns the resulting jam as bytes
+///
+/// # Parameters
+/// - `entry`: Path to the Hoon source file, relative to the hoon directory
+/// - `arbitrary`: Whether to build with arbitrary mode enabled
+/// - `new`: Whether to force a clean build
+///
+/// # Returns
+/// - A Result containing either the jam bytes or a choo error
+pub async fn build_jam(
+    entry: &str,
+    deps_dir: PathBuf,
+    out_dir: Option<PathBuf>,
+    arbitrary: bool,
+    new: bool,
+) -> Result<Vec<u8>, Error> {
+    info!("Dependencies directory: {:?}", deps_dir);
+    info!("Entry file: {:?}", entry);
+    let (nockapp, out_path) =
+        initialize_with_default_cli(entry.into(), deps_dir, out_dir, arbitrary, new).await?;
+    info!("Output path: {:?}", out_path);
+    run_build(nockapp, Some(out_path.clone())).await
+}
+
+pub async fn initialize_choo(cli: ChooCli) -> Result<(crown::nockapp::NockApp, PathBuf), Error> {
+    initialize_choo_(
+        cli.entry,
+        cli.directory,
+        cli.arbitrary,
+        cli.output,
+        cli.boot.clone(),
+    )
+    .await
+}
+
+pub async fn initialize_with_default_cli(
+    entry: std::path::PathBuf,
+    deps_dir: std::path::PathBuf,
+    out: Option<std::path::PathBuf>,
+    arbitrary: bool,
+    new: bool,
+) -> Result<(crown::nockapp::NockApp, PathBuf), Error> {
+    let cli = default_boot_cli(new);
+    initialize_choo_(entry, deps_dir, arbitrary, out, cli).await
+}
+
+pub async fn initialize_choo_(
+    entry: std::path::PathBuf,
+    deps_dir: std::path::PathBuf,
+    arbitrary: bool,
+    out: Option<std::path::PathBuf>,
+    boot_cli: BootCli,
+) -> Result<(crown::nockapp::NockApp, PathBuf), Error> {
+    debug!("Dependencies directory: {:?}", deps_dir);
+    debug!("Entry file: {:?}", entry);
+    let data_dir = system_data_dir();
+    let mut nockapp = boot::setup(
+        KERNEL_JAM,
+        Some(boot_cli.clone()),
+        &[],
+        "choo",
+        Some(data_dir),
+    )
+    .await?;
+    let mut slab = NounSlab::new();
+    let hoon_cord = Atom::from_value(&mut slab, HOON_TXT)
+        .unwrap_or_else(|_| {
+            panic!(
+                "Panicked at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        })
+        .as_noun();
+    let bootstrap_poke = T(&mut slab, &[D(tas!(b"boot")), hoon_cord]);
+    slab.set_root(bootstrap_poke);
+
+    nockapp
+        .add_io_driver(crown::one_punch_driver(slab, Operation::Poke))
+        .await;
+
+    let mut slab = NounSlab::new();
+    let entry_contents = {
+        let mut contents_vec: Vec<u8> = vec![];
+        let mut file = File::open(&entry).await?;
+        file.read_to_end(&mut contents_vec).await?;
+        Atom::from_value(&mut slab, contents_vec)?.as_noun()
+    };
+
+    let entry_string = canonicalize_and_string(&entry);
+    let entry_path = Atom::from_value(&mut slab, entry_string)?.as_noun();
+
+    let mut directory_noun = D(0);
+    let directory = canonicalize_and_string(&deps_dir);
+
+    let walker = WalkDir::new(&directory).follow_links(true).into_iter();
+    for entry_result in walker.filter_entry(is_valid_file_or_dir) {
+        let entry = entry_result?;
+        let is_file = entry.metadata()?.is_file();
+        if is_file {
+            let path_str = entry
+                .path()
+                .to_str()
+                .expect("Failed to convert path to string")
+                .strip_prefix(&directory)
+                .expect("Failed to strip prefix");
+            debug!("Path: {:?}", path_str);
+            let path_cord = Atom::from_value(&mut slab, path_str)?.as_noun();
+
+            let contents = {
+                let mut contents_vec: Vec<u8> = vec![];
+                let mut file = File::open(entry.path()).await?;
+                file.read_to_end(&mut contents_vec).await?;
+                Atom::from_value(&mut slab, contents_vec)?.as_noun()
+            };
+
+            let entry_cell = T(&mut slab, &[path_cord, contents]);
+            directory_noun = T(&mut slab, &[entry_cell, directory_noun]);
+        }
+    }
+    let arbitrary_noun = if arbitrary { D(0) } else { D(1) };
+
+    let out_path_string = if let Some(path) = &out {
+        let parent = path.parent().unwrap_or_else(|| Path::new("."));
+        // TODO: this breaks on everything except Bazel if an output path is specified
+        let filename = path.file_name().unwrap_or_else(|| OsStr::new(OUT_JAM_NAME));
+        let parent_canonical = canonicalize_and_string(parent);
+        format!("{}/{}", parent_canonical, filename.to_string_lossy())
+    } else {
+        let parent_dir = current_dir().expect("Failed to get current directory");
+        format!("{}/{}", canonicalize_and_string(&parent_dir), OUT_JAM_NAME)
+    };
+    debug!("Output path: {:?}", out_path_string);
+    let out_path = Atom::from_value(&mut slab, out_path_string.clone())?.as_noun();
+
+    let poke = T(
+        &mut slab,
+        &[D(tas!(b"build")), entry_path, entry_contents, directory_noun, arbitrary_noun, out_path],
+    );
+    slab.set_root(poke);
+
+    nockapp
+        .add_io_driver(crown::one_punch_driver(slab, Operation::Poke))
+        .await;
+    nockapp.add_io_driver(crown::file_driver()).await;
+    nockapp.add_io_driver(crown::exit_driver()).await;
+    Ok((nockapp, out_path_string.into()))
+}
+
+pub fn is_valid_file_or_dir(entry: &DirEntry) -> bool {
+    let is_dir = entry
+        .metadata()
+        .unwrap_or_else(|_| {
+            panic!(
+                "Panicked at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        })
+        .is_dir();
+
+    let is_hoon = entry
+        .file_name()
+        .to_str()
+        .map(|s| s.ends_with(".hoon"))
+        .unwrap_or(false);
+
+    let is_jock = entry
+        .file_name()
+        .to_str()
+        .map(|s| s.ends_with(".jock"))
+        .unwrap_or(false);
+
+    is_dir || is_hoon || is_jock
+}
+
+#[instrument]
+pub fn canonicalize_and_string(path: &std::path::Path) -> String {
+    trace!("Canonicalizing path: {:?}", path);
+    let path = path.canonicalize().expect("Failed to canonicalize path");
+    debug!("Canonicalized path: {:?}", path);
+    let path = path.to_str().expect("Failed to convert path to string");
+
+    path.to_string()
+}
+
+/// Run the build and verify the output file, used to build files outside of cli.
+pub async fn run_build(
+    nockapp: crown::nockapp::NockApp,
+    out_path: Option<PathBuf>,
+) -> Result<Vec<u8>, Error> {
+    nockapp.run().await?;
+    let out_path = out_path.unwrap_or_else(|| {
+        std::env::current_dir()
+            .unwrap_or_else(|_| {
+                panic!(
+                    "Panicked at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            })
+            .join(OUT_JAM_NAME)
+    });
+    Ok(fs::read(out_path).await?)
+}
+
+#[cfg(test)]
+mod tests {
+    #[test]
+    #[cfg_attr(miri, ignore)]
+    fn test_canonicalize_and_string() {
+        // Create a temp dir that will definitely exist
+        let temp_dir = std::env::temp_dir();
+
+        // Use canonicalize_and_string on the temp dir
+        let result = super::canonicalize_and_string(&temp_dir);
+
+        // Compare with direct canonicalization
+        let canonical = temp_dir.canonicalize().unwrap_or_else(|_| {
+            panic!(
+                "Panicked at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+        assert_eq!(
+            result,
+            canonical.to_str().unwrap_or_else(|| {
+                panic!(
+                    "Panicked at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            })
+        );
+    }
+}

+ 38 - 0
crates/nockapp/apps/choo/src/main.rs

@@ -0,0 +1,38 @@
+use clap::Parser;
+use crown::kernel::boot;
+use futures::FutureExt;
+
+use choo::*;
+use sword::mem::{AllocationError, NewStackError};
+
+#[tokio::main]
+async fn main() -> Result<(), Error> {
+    let cli = ChooCli::parse();
+    boot::init_default_tracing(&cli.boot.clone());
+    let result = std::panic::AssertUnwindSafe(async {
+        let (nockapp, _) = initialize_choo(cli).await?;
+        nockapp.run().await?;
+        Ok::<(), Error>(())
+    })
+    .catch_unwind()
+    .await;
+
+    match result {
+        Ok(Ok(_)) => println!("no panic!"),
+        Ok(Err(e)) => println!("Error initializing NockApp: {e:?}"),
+        Err(e) => {
+            println!("Caught panic!");
+            // now we downcast the error
+            // and print it out
+            if let Some(e) = e.downcast_ref::<AllocationError>() {
+                println!("Allocation error occurred: {}", e);
+            } else if let Some(e) = e.downcast_ref::<NewStackError>() {
+                println!("NockStack creation error occurred: {}", e);
+            } else {
+                println!("Unknown panic: {e:?}");
+            }
+        }
+    }
+
+    Ok(())
+}

+ 54 - 0
crates/nockapp/apps/choo/tests/build.rs

@@ -0,0 +1,54 @@
+// This test must be run in release mode or it will stack overflow.
+mod test {
+
+    use tempfile::TempDir;
+    use tracing::{debug, info};
+
+    #[ignore = "Skipping because test is too slow and CI should already self host choo from scratch"]
+    #[tokio::test]
+    async fn test_compile_test_app() -> Result<(), Box<dyn std::error::Error>> {
+        let temp_dir = TempDir::new()?;
+        let temp_path = temp_dir.path();
+        let out_path = format!(
+            "{}/out.jam",
+            choo::canonicalize_and_string(temp_path).to_lowercase()
+        );
+
+        // use std::path to get pwd() and then canonicalize
+        let pwd = std::env::current_dir()?;
+        let mut test_dir = pwd.clone();
+        test_dir.pop();
+        test_dir.push("test-app");
+
+        let entry = test_dir.join("bootstrap/kernel.hoon");
+
+        // TODO: Add -o flag to specify output file and then use the tmp-dir
+        // TODO: instead of mutating the non-tmp filesystem in this test
+        // Clean up any existing output file
+        let _ = tokio::fs::remove_file(out_path).await;
+
+        let mut deps_dir = pwd.clone();
+        deps_dir.pop();
+        deps_dir.push("hoon-deps");
+        info!("Test directory: {:?}", test_dir);
+        info!("Dependencies directory: {:?}", deps_dir);
+        info!("Entry file: {:?}", entry);
+
+        let (nockapp, out_path) =
+            choo::initialize_with_default_cli(entry, deps_dir, None, false, true).await?;
+
+        let result = choo::run_build(nockapp, Some(out_path.clone())).await;
+        assert!(result.is_ok());
+
+        // Cleanup
+        let _ = tokio::fs::remove_file(out_path.clone()).await;
+        debug!("Removed file");
+
+        // Second run to test consecutive execution
+        // FIXME: This currently panics because of the one-shot.
+        // let result = test_build(&mut nockapp).await;
+        // // Cleanup
+        // let _ = fs::remove_file("out.jam").await;
+        Ok(())
+    }
+}

+ 33 - 0
crates/nockapp/apps/hoon-deps/lib/builder.hoon

@@ -0,0 +1,33 @@
+/*  hoon-138-hoon  %hoon  /lib/hoon-138/hoon
+/*  wrapper-hoon  %hoon  /lib/wrapper/hoon
+/*  kernel-hoon  %hoon  /lib/kernel/hoon
+!.
+::
+::  Bootstrap builder: to build the bootstrap formula for Choo using
+::  Urbit Ford
+::
+::  sync files into a desk %choo
+::  dojo> =choo -build-file /=choo=/lib/builder/hoon
+::  dojo> .choo/jam choo
+::
+::  copy <your-fakezod>/.urb/put/choo.jam to choo/bootstrap/choo.jam
+^-  *
+~&  "compiling hoon"
+=/  hoon-knob=[t=type form=nock]
+  ~>  %bout
+  (~(mint ut %noun) %noun (ream hoon-138-hoon))
+~&  "compiling wrapper"
+=/  wrapper-knob=[t=type form=nock]
+  ~>  %bout
+  (~(mint ut t.hoon-knob) %noun (ream wrapper-hoon))
+~&  "compiling kernel"
+=/  kernel-knob=[t=type form=nock]
+  ~>  %bout
+  (~(mint ut t.wrapper-knob) %noun (rain /lib/choo/kernel/hoon kernel-hoon))
+=/  trap-nock=nock
+  [%7 [%7 form.hoon-knob form.wrapper-knob] form.kernel-knob]
+~&  %built-trap-nock
+trap-nock
+::  TODO: use this once we can execute trap in NockApp
+::=>  [trap=trap-nock hash=(mug trap-nock)]
+::|.  .*([.*(0 trap) hash] [%9 2 %10 [6 %0 3] %0 2])

+ 96 - 0
crates/nockapp/apps/hoon-deps/lib/wrapper.hoon

@@ -0,0 +1,96 @@
+|%
++$  goof    [mote=term =tang]
++$  wire    path
++$  ovum    [=wire =input]
++$  crud    [=goof =input]
++$  input   [eny=@ our=@ux now=@da cause=*]
+::
+++  keep
+  |*  inner=mold
+  =>
+  |%
+  +$  inner-state  inner
+  +$  outer-state
+    $%  [%0 desk-hash=(unit @uvI) internal=inner]
+    ==
+  +$  outer-fort
+    $_  ^|
+    |_  outer-state
+    ++  load
+      |~  arg=outer-state
+      **
+    ++  peek
+      |~  arg=path
+      *(unit (unit *))
+    ++  poke
+      |~  [num=@ ovum=*]
+      *[(list *) *]
+    ++  wish
+      |~  txt=@
+      **
+    --
+  ::
+  +$  fort
+    $_  ^|
+    |_  state=inner-state
+    ++  load
+      |~  arg=inner-state
+      *inner-state
+    ++  peek
+      |~  arg=path
+      *(unit (unit *))
+    ++  poke
+      |~  arg=ovum
+      [*(list *) *inner-state]
+    --
+  --
+  ::
+  |=  crash=?
+  |=  inner=fort
+  |=  hash=@uvI
+  =<  .(desk-hash.outer `hash)
+  |_  outer=outer-state
+  +*  inner-fort  ~(. inner internal.outer)
+  ++  load
+    |=  old=outer-state
+    ?+    -.old  ~&("wrapper +load: invalid old state" !!)
+        %0
+      =/  new-internal  (load:inner-fort internal.old)
+      ..load(internal.outer new-internal)
+    ==
+  ::
+  ++  peek
+    |=  arg=path
+    ^-  (unit (unit *))
+    (peek:inner-fort arg)
+  ::
+  ++  wish
+    |=  txt=@
+    ^-  *
+    q:(slap !>(~) (ream txt))
+  ::
+  ++  poke
+    |=  [num=@ ovum=*]
+    ^-  [(list *) _..poke]
+    =/  effects=(list *)  ?:(crash ~[exit/0] ~)
+    ?+   ovum  ~&("wrapper +poke invalid arg: {<ovum>}" effects^..poke)
+        [[%$ %arvo ~] *]
+      =/  g  ((soft crud) +.ovum)
+      ?~  g  ~&(%invalid-goof effects^..poke)
+      =-  [effects ..poke]
+      (slog tang.goof.u.g)
+    ::
+        [[%poke *] *]
+      =/  ovum  ((soft ^ovum) ovum)
+      ?~  ovum  ~&("wrapper +poke invalid arg: {<ovum>}" ~^..poke)
+      =/  o  ((soft input) input.u.ovum)
+      ?~  o
+        ~&  "wrapper: could not mold poke type: {<ovum>}"
+        =+  (road |.(;;(^^ovum ovum)))
+        ~^..poke
+      =^  effects  internal.outer
+        (poke:inner-fort u.ovum)
+      [effects ..poke(internal.outer internal.outer)]
+    ==
+  --
+--

+ 14075 - 0
crates/nockapp/apps/hoon/hoon-138.hoon

@@ -0,0 +1,14075 @@
+::
+::::    /sys/hoon                                       ::
+  ::                                                    ::
+=<  ride
+=>  %138  =>
+::                                                      ::
+::::    0: version stub                                 ::
+  ::                                                    ::
+~%  %k.138  ~  ~                                        ::
+|%
+++  hoon-version  +
+--  =>
+~%  %one  +  ~
+::    layer-1
+::
+::  basic mathematical operations
+|%
+::    unsigned arithmetic
++|  %math
+++  add
+  ~/  %add
+  ::    unsigned addition
+  ::
+  ::  a: augend
+  ::  b: addend
+  |=  [a=@ b=@]
+  ::  sum
+  ^-  @
+  ?:  =(0 a)  b
+  $(a (dec a), b +(b))
+::
+++  dec
+  ~/  %dec
+  ::    unsigned decrement by one.
+  |=  a=@
+  ~_  leaf+"decrement-underflow"
+  ?<  =(0 a)
+  =+  b=0
+  ::  decremented integer
+  |-  ^-  @
+  ?:  =(a +(b))  b
+  $(b +(b))
+::
+++  div
+  ~/  %div
+  ::    unsigned divide
+  ::
+  ::  a: dividend
+  ::  b: divisor
+  |:  [a=`@`1 b=`@`1]
+  ::  quotient
+  ^-  @
+  -:(dvr a b)
+::
+++  dvr
+  ~/  %dvr
+  ::    unsigned divide with remainder
+  ::
+  ::  a: dividend
+  ::  b: divisor
+  |:  [a=`@`1 b=`@`1]
+  ::  p: quotient
+  ::  q: remainder
+  ^-  [p=@ q=@]
+  ~_  leaf+"divide-by-zero"
+  ?<  =(0 b)
+  =+  c=0
+  |-
+  ?:  (lth a b)  [c a]
+  $(a (sub a b), c +(c))
+::
+++  gte
+  ~/  %gte
+  ::    unsigned greater than or equals
+  ::
+  ::  returns whether {a >= b}.
+  ::
+  ::  a: left hand operand (todo: name)
+  ::  b: right hand operand
+  |=  [a=@ b=@]
+  ::  greater than or equal to?
+  ^-  ?
+  !(lth a b)
+::
+++  gth
+  ~/  %gth
+  ::    unsigned greater than
+  ::
+  ::  returns whether {a > b}
+  ::
+  ::  a: left hand operand (todo: name)
+  ::  b: right hand operand
+  |=  [a=@ b=@]
+  ::  greater than?
+  ^-  ?
+  !(lte a b)
+::
+++  lte
+  ~/  %lte
+  ::    unsigned less than or equals
+  ::
+  ::  returns whether {a >= b}.
+  ::
+  ::  a: left hand operand (todo: name)
+  ::  b: right hand operand
+  |=  [a=@ b=@]
+  ::  less than or equal to?
+  |(=(a b) (lth a b))
+::
+++  lth
+  ~/  %lth
+  ::    unsigned less than
+  ::
+  ::  a: left hand operand (todo: name)
+  ::  b: right hand operand
+  |=  [a=@ b=@]
+  ::  less than?
+  ^-  ?
+  ?&  !=(a b)
+      |-
+      ?|  =(0 a)
+          ?&  !=(0 b)
+              $(a (dec a), b (dec b))
+  ==  ==  ==
+::
+++  max
+  ~/  %max
+  ::    unsigned maximum
+  |=  [a=@ b=@]
+  ::  the maximum
+  ^-  @
+  ?:  (gth a b)  a
+  b
+::
+++  min
+  ~/  %min
+  ::    unsigned minimum
+  |=  [a=@ b=@]
+  ::  the minimum
+  ^-  @
+  ?:  (lth a b)  a
+  b
+::
+++  mod
+  ~/  %mod
+  ::    unsigned modulus
+  ::
+  ::  a: dividend
+  ::  b: divisor
+  |:  [a=`@`1 b=`@`1]
+  ::  the remainder
+  ^-  @
+  +:(dvr a b)
+::
+++  mul
+  ~/  %mul
+  ::    unsigned multiplication
+  ::
+  ::  a: multiplicand
+  ::  b: multiplier
+  |:  [a=`@`1 b=`@`1]
+  ::  product
+  ^-  @
+  =+  c=0
+  |-
+  ?:  =(0 a)  c
+  $(a (dec a), c (add b c))
+::
+++  sub
+  ~/  %sub
+  ::    unsigned subtraction
+  ::
+  ::  a: minuend
+  ::  b: subtrahend
+  |=  [a=@ b=@]
+  ~_  leaf+"subtract-underflow"
+  ::  difference
+  ^-  @
+  ?:  =(0 b)  a
+  $(a (dec a), b (dec b))
+::
+::    tree addressing
++|  %tree
+++  cap
+  ~/  %cap
+  ::    tree head
+  ::
+  ::  tests whether an `a` is in the head or tail of a noun. produces %2 if it
+  ::  is within the head, or %3 if it is within the tail.
+  |=  a=@
+  ^-  ?(%2 %3)
+  ?-  a
+    %2        %2
+    %3        %3
+    ?(%0 %1)  !!
+    *         $(a (div a 2))
+  ==
+::
+++  mas
+  ~/  %mas
+  ::    axis within head/tail
+  ::
+  ::  computes the axis of `a` within either the head or tail of a noun
+  ::  (depends whether `a` lies within the the head or tail).
+  |=  a=@
+  ^-  @
+  ?-  a
+    ?(%2 %3)  1
+    ?(%0 %1)  !!
+    *         (add (mod a 2) (mul $(a (div a 2)) 2))
+  ==
+::
+++  peg
+  ~/  %peg
+  ::    axis within axis
+  ::
+  ::  computes the axis of {b} within axis {a}.
+  |=  [a=@ b=@]
+  ?<  =(0 a)
+  ?<  =(0 b)
+  ::  a composed axis
+  ^-  @
+  ?-  b
+    %1  a
+    %2  (mul a 2)
+    %3  +((mul a 2))
+    *   (add (mod b 2) (mul $(b (div b 2)) 2))
+  ==
+::
+::  #  %containers
+::
+::    the most basic of data types
++|  %containers
+::
++$  bite
+  ::    atom slice specifier
+  ::
+  $@(bloq [=bloq =step])
+::
++$  bloq
+  ::    blocksize
+  ::
+  ::  a blocksize is the power of 2 size of an atom. ie, 3 is a byte as 2^3 is
+  ::  8 bits.
+  @
+::
+++  each
+  |$  [this that]
+  ::    either {a} or {b}, defaulting to {a}.
+  ::
+  ::  mold generator: produces a discriminated fork between two types,
+  ::  defaulting to {a}.
+  ::
+  $%  [%| p=that]
+      [%& p=this]
+  ==
+::
++$  gate
+  ::    function
+  ::
+  ::  a core with one arm, `$`--the empty name--which transforms a sample noun
+  ::  into a product noun. If used dryly as a type, the subject must have a
+  ::  sample type of `*`.
+  $-(* *)
+::
+++  list
+  |$  [item]
+  ::    null-terminated list
+  ::
+  ::  mold generator: produces a mold of a null-terminated list of the
+  ::  homogeneous type {a}.
+  ::
+  $@(~ [i=item t=(list item)])
+::
+++  lone
+  |$  [item]
+  ::    single item tuple
+  ::
+  ::  mold generator: puts the face of `p` on the passed in mold.
+  ::
+  p=item
+::
+++  lest
+  |$  [item]
+  ::    null-terminated non-empty list
+  ::
+  ::  mold generator: produces a mold of a null-terminated list of the
+  ::  homogeneous type {a} with at least one element.
+  [i=item t=(list item)]
+::
++$  mold
+  ::    normalizing gate
+  ::
+  ::  a gate that accepts any noun, and validates its shape, producing the
+  ::  input if it fits or a default value if it doesn't.
+  ::
+  ::  examples: * @ud ,[p=time q=?(%a %b)]
+  $~(* $-(* *))
+::
+++  pair
+  |$  [head tail]
+  ::    dual tuple
+  ::
+  ::  mold generator: produces a tuple of the two types passed in.
+  ::
+  ::  a: first type, labeled {p}
+  ::  b: second type, labeled {q}
+  ::
+  [p=head q=tail]
+::
+++  pole
+  |$  [item]
+  ::    faceless list
+  ::
+  ::  like ++list, but without the faces {i} and {t}.
+  ::
+  $@(~ [item (pole item)])
+::
+++  qual
+  |$  [first second third fourth]
+  ::    quadruple tuple
+  ::
+  ::  mold generator: produces a tuple of the four types passed in.
+  ::
+  [p=first q=second r=third s=fourth]
+::
+++  quip
+  |$  [item state]
+  ::    pair of list of first and second
+  ::
+  ::  a common pattern in hoon code is to return a ++list of changes, along with
+  ::  a new state.
+  ::
+  ::  a: type of list item
+  ::  b: type of returned state
+  ::
+  [(list item) state]
+::
+++  step
+  ::    atom size or offset, in bloqs
+  ::
+  _`@u`1
+::
+++  trap
+  |$  [product]
+  ::    a core with one arm `$`
+  ::
+  _|?($:product)
+::
+++  tree
+  |$  [node]
+  ::    tree mold generator
+  ::
+  ::  a `++tree` can be empty, or contain a node of a type and
+  ::  left/right sub `++tree` of the same type. pretty-printed with `{}`.
+  ::
+  $@(~ [n=node l=(tree node) r=(tree node)])
+::
+++  trel
+  |$  [first second third]
+  ::    triple tuple
+  ::
+  ::  mold generator: produces a tuple of the three types passed in.
+  ::
+  [p=first q=second r=third]
+::
+++  unit
+  |$  [item]
+  ::    maybe
+  ::
+  ::  mold generator: either `~` or `[~ u=a]` where `a` is the
+  ::  type that was passed in.
+  ::
+  $@(~ [~ u=item])
+--  =>
+::
+~%  %two  +  ~
+::    layer-2
+::
+|%
+::    2a: unit logic
++|  %unit-logc
+::
+++  biff                                                ::  apply
+  |*  [a=(unit) b=$-(* (unit))]
+  ?~  a  ~
+  (b u.a)
+::
+++  bind                                                ::  argue
+  |*  [a=(unit) b=gate]
+  ?~  a  ~
+  [~ u=(b u.a)]
+::
+++  bond                                                ::  replace
+  |*  a=(trap)
+  |*  b=(unit)
+  ?~  b  $:a
+  u.b
+::
+++  both                                                ::  all the above
+  |*  [a=(unit) b=(unit)]
+  ?~  a  ~
+  ?~  b  ~
+  [~ u=[u.a u.b]]
+::
+++  clap                                                ::  combine
+  |*  [a=(unit) b=(unit) c=_=>(~ |=(^ +<-))]
+  ?~  a  b
+  ?~  b  a
+  [~ u=(c u.a u.b)]
+::
+++  clef                                                ::  compose
+  |*  [a=(unit) b=(unit) c=_=>(~ |=(^ `+<-))]
+  ?~  a  ~
+  ?~  b  ~
+  (c u.a u.b)
+::
+++  drop                                                ::  enlist
+  |*  a=(unit)
+  ?~  a  ~
+  [i=u.a t=~]
+::
+++  fall                                                ::  default
+  |*  [a=(unit) b=*]
+  ?~(a b u.a)
+::
+++  flit                                                ::  make filter
+  |*  a=$-(* ?)
+  |*  b=*
+  ?.((a b) ~ [~ u=b])
+::
+++  hunt                                                ::  first of units
+  |*  [ord=$-(^ ?) a=(unit) b=(unit)]
+  ^-  %-  unit
+      $?  _?>(?=(^ a) u.a)
+          _?>(?=(^ b) u.b)
+      ==
+  ?~  a  b
+  ?~  b  a
+  ?:((ord u.a u.b) a b)
+::
+++  lift                                                ::  lift mold (fmap)
+  |*  a=mold                                            ::  flipped
+  |*  b=(unit)                                          ::  curried
+  (bind b a)                                            ::  bind
+::
+++  mate                                                ::  choose
+  |*  [a=(unit) b=(unit)]
+  ?~  b  a
+  ?~  a  b
+  ?.(=(u.a u.b) ~>(%mean.'mate' !!) a)
+::
+++  need                                                ::  demand
+  ~/  %need
+  |*  a=(unit)
+  ?~  a  ~>(%mean.'need' !!)
+  u.a
+::
+++  some                                                ::  lift (pure)
+  |*  a=*
+  [~ u=a]
+::
+::    2b: list logic
++|  %list-logic
+::  +snoc: append an element to the end of a list
+::
+++  snoc
+  |*  [a=(list) b=*]
+  (weld a ^+(a [b]~))
+::
+::  +lure: List pURE
+++  lure
+  |*  a=*
+  [i=a t=~]
+::
+++  fand                                                ::  all indices
+  ~/  %fand
+  |=  [nedl=(list) hstk=(list)]
+  =|  i=@ud
+  =|  fnd=(list @ud)
+  |-  ^+  fnd
+  =+  [n=nedl h=hstk]
+  |-
+  ?:  |(?=(~ n) ?=(~ h))
+    (flop fnd)
+  ?:  =(i.n i.h)
+    ?~  t.n
+      ^$(i +(i), hstk +.hstk, fnd [i fnd])
+    $(n t.n, h t.h)
+  ^$(i +(i), hstk +.hstk)
+::
+++  find                                                ::  first index
+  ~/  %find
+  |=  [nedl=(list) hstk=(list)]
+  =|  i=@ud
+  |-   ^-  (unit @ud)
+  =+  [n=nedl h=hstk]
+  |-
+  ?:  |(?=(~ n) ?=(~ h))
+     ~
+  ?:  =(i.n i.h)
+    ?~  t.n
+      `i
+    $(n t.n, h t.h)
+  ^$(i +(i), hstk +.hstk)
+::
+++  flop                                                ::  reverse
+  ~/  %flop
+  |*  a=(list)
+  =>  .(a (homo a))
+  ^+  a
+  =+  b=`_a`~
+  |-
+  ?~  a  b
+  $(a t.a, b [i.a b])
+::
+++  gulf                                                ::  range inclusive
+  |=  [a=@ b=@]
+  ?>  (lte a b)
+  |-  ^-  (list @)
+  ?:(=(a +(b)) ~ [a $(a +(a))])
+::
+++  homo                                                ::  homogenize
+  |*  a=(list)
+  ^+  =<  $
+    |@  ++  $  ?:(*? ~ [i=(snag 0 a) t=$])
+    --
+  a
+::  +join: construct a new list, placing .sep between every pair in .lit
+::
+++  join
+  |*  [sep=* lit=(list)]
+  =.  sep  `_?>(?=(^ lit) i.lit)`sep
+  ?~  lit  ~
+  =|  out=(list _?>(?=(^ lit) i.lit))
+  |-  ^+  out
+  ?~  t.lit
+    (flop [i.lit out])
+  $(out [sep i.lit out], lit t.lit)
+::
+::  +bake: convert wet gate to dry gate by specifying argument mold
+::
+++  bake
+  |*  [f=gate a=mold]
+  |=  arg=a
+  (f arg)
+::
+++  lent                                                ::  length
+  ~/  %lent
+  |=  a=(list)
+  ^-  @
+  =+  b=0
+  |-
+  ?~  a  b
+  $(a t.a, b +(b))
+::
+++  levy
+  ~/  %levy                                             ::  all of
+  |*  [a=(list) b=$-(* ?)]
+  |-  ^-  ?
+  ?~  a  &
+  ?.  (b i.a)  |
+  $(a t.a)
+::
+++  lien                                                ::  some of
+  ~/  %lien
+  |*  [a=(list) b=$-(* ?)]
+  |-  ^-  ?
+  ?~  a  |
+  ?:  (b i.a)  &
+  $(a t.a)
+::
+++  limo                                                ::  listify
+  |*  a=*
+  ^+  =<  $
+    |@  ++  $  ?~(a ~ ?:(*? [i=-.a t=$] $(a +.a)))
+    --
+  a
+::
+++  murn                                                ::  maybe transform
+  ~/  %murn
+  |*  [a=(list) b=$-(* (unit))]
+  =>  .(a (homo a))
+  |-  ^-  (list _?>(?=(^ a) (need (b i.a))))
+  ?~  a  ~
+  =/  c  (b i.a)
+  ?~  c  $(a t.a)
+  [+.c $(a t.a)]
+::
+++  oust                                                ::  remove
+  ~/  %oust
+  |*  [[a=@ b=@] c=(list)]
+  (weld (scag +<-< c) (slag (add +<-< +<->) c))
+::
+++  reap                                                ::  replicate
+  ~/  %reap
+  |*  [a=@ b=*]
+  |-  ^-  (list _b)
+  ?~  a  ~
+  [b $(a (dec a))]
+::
+++  rear                                                ::  last item of list
+  ~/  %rear
+  |*  a=(list)
+  ^-  _?>(?=(^ a) i.a)
+  ?>  ?=(^ a)
+  ?:  =(~ t.a)  i.a  ::NOTE  avoiding tmi
+  $(a t.a)
+::
+++  reel                                                ::  right fold
+  ~/  %reel
+  |*  [a=(list) b=_=>(~ |=([* *] +<+))]
+  |-  ^+  ,.+<+.b
+  ?~  a
+    +<+.b
+  (b i.a $(a t.a))
+::
+++  roll                                                ::  left fold
+  ~/  %roll
+  |*  [a=(list) b=_=>(~ |=([* *] +<+))]
+  |-  ^+  ,.+<+.b
+  ?~  a
+    +<+.b
+  $(a t.a, b b(+<+ (b i.a +<+.b)))
+::
+++  scag                                                ::  prefix
+  ~/  %scag
+  |*  [a=@ b=(list)]
+  |-  ^+  b
+  ?:  |(?=(~ b) =(0 a))  ~
+  [i.b $(b t.b, a (dec a))]
+::
+++  skid                                                ::  separate
+  ~/  %skid
+  |*  [a=(list) b=$-(* ?)]
+  |-  ^+  [p=a q=a]
+  ?~  a  [~ ~]
+  =+  c=$(a t.a)
+  ?:((b i.a) [[i.a p.c] q.c] [p.c [i.a q.c]])
+::
+++  skim                                                ::  only
+  ~/  %skim
+  |*  [a=(list) b=$-(* ?)]
+  |-
+  ^+  a
+  ?~  a  ~
+  ?:((b i.a) [i.a $(a t.a)] $(a t.a))
+::
+++  skip                                                ::  except
+  ~/  %skip
+  |*  [a=(list) b=$-(* ?)]
+  |-
+  ^+  a
+  ?~  a  ~
+  ?:((b i.a) $(a t.a) [i.a $(a t.a)])
+::
+++  slag                                                ::  suffix
+  ~/  %slag
+  |*  [a=@ b=(list)]
+  |-  ^+  b
+  ?:  =(0 a)  b
+  ?~  b  ~
+  $(b t.b, a (dec a))
+::
+++  snag                                                ::  index
+  ~/  %snag
+  |*  [a=@ b=(list)]
+  |-  ^+  ?>(?=(^ b) i.b)
+  ?~  b
+    ~_  leaf+"snag-fail"
+    !!
+  ?:  =(0 a)  i.b
+  $(b t.b, a (dec a))
+::
+++  snip                                                ::  drop tail off list
+  ~/  %snip
+  |*  a=(list)
+  ^+  a
+  ?~  a  ~
+  ?:  =(~ t.a)  ~
+  [i.a $(a t.a)]
+::
+++  sort  !.                                            ::  quicksort
+  ~/  %sort
+  |*  [a=(list) b=$-([* *] ?)]
+  =>  .(a ^.(homo a))
+  |-  ^+  a
+  ?~  a  ~
+  =+  s=(skid t.a |:(c=i.a (b c i.a)))
+  %+  weld
+    $(a p.s)
+  ^+  t.a
+  [i.a $(a q.s)]
+::
+++  spin                                                ::  stateful turn
+  ::
+  ::  a: list
+  ::  b: state
+  ::  c: gate from list-item and state to product and new state
+  ~/  %spin
+  |*  [a=(list) b=* c=_|=(^ [** +<+])]
+  =>  .(c `$-([_?>(?=(^ a) i.a) _b] [_-:(c) _b])`c)
+  =/  acc=(list _-:(c))  ~
+  ::  transformed list and updated state
+  |-  ^-  (pair _acc _b)
+  ?~  a
+    [(flop acc) b]
+  =^  res  b  (c i.a b)
+  $(acc [res acc], a t.a)
+::
+++  spun                                                ::  internal spin
+  ::
+  ::  a: list
+  ::  b: gate from list-item and state to product and new state
+  ~/  %spun
+  |*  [a=(list) b=_|=(^ [** +<+])]
+  ::  transformed list
+  p:(spin a +<+.b b)
+::
+++  swag                                                ::  slice
+  |*  [[a=@ b=@] c=(list)]
+  (scag +<-> (slag +<-< c))
+::  +turn: transform each value of list :a using the function :b
+::
+++  turn
+  ~/  %turn
+  |*  [a=(list) b=gate]
+  =>  .(a (homo a))
+  ^-  (list _?>(?=(^ a) (b i.a)))
+  |-
+  ?~  a  ~
+  [i=(b i.a) t=$(a t.a)]
+::
+++  weld                                                ::  concatenate
+  ~/  %weld
+  |*  [a=(list) b=(list)]
+  =>  .(a ^.(homo a), b ^.(homo b))
+  |-  ^+  b
+  ?~  a  b
+  [i.a $(a t.a)]
+::
+++  snap                                               ::  replace item
+  ~/  %snap
+  |*  [a=(list) b=@ c=*]
+  ^+  a
+  (weld (scag b a) [c (slag +(b) a)])
+::
+++  into                                               ::  insert item
+  ~/  %into
+  |*  [a=(list) b=@ c=*]
+  ^+  a
+  (weld (scag b a) [c (slag b a)])
+::
+++  welp                                                ::  faceless weld
+  ~/  %welp
+  |*  [* *]
+  ?~  +<-
+    +<-(. +<+)
+  +<-(+ $(+<- +<->))
+::
+++  zing                                                ::  promote
+  ~/  %zing
+  |*  *
+  ?~  +<
+    +<
+  (welp +<- $(+< +<+))
+::
+::    2c: bit arithmetic
++|  %bit-arithmetic
+::
+++  bex                                                 ::  binary exponent
+  ~/  %bex
+  |=  a=bloq
+  ^-  @
+  ?:  =(0 a)  1
+  (mul 2 $(a (dec a)))
+::
+++  can                                                 ::  assemble
+  ~/  %can
+  |=  [a=bloq b=(list [p=step q=@])]
+  ^-  @
+  ?~  b  0
+  (add (end [a p.i.b] q.i.b) (lsh [a p.i.b] $(b t.b)))
+::
+++  cat                                                 ::  concatenate
+  ~/  %cat
+  |=  [a=bloq b=@ c=@]
+  (add (lsh [a (met a b)] c) b)
+::
+++  cut                                                 ::  slice
+  ~/  %cut
+  |=  [a=bloq [b=step c=step] d=@]
+  (end [a c] (rsh [a b] d))
+::
+++  end                                                 ::  tail
+  ~/  %end
+  |=  [a=bite b=@]
+  =/  [=bloq =step]  ?^(a a [a *step])
+  (mod b (bex (mul (bex bloq) step)))
+::
+++  fil                                                 ::  fill bloqstream
+  ~/  %fil
+  |=  [a=bloq b=step c=@]
+  =|  n=@ud
+  =.  c  (end a c)
+  =/  d  c
+  |-  ^-  @
+  ?:  =(n b)
+    (rsh a d)
+  $(d (add c (lsh a d)), n +(n))
+::
+++  lsh                                                 ::  left-shift
+  ~/  %lsh
+  |=  [a=bite b=@]
+  =/  [=bloq =step]  ?^(a a [a *step])
+  (mul b (bex (mul (bex bloq) step)))
+::
+++  met                                                 ::  measure
+  ~/  %met
+  |=  [a=bloq b=@]
+  ^-  @
+  =+  c=0
+  |-
+  ?:  =(0 b)  c
+  $(b (rsh a b), c +(c))
+::
+++  rap                                                 ::  assemble variable
+  ~/  %rap
+  |=  [a=bloq b=(list @)]
+  ^-  @
+  ?~  b  0
+  (cat a i.b $(b t.b))
+::
+++  rep                                                 ::  assemble fixed
+  ~/  %rep
+  |=  [a=bite b=(list @)]
+  =/  [=bloq =step]  ?^(a a [a *step])
+  =|  i=@ud
+  |-  ^-  @
+  ?~  b   0
+  %+  add  $(i +(i), b t.b)
+  (lsh [bloq (mul step i)] (end [bloq step] i.b))
+::
+++  rev
+  ::    reverses block order, accounting for leading zeroes
+  ::
+  ::  boz: block size
+  ::  len: size of dat, in boz
+  ::  dat: data to flip
+  ~/  %rev
+  |=  [boz=bloq len=@ud dat=@]
+  ^-  @
+  =.  dat  (end [boz len] dat)
+  %+  lsh
+    [boz (sub len (met boz dat))]
+  (swp boz dat)
+::
+++  rip                                                 ::  disassemble
+  ~/  %rip
+  |=  [a=bite b=@]
+  ^-  (list @)
+  ?:  =(0 b)  ~
+  [(end a b) $(b (rsh a b))]
+::
+++  rsh                                                 ::  right-shift
+  ~/  %rsh
+  |=  [a=bite b=@]
+  =/  [=bloq =step]  ?^(a a [a *step])
+  (div b (bex (mul (bex bloq) step)))
+::
+++  run                                                 ::  +turn into atom
+  ~/  %run
+  |=  [a=bite b=@ c=$-(@ @)]
+  (rep a (turn (rip a b) c))
+::
+++  rut                                                 ::  +turn into list
+  ~/  %rut
+  |*  [a=bite b=@ c=$-(@ *)]
+  (turn (rip a b) c)
+::
+++  sew                                                 ::  stitch into
+  ~/  %sew
+  |=  [a=bloq [b=step c=step d=@] e=@]
+  ^-  @
+  %+  add
+    (can a b^e c^d ~)
+  =/  f  [a (add b c)]
+  (lsh f (rsh f e))
+::
+++  swp                                                 ::  naive rev bloq order
+  ~/  %swp
+  |=  [a=bloq b=@]
+  (rep a (flop (rip a b)))
+::
+++  xeb                                                 ::  binary logarithm
+  ~/  %xeb
+  |=  a=@
+  ^-  @
+  (met 0 a)
+::
+++  fe                                                  ::  modulo bloq
+  |_  a=bloq
+  ++  dif                                               ::  difference
+    |=([b=@ c=@] (sit (sub (add out (sit b)) (sit c))))
+  ++  inv  |=(b=@ (sub (dec out) (sit b)))              ::  inverse
+  ++  net  |=  b=@  ^-  @                               ::  flip byte endianness
+           =>  .(b (sit b))
+           ?:  (lte a 3)
+             b
+           =+  c=(dec a)
+           %+  con
+             (lsh c $(a c, b (cut c [0 1] b)))
+           $(a c, b (cut c [1 1] b))
+  ++  out  (bex (bex a))                                ::  mod value
+  ++  rol  |=  [b=bloq c=@ d=@]  ^-  @                  ::  roll left
+           =+  e=(sit d)
+           =+  f=(bex (sub a b))
+           =+  g=(mod c f)
+           (sit (con (lsh [b g] e) (rsh [b (sub f g)] e)))
+  ++  ror  |=  [b=bloq c=@ d=@]  ^-  @                  ::  roll right
+           =+  e=(sit d)
+           =+  f=(bex (sub a b))
+           =+  g=(mod c f)
+           (sit (con (rsh [b g] e) (lsh [b (sub f g)] e)))
+  ++  sum  |=([b=@ c=@] (sit (add b c)))                ::  wrapping add
+  ++  sit  |=(b=@ (end a b))                            ::  enforce modulo
+  --
+::
+::    2d: bit logic
++|  %bit-logic
+::
+++  con                                                 ::  binary or
+  ~/  %con
+  |=  [a=@ b=@]
+  =+  [c=0 d=0]
+  |-  ^-  @
+  ?:  ?&(=(0 a) =(0 b))  d
+  %=  $
+    a   (rsh 0 a)
+    b   (rsh 0 b)
+    c   +(c)
+    d   %+  add  d
+          %+  lsh  [0 c]
+          ?&  =(0 (end 0 a))
+              =(0 (end 0 b))
+          ==
+  ==
+::
+++  dis                                                 ::  binary and
+  ~/  %dis
+  |=  [a=@ b=@]
+  =|  [c=@ d=@]
+  |-  ^-  @
+  ?:  ?|(=(0 a) =(0 b))  d
+  %=  $
+    a   (rsh 0 a)
+    b   (rsh 0 b)
+    c   +(c)
+    d   %+  add  d
+          %+  lsh  [0 c]
+          ?|  =(0 (end 0 a))
+              =(0 (end 0 b))
+          ==
+  ==
+::
+++  mix                                                 ::  binary xor
+  ~/  %mix
+  |=  [a=@ b=@]
+  ^-  @
+  =+  [c=0 d=0]
+  |-
+  ?:  ?&(=(0 a) =(0 b))  d
+  %=  $
+    a   (rsh 0 a)
+    b   (rsh 0 b)
+    c   +(c)
+    d   (add d (lsh [0 c] =((end 0 a) (end 0 b))))
+  ==
+::
+++  not  |=  [a=bloq b=@ c=@]                           ::  binary not (sized)
+  (mix c (dec (bex (mul b (bex a)))))
+::
+::    2e: insecure hashing
++|  %insecure-hashing
+::
+++  muk                                                 ::  standard murmur3
+  ~%  %muk  ..muk  ~
+  =+  ~(. fe 5)
+  |=  [syd=@ len=@ key=@]
+  =.  syd      (end 5 syd)
+  =/  pad      (sub len (met 3 key))
+  =/  data     (weld (rip 3 key) (reap pad 0))
+  =/  nblocks  (div len 4)  ::  intentionally off-by-one
+  =/  h1  syd
+  =+  [c1=0xcc9e.2d51 c2=0x1b87.3593]
+  =/  blocks  (rip 5 key)
+  =/  i  nblocks
+  =.  h1  =/  hi  h1  |-
+    ?:  =(0 i)  hi
+    =/  k1  (snag (sub nblocks i) blocks)  ::  negative array index
+    =.  k1  (sit (mul k1 c1))
+    =.  k1  (rol 0 15 k1)
+    =.  k1  (sit (mul k1 c2))
+    =.  hi  (mix hi k1)
+    =.  hi  (rol 0 13 hi)
+    =.  hi  (sum (sit (mul hi 5)) 0xe654.6b64)
+    $(i (dec i))
+  =/  tail  (slag (mul 4 nblocks) data)
+  =/  k1    0
+  =/  tlen  (dis len 3)
+  =.  h1
+    ?+  tlen  h1  ::  fallthrough switch
+      %3  =.  k1  (mix k1 (lsh [0 16] (snag 2 tail)))
+          =.  k1  (mix k1 (lsh [0 8] (snag 1 tail)))
+          =.  k1  (mix k1 (snag 0 tail))
+          =.  k1  (sit (mul k1 c1))
+          =.  k1  (rol 0 15 k1)
+          =.  k1  (sit (mul k1 c2))
+          (mix h1 k1)
+      %2  =.  k1  (mix k1 (lsh [0 8] (snag 1 tail)))
+          =.  k1  (mix k1 (snag 0 tail))
+          =.  k1  (sit (mul k1 c1))
+          =.  k1  (rol 0 15 k1)
+          =.  k1  (sit (mul k1 c2))
+          (mix h1 k1)
+      %1  =.  k1  (mix k1 (snag 0 tail))
+          =.  k1  (sit (mul k1 c1))
+          =.  k1  (rol 0 15 k1)
+          =.  k1  (sit (mul k1 c2))
+          (mix h1 k1)
+    ==
+  =.  h1  (mix h1 len)
+  |^  (fmix32 h1)
+  ++  fmix32
+    |=  h=@
+    =.  h  (mix h (rsh [0 16] h))
+    =.  h  (sit (mul h 0x85eb.ca6b))
+    =.  h  (mix h (rsh [0 13] h))
+    =.  h  (sit (mul h 0xc2b2.ae35))
+    =.  h  (mix h (rsh [0 16] h))
+    h
+  --
+::
+++  mug                                                 ::  mug with murmur3
+  ~/  %mug
+  |=  a=*
+  |^  ?@  a  (mum 0xcafe.babe 0x7fff a)
+      =/  b  (cat 5 $(a -.a) $(a +.a))
+      (mum 0xdead.beef 0xfffe b)
+  ::
+  ++  mum
+    |=  [syd=@uxF fal=@F key=@]
+    =/  wyd  (met 3 key)
+    =|  i=@ud
+    |-  ^-  @F
+    ?:  =(8 i)  fal
+    =/  haz=@F  (muk syd wyd key)
+    =/  ham=@F  (mix (rsh [0 31] haz) (end [0 31] haz))
+    ?.(=(0 ham) ham $(i +(i), syd +(syd)))
+  --
+::                                                      ::
+::    2f: noun ordering
++|  %noun-ordering
+::
+::  +aor: alphabetical order
+::
+::    Orders atoms before cells, and atoms in ascending LSB order.
+::
+++  aor
+  ~/  %aor
+  |=  [a=* b=*]
+  ^-  ?
+  ?:  =(a b)  &
+  ?.  ?=(@ a)
+    ?:  ?=(@ b)  |
+    ?:  =(-.a -.b)
+      $(a +.a, b +.b)
+    $(a -.a, b -.b)
+  ?.  ?=(@ b)  &
+  |-
+  =+  [c=(end 3 a) d=(end 3 b)]
+  ?:  =(c d)
+    $(a (rsh 3 a), b (rsh 3 b))
+  (lth c d)
+::  +dor: depth order
+::
+::    Orders in ascending tree depth.
+::
+++  dor
+  ~/  %dor
+  |=  [a=* b=*]
+  ^-  ?
+  ?:  =(a b)  &
+  ?.  ?=(@ a)
+    ?:  ?=(@ b)  |
+    ?:  =(-.a -.b)
+      $(a +.a, b +.b)
+    $(a -.a, b -.b)
+  ?.  ?=(@ b)  &
+  (lth a b)
+::  +gor: mug order
+::
+::    Orders in ascending +mug hash order, collisions fall back to +dor.
+::
+++  gor
+  ~/  %gor
+  |=  [a=* b=*]
+  ^-  ?
+  =+  [c=(mug a) d=(mug b)]
+  ?:  =(c d)
+    (dor a b)
+  (lth c d)
+::  +mor: (more) mug order
+::
+::    Orders in ascending double +mug hash order, collisions fall back to +dor.
+::
+++  mor
+  ~/  %mor
+  |=  [a=* b=*]
+  ^-  ?
+  =+  [c=(mug (mug a)) d=(mug (mug b))]
+  ?:  =(c d)
+    (dor a b)
+  (lth c d)
+::
+::    2g: unsigned powers
++|  %unsigned-powers
+::
+++  pow                                                 ::  unsigned exponent
+  ~/  %pow
+  |=  [a=@ b=@]
+  ?:  =(b 0)  1
+  |-  ?:  =(b 1)  a
+  =+  c=$(b (div b 2))
+  =+  d=(mul c c)
+  ?~  (dis b 1)  d  (mul d a)
+::
+++  sqt                                                 ::  unsigned sqrt/rem
+  ~/  %sqt
+  |=  a=@  ^-  [p=@ q=@]
+  ?~  a  [0 0]
+  =+  [q=(div (dec (xeb a)) 2) r=0]
+  =-  [-.b (sub a +.b)]
+  ^=  b  |-
+  =+  s=(add r (bex q))
+  =+  t=(mul s s)
+  ?:  =(q 0)
+    ?:((lte t a) [s t] [r (mul r r)])
+  ?:  (lte t a)
+    $(r s, q (dec q))
+  $(q (dec q))
+::
+::    2h: set logic
++|  %set-logic
+::
+++  in                                                  ::  set engine
+  ~/  %in
+  =|  a=(tree)  :: (set)
+  |@
+  ++  all                                               ::  logical AND
+    ~/  %all
+    |*  b=$-(* ?)
+    |-  ^-  ?
+    ?~  a
+      &
+    ?&((b n.a) $(a l.a) $(a r.a))
+  ::
+  ++  any                                               ::  logical OR
+    ~/  %any
+    |*  b=$-(* ?)
+    |-  ^-  ?
+    ?~  a
+      |
+    ?|((b n.a) $(a l.a) $(a r.a))
+  ::
+  ++  apt                                               ::  check correctness
+    =<  $
+    ~/  %apt
+    =|  [l=(unit) r=(unit)]
+    |.  ^-  ?
+    ?~  a   &
+    ?&  ?~(l & &((gor n.a u.l) !=(n.a u.l)))
+        ?~(r & &((gor u.r n.a) !=(u.r n.a)))
+        ?~(l.a & ?&((mor n.a n.l.a) !=(n.a n.l.a) $(a l.a, l `n.a)))
+        ?~(r.a & ?&((mor n.a n.r.a) !=(n.a n.r.a) $(a r.a, r `n.a)))
+    ==
+  ::
+  ++  bif                                               ::  splits a by b
+    ~/  %bif
+    |*  b=*
+    ^+  [l=a r=a]
+    =<  +
+    |-  ^+  a
+    ?~  a
+      [b ~ ~]
+    ?:  =(b n.a)
+      a
+    ?:  (gor b n.a)
+      =+  c=$(a l.a)
+      ?>  ?=(^ c)
+      c(r a(l r.c))
+    =+  c=$(a r.a)
+    ?>  ?=(^ c)
+    c(l a(r l.c))
+  ::
+  ++  del                                               ::  b without any a
+    ~/  %del
+    |*  b=*
+    |-  ^+  a
+    ?~  a
+      ~
+    ?.  =(b n.a)
+      ?:  (gor b n.a)
+        a(l $(a l.a))
+      a(r $(a r.a))
+    |-  ^-  [$?(~ _a)]
+    ?~  l.a  r.a
+    ?~  r.a  l.a
+    ?:  (mor n.l.a n.r.a)
+      l.a(r $(l.a r.l.a))
+    r.a(l $(r.a l.r.a))
+  ::
+  ++  dif                                               ::  difference
+    ~/  %dif
+    |*  b=_a
+    |-  ^+  a
+    ?~  b
+      a
+    =+  c=(bif n.b)
+    ?>  ?=(^ c)
+    =+  d=$(a l.c, b l.b)
+    =+  e=$(a r.c, b r.b)
+    |-  ^-  [$?(~ _a)]
+    ?~  d  e
+    ?~  e  d
+    ?:  (mor n.d n.e)
+      d(r $(d r.d))
+    e(l $(e l.e))
+  ::
+  ++  dig                                               ::  axis of a in b
+    |=  b=*
+    =+  c=1
+    |-  ^-  (unit @)
+    ?~  a  ~
+    ?:  =(b n.a)  [~ u=(peg c 2)]
+    ?:  (gor b n.a)
+      $(a l.a, c (peg c 6))
+    $(a r.a, c (peg c 7))
+  ::
+  ++  gas                                               ::  concatenate
+    ~/  %gas
+    |=  b=(list _?>(?=(^ a) n.a))
+    |-  ^+  a
+    ?~  b
+      a
+    $(b t.b, a (put i.b))
+  ::  +has: does :b exist in :a?
+  ::
+  ++  has
+    ~/  %has
+    |*  b=*
+    ^-  ?
+    ::    wrap extracted item type in a unit because bunting fails
+    ::
+    ::  If we used the real item type of _?^(a n.a !!) as the sample type,
+    ::  then hoon would bunt it to create the default sample for the gate.
+    ::
+    ::  However, bunting that expression fails if :a is ~. If we wrap it
+    ::  in a unit, the bunted unit doesn't include the bunted item type.
+    ::
+    ::  This way we can ensure type safety of :b without needing to perform
+    ::  this failing bunt. It's a hack.
+    ::
+    %.  [~ b]
+    |=  b=(unit _?>(?=(^ a) n.a))
+    =>  .(b ?>(?=(^ b) u.b))
+    |-  ^-  ?
+    ?~  a
+      |
+    ?:  =(b n.a)
+      &
+    ?:  (gor b n.a)
+      $(a l.a)
+    $(a r.a)
+  ::
+  ++  int                                               ::  intersection
+    ~/  %int
+    |*  b=_a
+    |-  ^+  a
+    ?~  b
+      ~
+    ?~  a
+      ~
+    ?.  (mor n.a n.b)
+      $(a b, b a)
+    ?:  =(n.b n.a)
+      a(l $(a l.a, b l.b), r $(a r.a, b r.b))
+    ?:  (gor n.b n.a)
+      %-  uni(a $(a l.a, r.b ~))  $(b r.b)
+    %-  uni(a $(a r.a, l.b ~))  $(b l.b)
+  ::
+  ++  put                                               ::  puts b in a, sorted
+    ~/  %put
+    |*  b=*
+    |-  ^+  a
+    ?~  a
+      [b ~ ~]
+    ?:  =(b n.a)
+      a
+    ?:  (gor b n.a)
+      =+  c=$(a l.a)
+      ?>  ?=(^ c)
+      ?:  (mor n.a n.c)
+        a(l c)
+      c(r a(l r.c))
+    =+  c=$(a r.a)
+    ?>  ?=(^ c)
+    ?:  (mor n.a n.c)
+      a(r c)
+    c(l a(r l.c))
+  ::
+  ++  rep                                               ::  reduce to product
+    ~/  %rep
+    |*  b=_=>(~ |=([* *] +<+))
+    |-
+    ?~  a  +<+.b
+    $(a r.a, +<+.b $(a l.a, +<+.b (b n.a +<+.b)))
+  ::
+  ++  run                                               ::  apply gate to values
+    ~/  %run
+    |*  b=gate
+    =+  c=`(set _?>(?=(^ a) (b n.a)))`~
+    |-  ?~  a  c
+    =.  c  (~(put in c) (b n.a))
+    =.  c  $(a l.a, c c)
+    $(a r.a, c c)
+  ::
+  ++  tap                                               ::  convert to list
+    =<  $
+    ~/  %tap
+    =+  b=`(list _?>(?=(^ a) n.a))`~
+    |.  ^+  b
+    ?~  a
+      b
+    $(a r.a, b [n.a $(a l.a)])
+  ::
+  ++  uni                                               ::  union
+    ~/  %uni
+    |*  b=_a
+    ?:  =(a b)  a
+    |-  ^+  a
+    ?~  b
+      a
+    ?~  a
+      b
+    ?:  =(n.b n.a)
+      b(l $(a l.a, b l.b), r $(a r.a, b r.b))
+    ?:  (mor n.a n.b)
+      ?:  (gor n.b n.a)
+        $(l.a $(a l.a, r.b ~), b r.b)
+      $(r.a $(a r.a, l.b ~), b l.b)
+    ?:  (gor n.a n.b)
+      $(l.b $(b l.b, r.a ~), a r.a)
+    $(r.b $(b r.b, l.a ~), a l.a)
+  ::
+  ++  wyt                                               ::  size of set
+    =<  $
+    ~%  %wyt  +  ~
+    |.  ^-  @
+    ?~(a 0 +((add $(a l.a) $(a r.a))))
+  --
+::
+::    2i: map logic
++|  %map-logic
+::
+++  by                                                  ::  map engine
+  ~/  %by
+  =|  a=(tree (pair))  ::  (map)
+  |@
+  ++  all                                               ::  logical AND
+    ~/  %all
+    |*  b=$-(* ?)
+    |-  ^-  ?
+    ?~  a
+      &
+    ?&((b q.n.a) $(a l.a) $(a r.a))
+  ::
+  ++  any                                               ::  logical OR
+    ~/  %any
+    |*  b=$-(* ?)
+    |-  ^-  ?
+    ?~  a
+      |
+    ?|((b q.n.a) $(a l.a) $(a r.a))
+  ::
+  ++  bif                                               ::  splits a by b
+    ~/  %bif
+    |*  b=*
+    |-  ^+  [l=a r=a]
+    ?~  a
+      [~ ~]
+    ?:  =(b p.n.a)
+      +.a
+    ?:  (gor b p.n.a)
+      =+  d=$(a l.a)
+      ?>  ?=(^ d)
+      [l.d a(l r.d)]
+    =+  d=$(a r.a)
+    ?>  ?=(^ d)
+    [a(r l.d) r.d]
+  ::
+  ++  del                                               ::  delete at key b
+    ~/  %del
+    |*  b=*
+    |-  ^+  a
+    ?~  a
+      ~
+    ?.  =(b p.n.a)
+      ?:  (gor b p.n.a)
+        a(l $(a l.a))
+      a(r $(a r.a))
+    |-  ^-  [$?(~ _a)]
+    ?~  l.a  r.a
+    ?~  r.a  l.a
+    ?:  (mor p.n.l.a p.n.r.a)
+      l.a(r $(l.a r.l.a))
+    r.a(l $(r.a l.r.a))
+  ::
+  ++  dif                                               ::  difference
+    ~/  %dif
+    |*  b=_a
+    |-  ^+  a
+    ?~  b
+      a
+    =+  c=(bif p.n.b)
+    ?>  ?=(^ c)
+    =+  d=$(a l.c, b l.b)
+    =+  e=$(a r.c, b r.b)
+    |-  ^-  [$?(~ _a)]
+    ?~  d  e
+    ?~  e  d
+    ?:  (mor p.n.d p.n.e)
+      d(r $(d r.d))
+    e(l $(e l.e))
+  ::
+  ++  dig                                               ::  axis of b key
+    |=  b=*
+    =+  c=1
+    |-  ^-  (unit @)
+    ?~  a  ~
+    ?:  =(b p.n.a)  [~ u=(peg c 2)]
+    ?:  (gor b p.n.a)
+      $(a l.a, c (peg c 6))
+    $(a r.a, c (peg c 7))
+  ::
+  ++  apt                                               ::  check correctness
+    =<  $
+    ~/  %apt
+    =|  [l=(unit) r=(unit)]
+    |.  ^-  ?
+    ?~  a   &
+    ?&  ?~(l & &((gor p.n.a u.l) !=(p.n.a u.l)))
+        ?~(r & &((gor u.r p.n.a) !=(u.r p.n.a)))
+        ?~  l.a   &
+        &((mor p.n.a p.n.l.a) !=(p.n.a p.n.l.a) $(a l.a, l `p.n.a))
+        ?~  r.a   &
+        &((mor p.n.a p.n.r.a) !=(p.n.a p.n.r.a) $(a r.a, r `p.n.a))
+    ==
+  ::
+  ++  gas                                               ::  concatenate
+    ~/  %gas
+    |*  b=(list [p=* q=*])
+    =>  .(b `(list _?>(?=(^ a) n.a))`b)
+    |-  ^+  a
+    ?~  b
+      a
+    $(b t.b, a (put p.i.b q.i.b))
+  ::
+  ++  get                                               ::  grab value by key
+    ~/  %get
+    |*  b=*
+    =>  .(b `_?>(?=(^ a) p.n.a)`b)
+    |-  ^-  (unit _?>(?=(^ a) q.n.a))
+    ?~  a
+      ~
+    ?:  =(b p.n.a)
+      (some q.n.a)
+    ?:  (gor b p.n.a)
+      $(a l.a)
+    $(a r.a)
+  ::
+  ++  got                                               ::  need value by key
+    |*  b=*
+    (need (get b))
+  ::
+  ++  gut                                               ::  fall value by key
+    |*  [b=* c=*]
+    (fall (get b) c)
+  ::
+  ++  has                                               ::  key existence check
+    ~/  %has
+    |*  b=*
+    !=(~ (get b))
+  ::
+  ++  int                                               ::  intersection
+    ~/  %int
+    |*  b=_a
+    |-  ^+  a
+    ?~  b
+      ~
+    ?~  a
+      ~
+    ?:  (mor p.n.a p.n.b)
+      ?:  =(p.n.b p.n.a)
+        b(l $(a l.a, b l.b), r $(a r.a, b r.b))
+      ?:  (gor p.n.b p.n.a)
+        %-  uni(a $(a l.a, r.b ~))  $(b r.b)
+      %-  uni(a $(a r.a, l.b ~))  $(b l.b)
+    ?:  =(p.n.a p.n.b)
+      b(l $(b l.b, a l.a), r $(b r.b, a r.a))
+    ?:  (gor p.n.a p.n.b)
+      %-  uni(a $(b l.b, r.a ~))  $(a r.a)
+    %-  uni(a $(b r.b, l.a ~))  $(a l.a)
+  ::
+  ++  jab
+    ~/  %jab
+    |*  [key=_?>(?=(^ a) p.n.a) fun=$-(_?>(?=(^ a) q.n.a) _?>(?=(^ a) q.n.a))]
+    ^+  a
+    ::
+    ?~  a  !!
+    ::
+    ?:  =(key p.n.a)
+      a(q.n (fun q.n.a))
+    ::
+    ?:  (gor key p.n.a)
+      a(l $(a l.a))
+    ::
+    a(r $(a r.a))
+  ::
+  ++  mar                                               ::  add with validation
+    |*  [b=* c=(unit *)]
+    ?~  c
+      (del b)
+    (put b u.c)
+  ::
+  ++  put                                               ::  adds key-value pair
+    ~/  %put
+    |*  [b=* c=*]
+    |-  ^+  a
+    ?~  a
+      [[b c] ~ ~]
+    ?:  =(b p.n.a)
+      ?:  =(c q.n.a)
+        a
+      a(n [b c])
+    ?:  (gor b p.n.a)
+      =+  d=$(a l.a)
+      ?>  ?=(^ d)
+      ?:  (mor p.n.a p.n.d)
+        a(l d)
+      d(r a(l r.d))
+    =+  d=$(a r.a)
+    ?>  ?=(^ d)
+    ?:  (mor p.n.a p.n.d)
+      a(r d)
+    d(l a(r l.d))
+  ::
+  ++  rep                                               ::  reduce to product
+    ~/  %rep
+    |*  b=_=>(~ |=([* *] +<+))
+    |-
+    ?~  a  +<+.b
+    $(a r.a, +<+.b $(a l.a, +<+.b (b n.a +<+.b)))
+  ::
+  ++  rib                                               ::  transform + product
+    |*  [b=* c=gate]
+    |-  ^+  [b a]
+    ?~  a  [b ~]
+    =+  d=(c n.a b)
+    =.  n.a  +.d
+    =+  e=$(a l.a, b -.d)
+    =+  f=$(a r.a, b -.e)
+    [-.f a(l +.e, r +.f)]
+  ::
+  ++  run                                               ::  apply gate to values
+    ~/  %run
+    |*  b=gate
+    |-
+    ?~  a  a
+    [n=[p=p.n.a q=(b q.n.a)] l=$(a l.a) r=$(a r.a)]
+  ::
+  ++  tap                                               ::  listify pairs
+    =<  $
+    ~/  %tap
+    =+  b=`(list _?>(?=(^ a) n.a))`~
+    |.  ^+  b
+    ?~  a
+      b
+    $(a r.a, b [n.a $(a l.a)])
+  ::
+  ++  uni                                               ::  union, merge
+    ~/  %uni
+    |*  b=_a
+    |-  ^+  a
+    ?~  b
+      a
+    ?~  a
+      b
+    ?:  =(p.n.b p.n.a)
+      b(l $(a l.a, b l.b), r $(a r.a, b r.b))
+    ?:  (mor p.n.a p.n.b)
+      ?:  (gor p.n.b p.n.a)
+        $(l.a $(a l.a, r.b ~), b r.b)
+      $(r.a $(a r.a, l.b ~), b l.b)
+    ?:  (gor p.n.a p.n.b)
+      $(l.b $(b l.b, r.a ~), a r.a)
+    $(r.b $(b r.b, l.a ~), a l.a)
+  ::
+  ++  uno                                               ::  general union
+    |*  b=_a
+    |*  meg=$-([* * *] *)
+    |-  ^+  a
+    ?~  b
+      a
+    ?~  a
+      b
+    ?:  =(p.n.b p.n.a)
+      :+  [p.n.a `_?>(?=(^ a) q.n.a)`(meg p.n.a q.n.a q.n.b)]
+        $(b l.b, a l.a)
+      $(b r.b, a r.a)
+    ?:  (mor p.n.a p.n.b)
+      ?:  (gor p.n.b p.n.a)
+        $(l.a $(a l.a, r.b ~), b r.b)
+      $(r.a $(a r.a, l.b ~), b l.b)
+    ?:  (gor p.n.a p.n.b)
+      $(l.b $(b l.b, r.a ~), a r.a)
+    $(r.b $(b r.b, l.a ~), a l.a)
+  ::
+  ++  urn                                               ::  apply gate to nodes
+    ~/  %urn
+    |*  b=$-([* *] *)
+    |-
+    ?~  a  ~
+    a(n n.a(q (b p.n.a q.n.a)), l $(a l.a), r $(a r.a))
+  ::
+  ++  wyt                                               ::  depth of map
+    =<  $
+    ~%  %wyt  +  ~
+    |.  ^-  @
+    ?~(a 0 +((add $(a l.a) $(a r.a))))
+  ::
+  ++  key                                               ::  set of keys
+    =<  $
+    ~/  %key
+    =+  b=`(set _?>(?=(^ a) p.n.a))`~
+    |.  ^+  b
+    ?~  a   b
+    $(a r.a, b $(a l.a, b (~(put in b) p.n.a)))
+  ::
+  ++  val                                               ::  list of vals
+    =+  b=`(list _?>(?=(^ a) q.n.a))`~
+    |-  ^+  b
+    ?~  a   b
+    $(a r.a, b [q.n.a $(a l.a)])
+  --
+::
+::    2j: jar and jug logic
++|  %jar-and-jug-logic
+++  ja                                                  ::  jar engine
+  =|  a=(tree (pair * (list)))  ::  (jar)
+  |@
+  ++  get                                               ::  gets list by key
+    |*  b=*
+    =+  c=(~(get by a) b)
+    ?~(c ~ u.c)
+  ::
+  ++  add                                               ::  adds key-list pair
+    |*  [b=* c=*]
+    =+  d=(get b)
+    (~(put by a) b [c d])
+  ::
+  ++  zip                                               ::  listify jar
+    =<  $
+    ~/  %zip
+    =+  b=`(list _?>(?=([[* ^] *] a) [p=p q=i.q]:n.a))`~
+    |.  ^+  b
+    ?~  a   b
+    %=  $
+      a  r.a
+      b  |-  ^+  b
+         ?~  q.n.a  ^$(a l.a)
+         [[p i.q]:n.a $(q.n.a t.q.n.a)]
+    ==
+  --
+++  ju                                                  ::  jug engine
+  =|  a=(tree (pair * (tree)))  ::  (jug)
+  |@
+  ++  del                                               ::  del key-set pair
+    |*  [b=* c=*]
+    ^+  a
+    =+  d=(get b)
+    =+  e=(~(del in d) c)
+    ?~  e
+      (~(del by a) b)
+    (~(put by a) b e)
+  ::
+  ++  gas                                               ::  concatenate
+    |*  b=(list [p=* q=*])
+    =>  .(b `(list _?>(?=([[* ^] ^] a) [p=p q=n.q]:n.a))`b)
+    |-  ^+  a
+    ?~  b
+      a
+    $(b t.b, a (put p.i.b q.i.b))
+  ::
+  ++  get                                               ::  gets set by key
+    |*  b=*
+    =+  c=(~(get by a) b)
+    ?~(c ~ u.c)
+  ::
+  ++  has                                               ::  existence check
+    |*  [b=* c=*]
+    ^-  ?
+    (~(has in (get b)) c)
+  ::
+  ++  put                                               ::  add key-set pair
+    |*  [b=* c=*]
+    ^+  a
+    =+  d=(get b)
+    (~(put by a) b (~(put in d) c))
+  --
+::
+::    2k: queue logic
++|  %queue-logic
+::
+++  to                                                  ::  queue engine
+  =|  a=(tree)  ::  (qeu)
+  |@
+  ++  apt                                               ::  check correctness
+    |-  ^-  ?
+    ?~  a  &
+    ?&  ?~(l.a & ?&((mor n.a n.l.a) $(a l.a)))
+        ?~(r.a & ?&((mor n.a n.r.a) $(a r.a)))
+    ==
+  ::
+  ++  bal
+    |-  ^+  a
+    ?~  a  ~
+    ?.  |(?=(~ l.a) (mor n.a n.l.a))
+      $(a l.a(r $(a a(l r.l.a))))
+    ?.  |(?=(~ r.a) (mor n.a n.r.a))
+      $(a r.a(l $(a a(r l.r.a))))
+    a
+  ::
+  ++  dep                                               ::  max depth of queue
+    |-  ^-  @
+    ?~  a  0
+    +((max $(a l.a) $(a r.a)))
+  ::
+  ++  gas                                               ::  insert list to que
+    |=  b=(list _?>(?=(^ a) n.a))
+    |-  ^+  a
+    ?~(b a $(b t.b, a (put i.b)))
+  ::
+  ++  get                                               ::  head-rest pair
+    |-  ^+  ?>(?=(^ a) [p=n.a q=*(tree _n.a)])
+    ?~  a
+      !!
+    ?~  r.a
+      [n.a l.a]
+    =+  b=$(a r.a)
+    :-  p.b
+    ?:  |(?=(~ q.b) (mor n.a n.q.b))
+      a(r q.b)
+    a(n n.q.b, l a(r l.q.b), r r.q.b)
+  ::
+  ++  nip                                               ::  removes root
+    |-  ^+  a
+    ?~  a  ~
+    ?~  l.a  r.a
+    ?~  r.a  l.a
+    ?:  (mor n.l.a n.r.a)
+      l.a(r $(l.a r.l.a))
+    r.a(l $(r.a l.r.a))
+  ::
+  ++  nap                                               ::  removes root
+    ?>  ?=(^ a)
+    ?:  =(~ l.a)  r.a
+    =+  b=get(a l.a)
+    bal(n.a p.b, l.a q.b)
+  ::
+  ++  put                                               ::  insert new tail
+    |*  b=*
+    |-  ^+  a
+    ?~  a
+      [b ~ ~]
+    bal(l.a $(a l.a))
+  ::
+  ++  run                                               ::  apply gate to values
+    |*  b=gate
+    |-
+    ?~  a  a
+    [n=(b n.a) l=$(a l.a) r=$(a r.a)]
+  ::
+  ++  tap                                               ::  adds list to end
+    =+  b=`(list _?>(?=(^ a) n.a))`~
+    |-  ^+  b
+    =+  0                                               ::  hack for jet match
+    ?~  a
+      b
+    $(a r.a, b [n.a $(a l.a)])
+  ::
+  ++  top                                               ::  produces head
+    |-  ^-  (unit _?>(?=(^ a) n.a))
+    ?~  a  ~
+    ?~(r.a [~ n.a] $(a r.a))
+  --
+::
+::    2l: container from container
++|  %container-from-container
+::
+++  malt                                                ::  map from list
+  |*  a=(list)
+  (molt `(list [p=_-<.a q=_->.a])`a)
+::
+++  molt                                                ::  map from pair list
+  |*  a=(list (pair))  ::  ^-  =,(i.-.a (map _p _q))
+  (~(gas by `(tree [p=_p.i.-.a q=_q.i.-.a])`~) a)
+::
+++  silt                                                ::  set from list
+  |*  a=(list)  ::  ^-  (set _i.-.a)
+  =+  b=*(tree _?>(?=(^ a) i.a))
+  (~(gas in b) a)
+::
+::    2m: container from noun
++|  %container-from-noun
+::
+++  ly                                                  ::  list from raw noun
+  le:nl
+::
+++  my                                                  ::  map from raw noun
+  my:nl
+::
+++  sy                                                  ::  set from raw noun
+  si:nl
+::
+++  nl
+  |%
+  ::                                                    ::
+  ++  le                                                ::  construct list
+    |*  a=(list)
+    ^+  =<  $
+      |@  ++  $  ?:(*? ~ [i=(snag 0 a) t=$])
+      --
+    a
+  ::                                                    ::
+  ++  my                                                ::  construct map
+    |*  a=(list (pair))
+    =>  .(a ^+((le a) a))
+    (~(gas by `(map _p.i.-.a _q.i.-.a)`~) a)
+  ::                                                    ::
+  ++  si                                                ::  construct set
+    |*  a=(list)
+    =>  .(a ^+((le a) a))
+    (~(gas in `(set _i.-.a)`~) a)
+  ::                                                    ::
+  ++  snag                                              ::  index
+    |*  [a=@ b=(list)]
+    ?~  b
+      ~_  leaf+"snag-fail"
+      !!
+    ?:  =(0 a)  i.b
+    $(b t.b, a (dec a))
+  ::                                                    ::
+  ++  weld                                              ::  concatenate
+    |*  [a=(list) b=(list)]
+    =>  .(a ^+((le a) a), b ^+((le b) b))
+    =+  42
+    |-
+    ?~  a  b
+    [i=i.a t=$(a t.a)]
+  --
+::    2n: functional hacks
++|  %functional-hacks
+::
+++  aftr  |*(a=$-(* *) |*(b=$-(* *) (pair b a)))        ::  pair after
+++  cork  |*([a=$-(* *) b=$-(* *)] (corl b a))          ::  compose forward
+++  corl                                                ::  compose backwards
+  |*  [a=$-(* *) b=$-(* *)]
+  =<  +:|.((a (b)))      ::  type check
+  |*  c=_,.+<.b
+  (a (b c))
+::
+++  cury                                                ::  curry left
+  |*  [a=$-(^ *) b=*]
+  |*  c=_,.+<+.a
+  (a b c)
+::
+++  curr                                                ::  curry right
+  |*  [a=$-(^ *) c=*]
+  |*  b=_,.+<-.a
+  (a b c)
+::
+++  fore  |*(a=$-(* *) |*(b=$-(* *) (pair a b)))        ::  pair before
+::
+++  head  |*(^ ,:+<-)                                   ::  get head
+++  same  |*(* +<)                                      ::  identity
+::
+++  succ  |=(@ +(+<))                                   ::  successor
+::
+++  tail  |*(^ ,:+<+)                                   ::  get tail
+++  test  |=(^ =(+<- +<+))                              ::  equality
+::
+++  lead  |*(* |*(* [+>+< +<]))                         ::  put head
+++  late  |*(* |*(* [+< +>+<]))                         ::  put tail
+::
+::    2o: containers
++|  %containers
+++  jar  |$  [key value]  (map key (list value))        ::  map of lists
+++  jug  |$  [key value]  (map key (set value))         ::  map of sets
+::
+++  map
+  |$  [key value]                                       ::  table
+  $|  (tree (pair key value))
+  |=(a=(tree (pair)) ?:(=(~ a) & ~(apt by a)))
+::
+++  qeu
+  |$  [item]                                            ::  queue
+  $|  (tree item)
+  |=(a=(tree) ?:(=(~ a) & ~(apt to a)))
+::
+++  set
+  |$  [item]                                            ::  set
+  $|  (tree item)
+  |=(a=(tree) ?:(=(~ a) & ~(apt in a)))
+::
+::    2p: serialization
++|  %serialization
+::
+++  cue                                                 ::  unpack
+  ~/  %cue
+  |=  a=@
+  ^-  *
+  =+  b=0
+  =+  m=`(map @ *)`~
+  =<  q
+  |-  ^-  [p=@ q=* r=(map @ *)]
+  ?:  =(0 (cut 0 [b 1] a))
+    =+  c=(rub +(b) a)
+    [+(p.c) q.c (~(put by m) b q.c)]
+  =+  c=(add 2 b)
+  ?:  =(0 (cut 0 [+(b) 1] a))
+    =+  u=$(b c)
+    =+  v=$(b (add p.u c), m r.u)
+    =+  w=[q.u q.v]
+    [(add 2 (add p.u p.v)) w (~(put by r.v) b w)]
+  =+  d=(rub c a)
+  [(add 2 p.d) (need (~(get by m) q.d)) m]
+::
+++  jam                                                 ::  pack
+  ~/  %jam
+  |=  a=*
+  ^-  @
+  =+  b=0
+  =+  m=`(map * @)`~
+  =<  q
+  |-  ^-  [p=@ q=@ r=(map * @)]
+  =+  c=(~(get by m) a)
+  ?~  c
+    =>  .(m (~(put by m) a b))
+    ?:  ?=(@ a)
+      =+  d=(mat a)
+      [(add 1 p.d) (lsh 0 q.d) m]
+    =>  .(b (add 2 b))
+    =+  d=$(a -.a)
+    =+  e=$(a +.a, b (add b p.d), m r.d)
+    [(add 2 (add p.d p.e)) (mix 1 (lsh [0 2] (cat 0 q.d q.e))) r.e]
+  ?:  ?&(?=(@ a) (lte (met 0 a) (met 0 u.c)))
+    =+  d=(mat a)
+    [(add 1 p.d) (lsh 0 q.d) m]
+  =+  d=(mat u.c)
+  [(add 2 p.d) (mix 3 (lsh [0 2] q.d)) m]
+::
+++  mat                                                 ::  length-encode
+  ~/  %mat
+  |=  a=@
+  ^-  [p=@ q=@]
+  ?:  =(0 a)
+    [1 1]
+  =+  b=(met 0 a)
+  =+  c=(met 0 b)
+  :-  (add (add c c) b)
+  (cat 0 (bex c) (mix (end [0 (dec c)] b) (lsh [0 (dec c)] a)))
+::
+++  rub                                                 ::  length-decode
+  ~/  %rub
+  |=  [a=@ b=@]
+  ^-  [p=@ q=@]
+  =+  ^=  c
+      =+  [c=0 m=(met 0 b)]
+      |-  ?<  (gth c m)
+      ?.  =(0 (cut 0 [(add a c) 1] b))
+        c
+      $(c +(c))
+  ?:  =(0 c)
+    [1 0]
+  =+  d=(add a +(c))
+  =+  e=(add (bex (dec c)) (cut 0 [d (dec c)] b))
+  [(add (add c c) e) (cut 0 [(add d (dec c)) e] b)]
+::
+++  fn  ::    float, infinity, or NaN
+        ::
+        ::  s=sign, e=exponent, a=arithmetic form
+        ::  (-1)^s * a * 2^e
+        $%  [%f s=? e=@s a=@u]
+            [%i s=?]
+            [%n ~]
+        ==
+::
+++  dn  ::    decimal float, infinity, or NaN
+        ::
+        ::  (-1)^s * a * 10^e
+        $%  [%d s=? e=@s a=@u]
+            [%i s=?]
+            [%n ~]
+        ==
+::
+++  rn  ::    parsed decimal float
+        ::
+        $%  [%d a=? b=[c=@ [d=@ e=@] f=? i=@]]
+            [%i a=?]
+            [%n ~]
+        ==
+::
+::    2q: molds and mold builders
++|  %molds-and-mold-builders
+::
++$  axis  @                                             ::  tree address
++$  bean  ?                                             ::  0=&=yes, 1=|=no
++$  flag  ?
++$  char  @t                                            ::  UTF8 byte
++$  cord  @t                                            ::  UTF8, LSB first
++$  byts  [wid=@ud dat=@]                               ::  bytes, MSB first
++$  date  [[a=? y=@ud] m=@ud t=tarp]                    ::  parsed date
++$  knot  @ta                                           ::  ASCII text
++$  noun  *                                             ::  any noun
++$  path  (list knot)                                   ::  like unix path
++$  pith  (list iota)                                   ::  typed urbit path
++$  stud                                                ::  standard name
+          $@  mark=@tas                                 ::  auth=urbit
+          $:  auth=@tas                                 ::  standards authority
+              type=path                                 ::  standard label
+          ==                                            ::
++$  tang  (list tank)                                   ::  bottom-first error
+::                                                      ::
++$  iota                                                ::  typed path segment
+  $+  iota
+  $~  [%n ~]
+  $@  @tas
+  $%  [%ub @ub]  [%uc @uc]  [%ud @ud]  [%ui @ui]
+      [%ux @ux]  [%uv @uv]  [%uw @uw]
+      [%sb @sb]  [%sc @sc]  [%sd @sd]  [%si @si]
+      [%sx @sx]  [%sv @sv]  [%sw @sw]
+      [%da @da]  [%dr @dr]
+      [%f ?]     [%n ~]
+      [%if @if]  [%is @is]
+      [%t @t]    [%ta @ta]  ::  @tas
+      [%p @p]    [%q @q]
+      [%rs @rs]  [%rd @rd]  [%rh @rh]  [%rq @rq]
+  ==
+::
+::  $tank: formatted print tree
+::
+::    just a cord, or
+::    %leaf: just a tape
+::    %palm: backstep list
+::           flat-mid, open, flat-open, flat-close
+::    %rose: flat list
+::           flat-mid, open, close
+::
++$  tank
+  $+  tank
+  $~  leaf/~
+  $@  cord
+  $%  [%leaf p=tape]
+      [%palm p=(qual tape tape tape tape) q=(list tank)]
+      [%rose p=(trel tape tape tape) q=(list tank)]
+  ==
+::
++$  tape  (list @tD)                                    ::  utf8 string as list
++$  tour  (list @c)                                     ::  utf32 clusters
++$  tarp  [d=@ud h=@ud m=@ud s=@ud f=(list @ux)]        ::  parsed time
++$  term  @tas                                          ::  ascii symbol
++$  wain  (list cord)                                   ::  text lines
++$  wall  (list tape)                                   ::  text lines
+::
+--  =>
+::                                                      ::
+~%  %tri  +
+  ==
+    %year  year
+    %yore  yore
+    %ob    ob
+  ==
+::    layer-3
+::
+|%
+::    3a: signed and modular ints
++|  %signed-and-modular-ints
+::
+++  egcd                                                ::  schneier's egcd
+  |=  [a=@ b=@]
+  =+  si
+  =+  [c=(sun a) d=(sun b)]
+  =+  [u=[c=(sun 1) d=--0] v=[c=--0 d=(sun 1)]]
+  |-  ^-  [d=@ u=@s v=@s]
+  ?:  =(--0 c)
+    [(abs d) d.u d.v]
+  ::  ?>  ?&  =(c (sum (pro (sun a) c.u) (pro (sun b) c.v)))
+  ::          =(d (sum (pro (sun a) d.u) (pro (sun b) d.v)))
+  ::      ==
+  =+  q=(fra d c)
+  %=  $
+    c  (dif d (pro q c))
+    d  c
+    u  [(dif d.u (pro q c.u)) c.u]
+    v  [(dif d.v (pro q c.v)) c.v]
+  ==
+::
+++  fo                                                  ::  modulo prime
+  ^|
+  |_  a=@
+  ++  dif
+    |=  [b=@ c=@]
+    (sit (sub (add a b) (sit c)))
+  ::
+  ++  exp
+    |=  [b=@ c=@]
+    ?:  =(0 b)
+      1
+    =+  d=$(b (rsh 0 b))
+    =+  e=(pro d d)
+    ?:(=(0 (end 0 b)) e (pro c e))
+  ::
+  ++  fra
+    |=  [b=@ c=@]
+    (pro b (inv c))
+  ::
+  ++  inv
+    |=  b=@
+    =+  c=(dul:si u:(egcd b a) a)
+    c
+  ::
+  ++  pro
+    |=  [b=@ c=@]
+    (sit (mul b c))
+  ::
+  ++  sit
+    |=  b=@
+    (mod b a)
+  ::
+  ++  sum
+    |=  [b=@ c=@]
+    (sit (add b c))
+  --
+::
+++  si                                                  ::  signed integer
+  ^?
+  |%
+  ++  abs  |=(a=@s (add (end 0 a) (rsh 0 a)))           ::  absolute value
+  ++  dif  |=  [a=@s b=@s]                              ::  subtraction
+           (sum a (new !(syn b) (abs b)))
+  ++  dul  |=  [a=@s b=@]                               ::  modulus
+           =+(c=(old a) ?:(-.c (mod +.c b) (sub b +.c)))
+  ++  fra  |=  [a=@s b=@s]                              ::  divide
+           (new =(0 (mix (syn a) (syn b))) (div (abs a) (abs b)))
+  ++  new  |=  [a=? b=@]                                ::  [sign value] to @s
+           `@s`?:(a (mul 2 b) ?:(=(0 b) 0 +((mul 2 (dec b)))))
+  ++  old  |=(a=@s [(syn a) (abs a)])                   ::  [sign value]
+  ++  pro  |=  [a=@s b=@s]                              ::  multiplication
+           (new =(0 (mix (syn a) (syn b))) (mul (abs a) (abs b)))
+  ++  rem  |=([a=@s b=@s] (dif a (pro b (fra a b))))    ::  remainder
+  ++  sum  |=  [a=@s b=@s]                              ::  addition
+           =+  [c=(old a) d=(old b)]
+           ?:  -.c
+             ?:  -.d
+               (new & (add +.c +.d))
+             ?:  (gte +.c +.d)
+               (new & (sub +.c +.d))
+             (new | (sub +.d +.c))
+           ?:  -.d
+             ?:  (gte +.c +.d)
+               (new | (sub +.c +.d))
+             (new & (sub +.d +.c))
+           (new | (add +.c +.d))
+  ++  sun  |=(a=@u (mul 2 a))                           ::  @u to @s
+  ++  syn  |=(a=@s =(0 (end 0 a)))                      ::  sign test
+  ++  cmp  |=  [a=@s b=@s]                              ::  compare
+           ^-  @s
+           ?:  =(a b)
+             --0
+           ?:  (syn a)
+             ?:  (syn b)
+               ?:  (gth a b)
+                 --1
+               -1
+             --1
+          ?:  (syn b)
+            -1
+          ?:  (gth a b)
+            -1
+          --1
+  --
+::
+::    3b: floating point
++|  %floating-point
+::
+++  fl                                                  ::  arb. precision fp
+  =/  [[p=@u v=@s w=@u] r=$?(%n %u %d %z %a) d=$?(%d %f %i)]
+    [[113 -16.494 32.765] %n %d]
+  ::  p=precision:     number of bits in arithmetic form; must be at least 2
+  ::  v=min exponent:  minimum value of e
+  ::  w=width:         max - min value of e, 0 is fixed point
+  ::  r=rounding mode: nearest (ties to even), up, down, to zero, away from zero
+  ::  d=behavior:      return denormals, flush denormals to zero,
+  ::                   infinite exponent range
+  =>
+    ~%  %cofl  +>  ~
+    ::    cofl
+    ::
+    ::  internal functions; mostly operating on [e=@s a=@u], in other words
+    ::  positive numbers. many of these error out if a=0.
+    |%
+    ++  rou
+      |=  [a=[e=@s a=@u]]  ^-  fn  (rau a &)
+    ::
+    ++  rau
+      |=  [a=[e=@s a=@u] t=?]  ^-  fn
+      ?-  r
+        %z  (lug %fl a t)  %d  (lug %fl a t)
+        %a  (lug %ce a t)  %u  (lug %ce a t)
+        %n  (lug %ne a t)
+      ==
+    ::
+    ++  add                                             ::  add; exact if e
+      |=  [a=[e=@s a=@u] b=[e=@s a=@u] e=?]  ^-  fn
+      =+  q=(dif:si e.a e.b)
+      |-  ?.  (syn:si q)  $(b a, a b, q +(q))           ::  a has larger exp
+      ?:  e
+        [%f & e.b (^add (lsh [0 (abs:si q)] a.a) a.b)]
+      =+  [ma=(met 0 a.a) mb=(met 0 a.b)]
+      =+  ^=  w  %+  dif:si  e.a  %-  sun:si            ::  expanded exp of a
+        ?:  (gth prc ma)  (^sub prc ma)  0
+      =+  ^=  x  %+  sum:si  e.b  (sun:si mb)           ::  highest exp for b
+      ?:  =((cmp:si w x) --1)                           ::  don't need to add
+        ?-  r
+          %z  (lug %fl a &)  %d  (lug %fl a &)
+          %a  (lug %lg a &)  %u  (lug %lg a &)
+          %n  (lug %na a &)
+        ==
+      (rou [e.b (^add (lsh [0 (abs:si q)] a.a) a.b)])
+    ::
+    ++  sub                                             ::  subtract; exact if e
+      |=  [a=[e=@s a=@u] b=[e=@s a=@u] e=?]  ^-  fn
+      =+  q=(dif:si e.a e.b)
+      |-  ?.  (syn:si q)
+        (fli $(b a, a b, q +(q), r swr))
+      =+  [ma=(met 0 a.a) mb=(met 0 a.b)]
+      =+  ^=  w  %+  dif:si  e.a  %-  sun:si
+        ?:  (gth prc ma)  (^sub prc ma)  0
+      =+  ^=  x  %+  sum:si  e.b  (sun:si +(mb))
+      ?:  &(!e =((cmp:si w x) --1))
+        ?-  r
+          %z  (lug %sm a &)  %d  (lug %sm a &)
+          %a  (lug %ce a &)  %u  (lug %ce a &)
+          %n  (lug %nt a &)
+        ==
+      =+  j=(lsh [0 (abs:si q)] a.a)
+      |-  ?.  (gte j a.b)
+        (fli $(a.b j, j a.b, r swr))
+      =+  i=(^sub j a.b)
+      ?~  i  [%f & zer]
+      ?:  e  [%f & e.b i]  (rou [e.b i])
+    ::
+    ++  mul                                             ::  multiply
+      |=  [a=[e=@s a=@u] b=[e=@s a=@u]]  ^-  fn
+      (rou (sum:si e.a e.b) (^mul a.a a.b))
+    ::
+    ++  div                                             ::  divide
+      |=  [a=[e=@s a=@u] b=[e=@s a=@u]]  ^-  fn
+      =+  [ma=(met 0 a.a) mb=(met 0 a.b)]
+      =+  v=(dif:si (sun:si ma) (sun:si +((^add mb prc))))
+      =.  a  ?:  (syn:si v)  a
+      a(e (sum:si v e.a), a (lsh [0 (abs:si v)] a.a))
+      =+  [j=(dif:si e.a e.b) q=(dvr a.a a.b)]
+      (rau [j p.q] =(q.q 0))
+    ::
+    ++  sqt                                             ::  square root
+      |=  [a=[e=@s a=@u]]  ^-  fn
+      =.  a
+        =+  [w=(met 0 a.a) x=(^mul +(prc) 2)]
+        =+  ?:((^lth w x) (^sub x w) 0)
+        =+  ?:  =((dis - 1) (dis (abs:si e.a) 1))  -
+          (^add - 1)
+        a(e (dif:si e.a (sun:si -)), a (lsh [0 -] a.a))
+      =+  [y=(^sqt a.a) z=(fra:si e.a --2)]
+      (rau [z p.y] =(q.y 0))
+    ::
+    ++  lth                                             ::  less-than
+      |=  [a=[e=@s a=@u] b=[e=@s a=@u]]  ^-  ?
+      ?:  =(e.a e.b)  (^lth a.a a.b)
+      =+  c=(cmp:si (ibl a) (ibl b))
+      ?:  =(c -1)  &  ?:  =(c --1)  |
+      ?:  =((cmp:si e.a e.b) -1)
+        (^lth (rsh [0 (abs:si (dif:si e.a e.b))] a.a) a.b)
+      (^lth (lsh [0 (abs:si (dif:si e.a e.b))] a.a) a.b)
+    ::
+    ++  equ                                             ::  equals
+      |=  [a=[e=@s a=@u] b=[e=@s a=@u]]  ^-  ?
+      ?.  =((ibl a) (ibl b))  |
+      ?:  =((cmp:si e.a e.b) -1)
+        =((lsh [0 (abs:si (dif:si e.a e.b))] a.b) a.a)
+      =((lsh [0 (abs:si (dif:si e.a e.b))] a.a) a.b)
+    ::
+    ::    integer binary logarithm: 2^ibl(a) <= |a| < 2^(ibl(a)+1)
+    ++  ibl
+      |=  [a=[e=@s a=@u]]  ^-  @s
+      (sum:si (sun:si (dec (met 0 a.a))) e.a)
+    ::
+    ::  +uni
+    ::
+    ::    change to a representation where a.a is odd
+    ::    every fn has a unique representation of this kind
+    ++  uni
+      |=  [a=[e=@s a=@u]]
+      |-  ?:  =((end 0 a.a) 1)  a
+      $(a.a (rsh 0 a.a), e.a (sum:si e.a --1))
+    ::
+    ::  +xpd: expands to either full precision or to denormalized
+    ++  xpd
+      |=  [a=[e=@s a=@u]]
+      =+  ma=(met 0 a.a)
+      ?:  (gte ma prc)  a
+      =+  ?:  =(den %i)  (^sub prc ma)
+          =+  ^=  q
+            =+  w=(dif:si e.a emn)
+            ?:  (syn:si w)  (abs:si w)  0
+          (min q (^sub prc ma))
+      a(e (dif:si e.a (sun:si -)), a (lsh [0 -] a.a))
+    ::
+    ::  +lug: central rounding mechanism
+    ::
+    ::    can perform: floor, ceiling, smaller, larger,
+    ::                 nearest (round ties to: even, away from 0, toward 0)
+    ::    s is sticky bit: represents a value less than ulp(a) = 2^(e.a)
+    ::
+    ++  lug
+      ~/  %lug
+      |=  [t=$?(%fl %ce %sm %lg %ne %na %nt) a=[e=@s a=@u] s=?]  ^-  fn
+      ?<  =(a.a 0)
+      =-
+        ?.  =(den %f)  -                                ::  flush denormals
+        ?.  ?=([%f *] -)  -
+        ?:  =((met 0 ->+>) prc)  -  [%f & zer]
+      ::
+      =+  m=(met 0 a.a)
+      ?>  |(s (gth m prc))                              ::  require precision
+      =+  ^=  q  %+  max
+          ?:  (gth m prc)  (^sub m prc)  0              ::  reduce precision
+        %-  abs:si  ?:  =(den %i)  --0                  ::  enforce min. exp
+        ?:  =((cmp:si e.a emn) -1)  (dif:si emn e.a)  --0
+      =^  b  a  :-  (end [0 q] a.a)
+        a(e (sum:si e.a (sun:si q)), a (rsh [0 q] a.a))
+      ::
+      ?~  a.a
+        ?<  =(den %i)
+        ?-  t
+          %fl  [%f & zer]
+          %sm  [%f & zer]
+          %ce  [%f & spd]
+          %lg  [%f & spd]
+          %ne  ?:  s  [%f & ?:((lte b (bex (dec q))) zer spd)]
+               [%f & ?:((^lth b (bex (dec q))) zer spd)]
+          %nt  ?:  s  [%f & ?:((lte b (bex (dec q))) zer spd)]
+               [%f & ?:((^lth b (bex (dec q))) zer spd)]
+          %na  [%f & ?:((^lth b (bex (dec q))) zer spd)]
+        ==
+      ::
+      =.  a  (xpd a)
+      ::
+      =.  a
+        ?-  t
+          %fl  a
+          %lg  a(a +(a.a))
+          %sm  ?.  &(=(b 0) s)  a
+               ?:  &(=(e.a emn) !=(den %i))  a(a (dec a.a))
+               =+  y=(dec (^mul a.a 2))
+               ?.  (lte (met 0 y) prc)  a(a (dec a.a))
+               [(dif:si e.a --1) y]
+          %ce  ?:  &(=(b 0) s)  a  a(a +(a.a))
+          %ne  ?~  b  a
+               =+  y=(bex (dec q))
+               ?:  &(=(b y) s)                          ::  round halfs to even
+                 ?~  (dis a.a 1)  a  a(a +(a.a))
+               ?:  (^lth b y)  a  a(a +(a.a))
+          %na  ?~  b  a
+               =+  y=(bex (dec q))
+               ?:  (^lth b y)  a  a(a +(a.a))
+          %nt  ?~  b  a
+               =+  y=(bex (dec q))
+               ?:  =(b y)  ?:  s  a  a(a +(a.a))
+               ?:  (^lth b y)  a  a(a +(a.a))
+        ==
+      ::
+      =.  a  ?.  =((met 0 a.a) +(prc))  a
+        a(a (rsh 0 a.a), e (sum:si e.a --1))
+      ?~  a.a  [%f & zer]
+      ::
+      ?:  =(den %i)  [%f & a]
+      ?:  =((cmp:si emx e.a) -1)  [%i &]  [%f & a]      ::  enforce max. exp
+    ::
+    ++  drg                                             ::  dragon4; get
+      ~/  %drg                                          ::  printable decimal;
+      |=  [a=[e=@s a=@u]]  ^-  [@s @u]                  ::  guaranteed accurate
+      ?<  =(a.a 0)                                      ::  for rounded floats
+      =.  a  (xpd a)
+      =+  r=(lsh [0 ?:((syn:si e.a) (abs:si e.a) 0)] a.a)
+      =+  s=(lsh [0 ?.((syn:si e.a) (abs:si e.a) 0)] 1)
+      =+  mn=(lsh [0 ?:((syn:si e.a) (abs:si e.a) 0)] 1)
+      =+  mp=mn
+      =>  ?.
+            ?&  =(a.a (bex (dec prc)))                  ::  if next smallest
+                |(!=(e.a emn) =(den %i))                ::  float is half ULP,
+            ==                                          ::  tighten lower bound
+          .
+        %=  .
+          mp  (lsh 0 mp)
+          r  (lsh 0 r)
+          s  (lsh 0 s)
+        ==
+      =+  [k=--0 q=(^div (^add s 9) 10)]
+      |-  ?:  (^lth r q)
+        %=  $
+          k  (dif:si k --1)
+          r  (^mul r 10)
+          mn  (^mul mn 10)
+          mp  (^mul mp 10)
+        ==
+      |-  ?:  (gte (^add (^mul r 2) mp) (^mul s 2))
+        $(s (^mul s 10), k (sum:si k --1))
+      =+  [u=0 o=0]
+      |-                                                ::  r/s+o = a*10^-k
+      =+  v=(dvr (^mul r 10) s)
+      =>  %=  .
+          k  (dif:si k --1)
+          u  p.v
+          r  q.v
+          mn  (^mul mn 10)
+          mp  (^mul mp 10)
+        ==
+      =+  l=(^lth (^mul r 2) mn)                        ::  in lower bound
+      =+  ^=  h                                         ::  in upper bound
+        ?|  (^lth (^mul s 2) mp)
+            (gth (^mul r 2) (^sub (^mul s 2) mp))
+        ==
+      ?:  &(!l !h)
+        $(o (^add (^mul o 10) u))
+      =+  q=&(h |(!l (gth (^mul r 2) s)))
+      =.  o  (^add (^mul o 10) ?:(q +(u) u))
+      [k o]
+    ::
+    ++  toj                                             ::  round to integer
+      |=  [a=[e=@s a=@u]]  ^-  fn
+      ?.  =((cmp:si e.a --0) -1)  [%f & a]
+      =+  x=(abs:si e.a)
+      =+  y=(rsh [0 x] a.a)
+      ?:  |(=(r %d) =(r %z))  [%f & --0 y]
+      =+  z=(end [0 x] a.a)
+      ?:  |(=(r %u) =(r %a))  [%f & --0 ?~(z y +(y))]
+      =+  i=(bex (dec x))
+      ?:  &(=(z i) =((dis y 1) 0))  [%f & --0 y]
+      ?:  (^lth z i)  [%f & --0 y]  [%f & --0 +(y)]
+    ::
+    ++  ned                                             ::  require ?=([%f *] a)
+      |=  [a=fn]  ^-  [%f s=? e=@s a=@u]
+      ?:  ?=([%f *] a)  a
+      ~_  leaf+"need-float"
+      !!
+    ::
+    ++  shf                                             ::  a * 2^b; no rounding
+      |=  [a=fn b=@s]
+      ?:  |(?=([%n *] a) ?=([%i *] a))  a
+      a(e (sum:si e.a b))
+    ::
+    ++  fli                                             ::  flip sign
+      |=  [a=fn]  ^-  fn
+      ?-(-.a %f a(s !s.a), %i a(s !s.a), %n a)
+    ::
+    ++  swr  ?+(r r %d %u, %u %d)                       ::  flipped rounding
+    ++  prc  ?>((gth p 1) p)                            ::  force >= 2 precision
+    ++  den  d                                          ::  denorm+flush+inf exp
+    ++  emn  v                                          ::  minimum exponent
+    ++  emx  (sum:si emn (sun:si w))                    ::  maximum exponent
+    ++  spd  [e=emn a=1]                                ::  smallest denormal
+    ++  spn  [e=emn a=(bex (dec prc))]                  ::  smallest normal
+    ++  lfn  [e=emx a=(fil 0 prc 1)]                    ::  largest
+    ++  lfe  (sum:si emx (sun:si prc))                  ::  2^lfe is > than all
+    ++  zer  [e=--0 a=0]
+    --
+  |%
+  ++  rou                                               ::  round
+    |=  [a=fn]  ^-  fn
+    ?.  ?=([%f *] a)  a
+    ?~  a.a  [%f s.a zer]
+    ?:  s.a  (^rou +>.a)
+    =.(r swr (fli (^rou +>.a)))
+  ::
+  ++  syn                                               ::  get sign
+    |=  [a=fn]  ^-  ?
+    ?-(-.a %f s.a, %i s.a, %n &)
+  ::
+  ++  abs                                               ::  absolute value
+    |=  [a=fn]  ^-  fn
+    ?:  ?=([%f *] a)  [%f & e.a a.a]
+    ?:  ?=([%i *] a)  [%i &]  [%n ~]
+  ::
+  ++  add                                               ::  add
+    |=  [a=fn b=fn]  ^-  fn
+    ?:  |(?=([%n *] a) ?=([%n *] b))  [%n ~]
+    ?:  |(?=([%i *] a) ?=([%i *] b))
+      ?:  &(?=([%i *] a) ?=([%i *] b))
+        ?:  =(a b)  a  [%n ~]
+      ?:  ?=([%i *] a)  a  b
+    ?:  |(=(a.a 0) =(a.b 0))
+      ?.  &(=(a.a 0) =(a.b 0))  %-  rou  ?~(a.a b a)
+      [%f ?:(=(r %d) &(s.a s.b) |(s.a s.b)) zer]
+    %-  |=  [a=fn]
+        ?.  ?=([%f *] a)  a
+        ?.  =(a.a 0)  a
+        [%f !=(r %d) zer]
+    ?:  =(s.a s.b)
+      ?:  s.a  (^add +>.a +>.b |)
+      =.(r swr (fli (^add +>.a +>.b |)))
+    ?:  s.a  (^sub +>.a +>.b |)
+    (^sub +>.b +>.a |)
+  ::
+  ++  ead                                               ::  exact add
+    |=  [a=fn b=fn]  ^-  fn
+    ?:  |(?=([%n *] a) ?=([%n *] b))  [%n ~]
+    ?:  |(?=([%i *] a) ?=([%i *] b))
+      ?:  &(?=([%i *] a) ?=([%i *] b))
+        ?:  =(a b)  a  [%n ~]
+      ?:  ?=([%i *] a)  a  b
+    ?:  |(=(a.a 0) =(a.b 0))
+      ?.  &(=(a.a 0) =(a.b 0))  ?~(a.a b a)
+      [%f ?:(=(r %d) &(s.a s.b) |(s.a s.b)) zer]
+    %-  |=  [a=fn]
+        ?.  ?=([%f *] a)  a
+        ?.  =(a.a 0)  a
+        [%f !=(r %d) zer]
+    ?:  =(s.a s.b)
+      ?:  s.a  (^add +>.a +>.b &)
+      (fli (^add +>.a +>.b &))
+    ?:  s.a  (^sub +>.a +>.b &)
+    (^sub +>.b +>.a &)
+  ::
+  ++  sub                                               ::  subtract
+    |=  [a=fn b=fn]  ^-  fn  (add a (fli b))
+  ::
+  ++  mul                                               ::  multiply
+    |=  [a=fn b=fn]  ^-  fn
+    ?:  |(?=([%n *] a) ?=([%n *] b))  [%n ~]
+    ?:  ?=([%i *] a)
+      ?:  ?=([%i *] b)
+        [%i =(s.a s.b)]
+      ?:  =(a.b 0)  [%n ~]  [%i =(s.a s.b)]
+    ?:  ?=([%i *] b)
+      ?:  =(a.a 0)  [%n ~]  [%i =(s.a s.b)]
+    ?:  |(=(a.a 0) =(a.b 0))  [%f =(s.a s.b) zer]
+    ?:  =(s.a s.b)  (^mul +>.a +>.b)
+    =.(r swr (fli (^mul +>.a +>.b)))
+  ::
+  ++  emu                                               ::  exact multiply
+    |=  [a=fn b=fn]  ^-  fn
+    ?:  |(?=([%n *] a) ?=([%n *] b))  [%n ~]
+    ?:  ?=([%i *] a)
+      ?:  ?=([%i *] b)
+        [%i =(s.a s.b)]
+      ?:  =(a.b 0)  [%n ~]  [%i =(s.a s.b)]
+    ?:  ?=([%i *] b)
+      ?:  =(a.a 0)  [%n ~]  [%i =(s.a s.b)]
+    ?:  |(=(a.a 0) =(a.b 0))  [%f =(s.a s.b) zer]
+    [%f =(s.a s.b) (sum:si e.a e.b) (^^mul a.a a.b)]
+  ::
+  ++  div                                               ::  divide
+    |=  [a=fn b=fn]  ^-  fn
+    ?:  |(?=([%n *] a) ?=([%n *] b))  [%n ~]
+    ?:  ?=([%i *] a)
+      ?:  ?=([%i *] b)  [%n ~]  [%i =(s.a s.b)]
+    ?:  ?=([%i *] b)  [%f =(s.a s.b) zer]
+    ?:  =(a.a 0)  ?:  =(a.b 0)  [%n ~]  [%f =(s.a s.b) zer]
+    ?:  =(a.b 0)  [%i =(s.a s.b)]
+    ?:  =(s.a s.b)  (^div +>.a +>.b)
+    =.(r swr (fli (^div +>.a +>.b)))
+  ::
+  ++  fma                                               ::  fused multiply-add
+    |=  [a=fn b=fn c=fn]  ^-  fn                        ::  (a * b) + c
+    (add (emu a b) c)
+  ::
+  ++  sqt                                               ::  square root
+    |=  [a=fn]  ^-  fn
+    ?:  ?=([%n *] a)  [%n ~]
+    ?:  ?=([%i *] a)  ?:(s.a a [%n ~])
+    ?~  a.a  [%f s.a zer]
+    ?:  s.a  (^sqt +>.a)  [%n ~]
+  ::
+  ++  inv                                               ::  inverse
+    |=  [a=fn]  ^-  fn
+    (div [%f & --0 1] a)
+  ::
+  ++  sun                                               ::  uns integer to float
+    |=  [a=@u]  ^-  fn
+    (rou [%f & --0 a])
+  ::
+  ++  san                                               ::  sgn integer to float
+    |=  [a=@s]  ^-  fn
+    =+  b=(old:si a)
+    (rou [%f -.b --0 +.b])
+  ::
+  ++  lth                                               ::  less-than
+    ::    comparisons return ~ in the event of a NaN
+    |=  [a=fn b=fn]  ^-  (unit ?)
+    ?:  |(?=([%n *] a) ?=([%n *] b))  ~  :-  ~
+    ?:  =(a b)  |
+    ?:  ?=([%i *] a)  !s.a  ?:  ?=([%i *] b)  s.b
+    ?:  |(=(a.a 0) =(a.b 0))
+      ?:  &(=(a.a 0) =(a.b 0))  |
+      ?:  =(a.a 0)  s.b  !s.a
+    ?:  !=(s.a s.b)  s.b
+    ?:  s.a  (^lth +>.a +>.b)  (^lth +>.b +>.a)
+  ::
+  ++  lte                                               ::  less-equal
+    |=  [a=fn b=fn]  ^-  (unit ?)
+    %+  bind  (lth b a)  |=  a=?  !a
+  ::
+  ++  equ                                               ::  equal
+    |=  [a=fn b=fn]  ^-  (unit ?)
+    ?:  |(?=([%n *] a) ?=([%n *] b))  ~  :-  ~
+    ?:  =(a b)  &
+    ?:  |(?=([%i *] a) ?=([%i *] b))  |
+    ?:  |(=(a.a 0) =(a.b 0))
+      ?:  &(=(a.a 0) =(a.b 0))  &  |
+    ?:  |(=(e.a e.b) !=(s.a s.b))  |
+    (^equ +>.a +>.b)
+  ::
+  ++  gte                                               ::  greater-equal
+    |=  [a=fn b=fn]  ^-  (unit ?)  (lte b a)
+  ::
+  ++  gth                                               ::  greater-than
+    |=  [a=fn b=fn]  ^-  (unit ?)  (lth b a)
+  ::
+  ++  drg                                               ::  float to decimal
+    |=  [a=fn]  ^-  dn
+    ?:  ?=([%n *] a)  [%n ~]
+    ?:  ?=([%i *] a)  [%i s.a]
+    ?~  a.a  [%d s.a --0 0]
+    [%d s.a (^drg +>.a)]
+  ::
+  ++  grd                                               ::  decimal to float
+    |=  [a=dn]  ^-  fn
+    ?:  ?=([%n *] a)  [%n ~]
+    ?:  ?=([%i *] a)  [%i s.a]
+    =>  .(r %n)
+    =+  q=(abs:si e.a)
+    ?:  (syn:si e.a)
+      (mul [%f s.a --0 a.a] [%f & e.a (pow 5 q)])
+    (div [%f s.a --0 a.a] [%f & (sun:si q) (pow 5 q)])
+  ::
+  ++  toi                                               ::  round to integer @s
+    |=  [a=fn]  ^-  (unit @s)
+    =+  b=(toj a)
+    ?.  ?=([%f *] b)  ~  :-  ~
+    =+  c=(^^mul (bex (abs:si e.b)) a.b)
+    (new:si s.b c)
+  ::
+  ++  toj                                               ::  round to integer fn
+    |=  [a=fn]  ^-  fn
+    ?.  ?=([%f *] a)  a
+    ?~  a.a  [%f s.a zer]
+    ?:  s.a  (^toj +>.a)
+    =.(r swr (fli (^toj +>.a)))
+  --
+::    +ff
+::
+::  this core has no use outside of the functionality
+::  provided to ++rd, ++rs, ++rq, and ++rh
+::
+::  w=width:         bits in exponent field
+::  p=precision:     bits in fraction field
+::  b=bias:          added to exponent when storing
+::  r=rounding mode: same as in ++fl
+++  ff                                                  ::  ieee 754 format fp
+  |_  [[w=@u p=@u b=@s] r=$?(%n %u %d %z %a)]
+  ::
+  ++  sb  (bex (^add w p))                              ::  sign bit
+  ++  me  (dif:si (dif:si --1 b) (sun:si p))            ::  minimum exponent
+  ::
+  ++  pa
+    %*(. fl p +(p), v me, w (^sub (bex w) 3), d %d, r r)
+  ::
+  ++  sea                                               ::  @r to fn
+    |=  [a=@r]  ^-  fn
+    =+  [f=(cut 0 [0 p] a) e=(cut 0 [p w] a)]
+    =+  s=(sig a)
+    ?:  =(e 0)
+      ?:  =(f 0)  [%f s --0 0]  [%f s me f]
+    ?:  =(e (fil 0 w 1))
+      ?:  =(f 0)  [%i s]  [%n ~]
+    =+  q=:(sum:si (sun:si e) me -1)
+    =+  r=(^add f (bex p))
+    [%f s q r]
+  ::
+  ++  bit  |=  [a=fn]  (bif (rou:pa a))                 ::  fn to @r w+ rounding
+  ::
+  ++  bif                                               ::  fn to @r no rounding
+    |=  [a=fn]  ^-  @r
+    ?:  ?=([%i *] a)
+      =+  q=(lsh [0 p] (fil 0 w 1))
+      ?:  s.a  q  (^add q sb)
+    ?:  ?=([%n *] a)  (lsh [0 (dec p)] (fil 0 +(w) 1))
+    ?~  a.a  ?:  s.a  `@r`0  sb
+    =+  ma=(met 0 a.a)
+    ?.  =(ma +(p))
+      ?>  =(e.a me)
+      ?>  (^lth ma +(p))
+      ?:  s.a  `@r`a.a  (^add a.a sb)
+    =+  q=(sum:si (dif:si e.a me) --1)
+    =+  r=(^add (lsh [0 p] (abs:si q)) (end [0 p] a.a))
+    ?:  s.a  r  (^add r sb)
+  ::
+  ++  sig                                               ::  get sign
+    |=  [a=@r]  ^-  ?
+    =(0 (cut 0 [(^add p w) 1] a))
+  ::
+  ++  exp                                               ::  get exponent
+    |=  [a=@r]  ^-  @s
+    (dif:si (sun:si (cut 0 [p w] a)) b)
+  ::
+  ++  add                                               ::  add
+    |=  [a=@r b=@r]
+    (bif (add:pa (sea a) (sea b)))
+  ::
+  ++  sub                                               ::  subtract
+    |=  [a=@r b=@r]
+    (bif (sub:pa (sea a) (sea b)))
+  ::
+  ++  mul                                               ::  multiply
+    |=  [a=@r b=@r]
+    (bif (mul:pa (sea a) (sea b)))
+  ::
+  ++  div                                               ::  divide
+    |=  [a=@r b=@r]
+    (bif (div:pa (sea a) (sea b)))
+  ::
+  ++  fma                                               ::  fused multiply-add
+    |=  [a=@r b=@r c=@r]
+    (bif (fma:pa (sea a) (sea b) (sea c)))
+  ::
+  ++  sqt                                               ::  square root
+    |=  [a=@r]
+    (bif (sqt:pa (sea a)))
+  ::
+  ++  lth                                               ::  less-than
+    |=  [a=@r b=@r]  (fall (lth:pa (sea a) (sea b)) |)
+  ++  lte                                               ::  less-equals
+    |=  [a=@r b=@r]  (fall (lte:pa (sea a) (sea b)) |)
+  ++  equ                                               ::  equals
+    |=  [a=@r b=@r]  (fall (equ:pa (sea a) (sea b)) |)
+  ++  gte                                               ::  greater-equals
+    |=  [a=@r b=@r]  (fall (gte:pa (sea a) (sea b)) |)
+  ++  gth                                               ::  greater-than
+    |=  [a=@r b=@r]  (fall (gth:pa (sea a) (sea b)) |)
+  ++  sun                                               ::  uns integer to @r
+    |=  [a=@u]  (bit [%f & --0 a])
+  ++  san                                               ::  signed integer to @r
+    |=  [a=@s]  (bit [%f (syn:si a) --0 (abs:si a)])
+  ++  toi                                               ::  round to integer
+    |=  [a=@r]  (toi:pa (sea a))
+  ++  drg                                               ::  @r to decimal float
+    |=  [a=@r]  (drg:pa (sea a))
+  ++  grd                                               ::  decimal float to @r
+    |=  [a=dn]  (bif (grd:pa a))
+  --
+::
+++  rlyd  |=  a=@rd  ^-  dn  (drg:rd a)                 ::  prep @rd for print
+++  rlys  |=  a=@rs  ^-  dn  (drg:rs a)                 ::  prep @rs for print
+++  rlyh  |=  a=@rh  ^-  dn  (drg:rh a)                 ::  prep @rh for print
+++  rlyq  |=  a=@rq  ^-  dn  (drg:rq a)                 ::  prep @rq for print
+++  ryld  |=  a=dn  ^-  @rd  (grd:rd a)                 ::  finish parsing @rd
+++  ryls  |=  a=dn  ^-  @rs  (grd:rs a)                 ::  finish parsing @rs
+++  rylh  |=  a=dn  ^-  @rh  (grd:rh a)                 ::  finish parsing @rh
+++  rylq  |=  a=dn  ^-  @rq  (grd:rq a)                 ::  finish parsing @rq
+::
+++  rd                                                  ::  double precision fp
+  ^|
+  ~%  %rd  +>  ~
+  |_  r=$?(%n %u %d %z)
+  ::  round to nearest, round up, round down, round to zero
+  ::
+  ++  ma
+    %*(. ff w 11, p 52, b --1.023, r r)
+  ::
+  ++  sea                                               ::  @rd to fn
+    |=  [a=@rd]  (sea:ma a)
+  ::
+  ++  bit                                               ::  fn to @rd
+    |=  [a=fn]  ^-  @rd  (bit:ma a)
+  ::
+  ++  add  ~/  %add                                     ::  add
+    |=  [a=@rd b=@rd]  ^-  @rd
+    ~_  leaf+"rd-fail"
+    (add:ma a b)
+  ::
+  ++  sub  ~/  %sub                                     ::  subtract
+    |=  [a=@rd b=@rd]  ^-  @rd
+    ~_  leaf+"rd-fail"
+    (sub:ma a b)
+  ::
+  ++  mul  ~/  %mul                                     ::  multiply
+    |=  [a=@rd b=@rd]  ^-  @rd
+    ~_  leaf+"rd-fail"
+    (mul:ma a b)
+  ::
+  ++  div  ~/  %div                                     ::  divide
+    |=  [a=@rd b=@rd]  ^-  @rd
+    ~_  leaf+"rd-fail"
+    (div:ma a b)
+  ::
+  ++  fma  ~/  %fma                                     ::  fused multiply-add
+    |=  [a=@rd b=@rd c=@rd]  ^-  @rd
+    ~_  leaf+"rd-fail"
+    (fma:ma a b c)
+  ::
+  ++  sqt  ~/  %sqt                                     ::  square root
+    |=  [a=@rd]  ^-  @rd  ~_  leaf+"rd-fail"
+    (sqt:ma a)
+  ::
+  ++  lth  ~/  %lth                                     ::  less-than
+    |=  [a=@rd b=@rd]
+    ~_  leaf+"rd-fail"
+    (lth:ma a b)
+  ::
+  ++  lte  ~/  %lte                                     ::  less-equals
+    |=  [a=@rd b=@rd]
+    ~_  leaf+"rd-fail"
+    (lte:ma a b)
+  ::
+  ++  equ  ~/  %equ                                     ::  equals
+    |=  [a=@rd b=@rd]
+    ~_  leaf+"rd-fail"
+    (equ:ma a b)
+  ::
+  ++  gte  ~/  %gte                                     ::  greater-equals
+    |=  [a=@rd b=@rd]
+    ~_  leaf+"rd-fail"
+    (gte:ma a b)
+  ::
+  ++  gth  ~/  %gth                                     ::  greater-than
+    |=  [a=@rd b=@rd]
+    ~_  leaf+"rd-fail"
+    (gth:ma a b)
+  ::
+  ++  sun  |=  [a=@u]  ^-  @rd  (sun:ma a)              ::  uns integer to @rd
+  ++  san  |=  [a=@s]  ^-  @rd  (san:ma a)              ::  sgn integer to @rd
+  ++  sig  |=  [a=@rd]  ^-  ?  (sig:ma a)               ::  get sign
+  ++  exp  |=  [a=@rd]  ^-  @s  (exp:ma a)              ::  get exponent
+  ++  toi  |=  [a=@rd]  ^-  (unit @s)  (toi:ma a)       ::  round to integer
+  ++  drg  |=  [a=@rd]  ^-  dn  (drg:ma a)              ::  @rd to decimal float
+  ++  grd  |=  [a=dn]  ^-  @rd  (grd:ma a)              ::  decimal float to @rd
+  --
+::
+++  rs                                                  ::  single precision fp
+  ~%  %rs  +>  ~
+  ^|
+  ::    round to nearest, round up, round down, round to zero
+  |_  r=$?(%n %u %d %z)
+  ::
+  ++  ma
+    %*(. ff w 8, p 23, b --127, r r)
+  ::
+  ++  sea                                               ::  @rs to fn
+    |=  [a=@rs]  (sea:ma a)
+  ::
+  ++  bit                                               ::  fn to @rs
+    |=  [a=fn]  ^-  @rs  (bit:ma a)
+  ::
+  ++  add  ~/  %add                                     ::  add
+    |=  [a=@rs b=@rs]  ^-  @rs
+    ~_  leaf+"rs-fail"
+    (add:ma a b)
+  ::
+  ++  sub  ~/  %sub                                     ::  subtract
+    |=  [a=@rs b=@rs]  ^-  @rs
+    ~_  leaf+"rs-fail"
+    (sub:ma a b)
+  ::
+  ++  mul  ~/  %mul                                     ::  multiply
+    |=  [a=@rs b=@rs]  ^-  @rs
+    ~_  leaf+"rs-fail"
+    (mul:ma a b)
+  ::
+  ++  div  ~/  %div                                     ::  divide
+    |=  [a=@rs b=@rs]  ^-  @rs
+    ~_  leaf+"rs-fail"
+    (div:ma a b)
+  ::
+  ++  fma  ~/  %fma                                     ::  fused multiply-add
+    |=  [a=@rs b=@rs c=@rs]  ^-  @rs
+    ~_  leaf+"rs-fail"
+    (fma:ma a b c)
+  ::
+  ++  sqt  ~/  %sqt                                     ::  square root
+    |=  [a=@rs]  ^-  @rs
+    ~_  leaf+"rs-fail"
+    (sqt:ma a)
+  ::
+  ++  lth  ~/  %lth                                     ::  less-than
+    |=  [a=@rs b=@rs]
+    ~_  leaf+"rs-fail"
+    (lth:ma a b)
+  ::
+  ++  lte  ~/  %lte                                     ::  less-equals
+    |=  [a=@rs b=@rs]
+    ~_  leaf+"rs-fail"
+    (lte:ma a b)
+  ::
+  ++  equ  ~/  %equ                                     ::  equals
+    |=  [a=@rs b=@rs]
+    ~_  leaf+"rs-fail"
+    (equ:ma a b)
+  ::
+  ++  gte  ~/  %gte                                     ::  greater-equals
+    |=  [a=@rs b=@rs]
+    ~_  leaf+"rs-fail"
+    (gte:ma a b)
+  ::
+  ++  gth  ~/  %gth                                     ::  greater-than
+    |=  [a=@rs b=@rs]
+    ~_  leaf+"rs-fail"
+    (gth:ma a b)
+  ::
+  ++  sun  |=  [a=@u]  ^-  @rs  (sun:ma a)              ::  uns integer to @rs
+  ++  san  |=  [a=@s]  ^-  @rs  (san:ma a)              ::  sgn integer to @rs
+  ++  sig  |=  [a=@rs]  ^-  ?  (sig:ma a)               ::  get sign
+  ++  exp  |=  [a=@rs]  ^-  @s  (exp:ma a)              ::  get exponent
+  ++  toi  |=  [a=@rs]  ^-  (unit @s)  (toi:ma a)       ::  round to integer
+  ++  drg  |=  [a=@rs]  ^-  dn  (drg:ma a)              ::  @rs to decimal float
+  ++  grd  |=  [a=dn]  ^-  @rs  (grd:ma a)              ::  decimal float to @rs
+  --
+::
+++  rq                                                  ::  quad precision fp
+  ~%  %rq  +>  ~
+  ^|
+  ::    round to nearest, round up, round down, round to zero
+  |_  r=$?(%n %u %d %z)
+  ::
+  ++  ma
+    %*(. ff w 15, p 112, b --16.383, r r)
+  ::
+  ++  sea                                               ::  @rq to fn
+    |=  [a=@rq]  (sea:ma a)
+  ::
+  ++  bit                                               ::  fn to @rq
+    |=  [a=fn]  ^-  @rq  (bit:ma a)
+  ::
+  ++  add  ~/  %add                                     ::  add
+    |=  [a=@rq b=@rq]  ^-  @rq
+    ~_  leaf+"rq-fail"
+    (add:ma a b)
+  ::
+  ++  sub  ~/  %sub                                     ::  subtract
+    |=  [a=@rq b=@rq]  ^-  @rq
+    ~_  leaf+"rq-fail"
+    (sub:ma a b)
+  ::
+  ++  mul  ~/  %mul                                     ::  multiply
+    |=  [a=@rq b=@rq]  ^-  @rq
+    ~_  leaf+"rq-fail"
+    (mul:ma a b)
+  ::
+  ++  div  ~/  %div                                     ::  divide
+    |=  [a=@rq b=@rq]  ^-  @rq
+    ~_  leaf+"rq-fail"
+    (div:ma a b)
+  ::
+  ++  fma  ~/  %fma                                     ::  fused multiply-add
+    |=  [a=@rq b=@rq c=@rq]  ^-  @rq
+    ~_  leaf+"rq-fail"
+    (fma:ma a b c)
+  ::
+  ++  sqt  ~/  %sqt                                     ::  square root
+    |=  [a=@rq]  ^-  @rq
+    ~_  leaf+"rq-fail"
+    (sqt:ma a)
+  ::
+  ++  lth  ~/  %lth                                     ::  less-than
+    |=  [a=@rq b=@rq]
+    ~_  leaf+"rq-fail"
+    (lth:ma a b)
+  ::
+  ++  lte  ~/  %lte                                     ::  less-equals
+    |=  [a=@rq b=@rq]
+    ~_  leaf+"rq-fail"
+    (lte:ma a b)
+  ::
+  ++  equ  ~/  %equ                                     ::  equals
+    |=  [a=@rq b=@rq]
+    ~_  leaf+"rq-fail"
+    (equ:ma a b)
+  ::
+  ++  gte  ~/  %gte                                     ::  greater-equals
+    |=  [a=@rq b=@rq]
+    ~_  leaf+"rq-fail"
+    (gte:ma a b)
+  ::
+  ++  gth  ~/  %gth                                     ::  greater-than
+    |=  [a=@rq b=@rq]
+    ~_  leaf+"rq-fail"
+    (gth:ma a b)
+  ::
+  ++  sun  |=  [a=@u]  ^-  @rq  (sun:ma a)              ::  uns integer to @rq
+  ++  san  |=  [a=@s]  ^-  @rq  (san:ma a)              ::  sgn integer to @rq
+  ++  sig  |=  [a=@rq]  ^-  ?  (sig:ma a)               ::  get sign
+  ++  exp  |=  [a=@rq]  ^-  @s  (exp:ma a)              ::  get exponent
+  ++  toi  |=  [a=@rq]  ^-  (unit @s)  (toi:ma a)       ::  round to integer
+  ++  drg  |=  [a=@rq]  ^-  dn  (drg:ma a)              ::  @rq to decimal float
+  ++  grd  |=  [a=dn]  ^-  @rq  (grd:ma a)              ::  decimal float to @rq
+  --
+::
+++  rh                                                  ::  half precision fp
+  ~%  %rh  +>  ~
+  ^|
+  ::    round to nearest, round up, round down, round to zero
+  |_  r=$?(%n %u %d %z)
+  ::
+  ++  ma
+    %*(. ff w 5, p 10, b --15, r r)
+  ::
+  ++  sea                                               ::  @rh to fn
+    |=  [a=@rh]  (sea:ma a)
+  ::
+  ++  bit                                               ::  fn to @rh
+    |=  [a=fn]  ^-  @rh  (bit:ma a)
+  ::
+  ++  add  ~/  %add                                     ::  add
+    |=  [a=@rh b=@rh]  ^-  @rh
+    ~_  leaf+"rh-fail"
+    (add:ma a b)
+  ::
+  ++  sub  ~/  %sub                                     ::  subtract
+    |=  [a=@rh b=@rh]  ^-  @rh
+    ~_  leaf+"rh-fail"
+    (sub:ma a b)
+  ::
+  ++  mul  ~/  %mul                                     ::  multiply
+    |=  [a=@rh b=@rh]  ^-  @rh
+    ~_  leaf+"rh-fail"
+    (mul:ma a b)
+  ::
+  ++  div  ~/  %div                                     ::  divide
+    |=  [a=@rh b=@rh]  ^-  @rh
+    ~_  leaf+"rh-fail"
+    (div:ma a b)
+  ::
+  ++  fma  ~/  %fma                                     ::  fused multiply-add
+    |=  [a=@rh b=@rh c=@rh]  ^-  @rh
+    ~_  leaf+"rh-fail"
+    (fma:ma a b c)
+  ::
+  ++  sqt  ~/  %sqt                                     ::  square root
+    |=  [a=@rh]  ^-  @rh
+    ~_  leaf+"rh-fail"
+    (sqt:ma a)
+  ::
+  ++  lth  ~/  %lth                                     ::  less-than
+    |=  [a=@rh b=@rh]
+    ~_  leaf+"rh-fail"
+    (lth:ma a b)
+  ::
+  ++  lte  ~/  %lte                                     ::  less-equals
+    |=  [a=@rh b=@rh]
+    ~_  leaf+"rh-fail"
+    (lte:ma a b)
+  ::
+  ++  equ  ~/  %equ                                     ::  equals
+    |=  [a=@rh b=@rh]
+    ~_  leaf+"rh-fail"
+    (equ:ma a b)
+  ::
+  ++  gte  ~/  %gte                                     ::  greater-equals
+    |=  [a=@rh b=@rh]
+    ~_  leaf+"rh-fail"
+    (gte:ma a b)
+  ::
+  ++  gth  ~/  %gth                                     ::  greater-than
+    |=  [a=@rh b=@rh]
+    ~_  leaf+"rh-fail"
+    (gth:ma a b)
+  ::
+  ++  tos                                               ::  @rh to @rs
+    |=  [a=@rh]  (bit:rs (sea a))
+  ::
+  ++  fos                                               ::  @rs to @rh
+    |=  [a=@rs]  (bit (sea:rs a))
+  ::
+  ++  sun  |=  [a=@u]  ^-  @rh  (sun:ma a)              ::  uns integer to @rh
+  ++  san  |=  [a=@s]  ^-  @rh  (san:ma a)              ::  sgn integer to @rh
+  ++  sig  |=  [a=@rh]  ^-  ?  (sig:ma a)               ::  get sign
+  ++  exp  |=  [a=@rh]  ^-  @s  (exp:ma a)              ::  get exponent
+  ++  toi  |=  [a=@rh]  ^-  (unit @s)  (toi:ma a)       ::  round to integer
+  ++  drg  |=  [a=@rh]  ^-  dn  (drg:ma a)              ::  @rh to decimal float
+  ++  grd  |=  [a=dn]  ^-  @rh  (grd:ma a)              ::  decimal float to @rh
+  --
+::
+::    3c: urbit time
++|  %urbit-time
+::
+++  year                                                ::  date to @d
+  |=  det=date
+  ^-  @da
+  =+  ^=  yer
+      ?:  a.det
+        (add 292.277.024.400 y.det)
+      (sub 292.277.024.400 (dec y.det))
+  =+  day=(yawn yer m.det d.t.det)
+  (yule day h.t.det m.t.det s.t.det f.t.det)
+::
+++  yore                                                ::  @d to date
+  |=  now=@da
+  ^-  date
+  =+  rip=(yell now)
+  =+  ger=(yall d.rip)
+  :-  ?:  (gth y.ger 292.277.024.400)
+        [a=& y=(sub y.ger 292.277.024.400)]
+      [a=| y=+((sub 292.277.024.400 y.ger))]
+  [m.ger d.ger h.rip m.rip s.rip f.rip]
+::
+++  yell                                                ::  tarp from @d
+  |=  now=@d
+  ^-  tarp
+  =+  sec=(rsh 6 now)
+  =+  ^=  fan
+      =+  [muc=4 raw=(end 6 now)]
+      |-  ^-  (list @ux)
+      ?:  |(=(0 raw) =(0 muc))
+        ~
+      =>  .(muc (dec muc))
+      [(cut 4 [muc 1] raw) $(raw (end [4 muc] raw))]
+  =+  day=(div sec day:yo)
+  =>  .(sec (mod sec day:yo))
+  =+  hor=(div sec hor:yo)
+  =>  .(sec (mod sec hor:yo))
+  =+  mit=(div sec mit:yo)
+  =>  .(sec (mod sec mit:yo))
+  [day hor mit sec fan]
+::
+++  yule                                                ::  time atom
+  |=  rip=tarp
+  ^-  @d
+  =+  ^=  sec  ;:  add
+                 (mul d.rip day:yo)
+                 (mul h.rip hor:yo)
+                 (mul m.rip mit:yo)
+                 s.rip
+               ==
+  =+  ^=  fac  =+  muc=4
+               |-  ^-  @
+               ?~  f.rip
+                 0
+               =>  .(muc (dec muc))
+               (add (lsh [4 muc] i.f.rip) $(f.rip t.f.rip))
+  (con (lsh 6 sec) fac)
+::
+++  yall                                                ::  day / to day of year
+  |=  day=@ud
+  ^-  [y=@ud m=@ud d=@ud]
+  =+  [era=0 cet=0 lep=*?]
+  =>  .(era (div day era:yo), day (mod day era:yo))
+  =>  ^+  .
+      ?:  (lth day +(cet:yo))
+        .(lep &, cet 0)
+      =>  .(lep |, cet 1, day (sub day +(cet:yo)))
+      .(cet (add cet (div day cet:yo)), day (mod day cet:yo))
+  =+  yer=(add (mul 400 era) (mul 100 cet))
+  |-  ^-  [y=@ud m=@ud d=@ud]
+  =+  dis=?:(lep 366 365)
+  ?.  (lth day dis)
+    =+  ner=+(yer)
+    $(yer ner, day (sub day dis), lep =(0 (end [0 2] ner)))
+  |-  ^-  [y=@ud m=@ud d=@ud]
+  =+  [mot=0 cah=?:(lep moy:yo moh:yo)]
+  |-  ^-  [y=@ud m=@ud d=@ud]
+  =+  zis=(snag mot cah)
+  ?:  (lth day zis)
+    [yer +(mot) +(day)]
+  $(mot +(mot), day (sub day zis))
+::
+++  yawn                                                ::  days since Jesus
+  |=  [yer=@ud mot=@ud day=@ud]
+  ^-  @ud
+  =>  .(mot (dec mot), day (dec day))
+  =>  ^+  .
+      %=    .
+          day
+        =+  cah=?:((yelp yer) moy:yo moh:yo)
+        |-  ^-  @ud
+        ?:  =(0 mot)
+          day
+        $(mot (dec mot), cah (slag 1 cah), day (add day (snag 0 cah)))
+      ==
+  |-  ^-  @ud
+  ?.  =(0 (mod yer 4))
+    =+  ney=(dec yer)
+    $(yer ney, day (add day ?:((yelp ney) 366 365)))
+  ?.  =(0 (mod yer 100))
+    =+  nef=(sub yer 4)
+    $(yer nef, day (add day ?:((yelp nef) 1.461 1.460)))
+  ?.  =(0 (mod yer 400))
+    =+  nec=(sub yer 100)
+    $(yer nec, day (add day ?:((yelp nec) 36.525 36.524)))
+  (add day (mul (div yer 400) (add 1 (mul 4 36.524))))
+::
+++  yelp                                                ::  leap year
+  |=  yer=@ud  ^-  ?
+  &(=(0 (mod yer 4)) |(!=(0 (mod yer 100)) =(0 (mod yer 400))))
+::
+++  yo                                                  ::  time constants
+  |%  ++  cet  36.524                 ::  (add 24 (mul 100 365))
+      ++  day  86.400                 ::  (mul 24 hor)
+      ++  era  146.097                ::  (add 1 (mul 4 cet))
+      ++  hor  3.600                  ::  (mul 60 mit)
+      ++  jes  106.751.991.084.417    ::  (mul 730.692.561 era)
+      ++  mit  60
+      ++  moh  `(list @ud)`[31 28 31 30 31 30 31 31 30 31 30 31 ~]
+      ++  moy  `(list @ud)`[31 29 31 30 31 30 31 31 30 31 30 31 ~]
+      ++  qad  126.144.001            ::  (add 1 (mul 4 yer))
+      ++  yer  31.536.000             ::  (mul 365 day)
+  --
+::
+::    3d: SHA hash family
++|  %sha-hash-family
+::
+++  shad  |=(ruz=@ (shax (shax ruz)))                   ::  double sha-256
+++  shaf                                                ::  half sha-256
+  |=  [sal=@ ruz=@]
+  =+  haz=(shas sal ruz)
+  (mix (end 7 haz) (rsh 7 haz))
+::
+++  sham                                                ::  128bit noun hash
+  |=  yux=*  ^-  @uvH  ^-  @
+  ?@  yux
+    (shaf %mash yux)
+  (shaf %sham (jam yux))
+::
+++  shas                                                ::  salted hash
+  ~/  %shas
+  |=  [sal=@ ruz=@]
+  =/  len  (max 32 (met 3 sal))
+  (shay len (mix sal (shax ruz)))
+::
+++  shax                                                ::  sha-256
+  ~/  %shax
+  |=  ruz=@  ^-  @
+  (shay [(met 3 ruz) ruz])
+::
+++  shay                                                ::  sha-256 with length
+  ~/  %shay
+  |=  [len=@u ruz=@]  ^-  @
+  =>  .(ruz (cut 3 [0 len] ruz))
+  =+  [few==>(fe .(a 5)) wac=|=([a=@ b=@] (cut 5 [a 1] b))]
+  =+  [sum=sum.few ror=ror.few net=net.few inv=inv.few]
+  =+  ral=(lsh [0 3] len)
+  =+  ^=  ful
+      %+  can  0
+      :~  [ral ruz]
+          [8 128]
+          [(mod (sub 960 (mod (add 8 ral) 512)) 512) 0]
+          [64 (~(net fe 6) ral)]
+      ==
+  =+  lex=(met 9 ful)
+  =+  ^=  kbx  0xc671.78f2.bef9.a3f7.a450.6ceb.90be.fffa.
+                 8cc7.0208.84c8.7814.78a5.636f.748f.82ee.
+                 682e.6ff3.5b9c.ca4f.4ed8.aa4a.391c.0cb3.
+                 34b0.bcb5.2748.774c.1e37.6c08.19a4.c116.
+                 106a.a070.f40e.3585.d699.0624.d192.e819.
+                 c76c.51a3.c24b.8b70.a81a.664b.a2bf.e8a1.
+                 9272.2c85.81c2.c92e.766a.0abb.650a.7354.
+                 5338.0d13.4d2c.6dfc.2e1b.2138.27b7.0a85.
+                 1429.2967.06ca.6351.d5a7.9147.c6e0.0bf3.
+                 bf59.7fc7.b003.27c8.a831.c66d.983e.5152.
+                 76f9.88da.5cb0.a9dc.4a74.84aa.2de9.2c6f.
+                 240c.a1cc.0fc1.9dc6.efbe.4786.e49b.69c1.
+                 c19b.f174.9bdc.06a7.80de.b1fe.72be.5d74.
+                 550c.7dc3.2431.85be.1283.5b01.d807.aa98.
+                 ab1c.5ed5.923f.82a4.59f1.11f1.3956.c25b.
+                 e9b5.dba5.b5c0.fbcf.7137.4491.428a.2f98
+  =+  ^=  hax  0x5be0.cd19.1f83.d9ab.9b05.688c.510e.527f.
+                 a54f.f53a.3c6e.f372.bb67.ae85.6a09.e667
+  =+  i=0
+  |-  ^-  @
+  ?:  =(i lex)
+    (run 5 hax net)
+  =+  ^=  wox
+      =+  dux=(cut 9 [i 1] ful)
+      =+  wox=(run 5 dux net)
+      =+  j=16
+      |-  ^-  @
+      ?:  =(64 j)
+        wox
+      =+  :*  l=(wac (sub j 15) wox)
+              m=(wac (sub j 2) wox)
+              n=(wac (sub j 16) wox)
+              o=(wac (sub j 7) wox)
+          ==
+      =+  x=:(mix (ror 0 7 l) (ror 0 18 l) (rsh [0 3] l))
+      =+  y=:(mix (ror 0 17 m) (ror 0 19 m) (rsh [0 10] m))
+      =+  z=:(sum n x o y)
+      $(wox (con (lsh [5 j] z) wox), j +(j))
+  =+  j=0
+  =+  :*  a=(wac 0 hax)
+          b=(wac 1 hax)
+          c=(wac 2 hax)
+          d=(wac 3 hax)
+          e=(wac 4 hax)
+          f=(wac 5 hax)
+          g=(wac 6 hax)
+          h=(wac 7 hax)
+      ==
+  |-  ^-  @
+  ?:  =(64 j)
+    %=  ^$
+      i  +(i)
+      hax  %+  rep  5
+           :~  (sum a (wac 0 hax))
+               (sum b (wac 1 hax))
+               (sum c (wac 2 hax))
+               (sum d (wac 3 hax))
+               (sum e (wac 4 hax))
+               (sum f (wac 5 hax))
+               (sum g (wac 6 hax))
+               (sum h (wac 7 hax))
+           ==
+    ==
+  =+  l=:(mix (ror 0 2 a) (ror 0 13 a) (ror 0 22 a))    ::  s0
+  =+  m=:(mix (dis a b) (dis a c) (dis b c))            ::  maj
+  =+  n=(sum l m)                                       ::  t2
+  =+  o=:(mix (ror 0 6 e) (ror 0 11 e) (ror 0 25 e))    ::  s1
+  =+  p=(mix (dis e f) (dis (inv e) g))                 ::  ch
+  =+  q=:(sum h o p (wac j kbx) (wac j wox))            ::  t1
+  $(j +(j), a (sum q n), b a, c b, d c, e (sum d q), f e, g f, h g)
+::
+++  shaw                                                ::  hash to nbits
+  |=  [sal=@ len=@ ruz=@]
+  (~(raw og (shas sal (mix len ruz))) len)
+::
+++  shaz                                                ::  sha-512
+  |=  ruz=@  ^-  @
+  (shal [(met 3 ruz) ruz])
+::
+++  shal                                                ::  sha-512 with length
+  ~/  %shal
+  |=  [len=@ ruz=@]  ^-  @
+  =>  .(ruz (cut 3 [0 len] ruz))
+  =+  [few==>(fe .(a 6)) wac=|=([a=@ b=@] (cut 6 [a 1] b))]
+  =+  [sum=sum.few ror=ror.few net=net.few inv=inv.few]
+  =+  ral=(lsh [0 3] len)
+  =+  ^=  ful
+      %+  can  0
+      :~  [ral ruz]
+          [8 128]
+          [(mod (sub 1.920 (mod (add 8 ral) 1.024)) 1.024) 0]
+          [128 (~(net fe 7) ral)]
+      ==
+  =+  lex=(met 10 ful)
+  =+  ^=  kbx  0x6c44.198c.4a47.5817.5fcb.6fab.3ad6.faec.
+                 597f.299c.fc65.7e2a.4cc5.d4be.cb3e.42b6.
+                 431d.67c4.9c10.0d4c.3c9e.be0a.15c9.bebc.
+                 32ca.ab7b.40c7.2493.28db.77f5.2304.7d84.
+                 1b71.0b35.131c.471b.113f.9804.bef9.0dae.
+                 0a63.7dc5.a2c8.98a6.06f0.67aa.7217.6fba.
+                 f57d.4f7f.ee6e.d178.eada.7dd6.cde0.eb1e.
+                 d186.b8c7.21c0.c207.ca27.3ece.ea26.619c.
+                 c671.78f2.e372.532b.bef9.a3f7.b2c6.7915.
+                 a450.6ceb.de82.bde9.90be.fffa.2363.1e28.
+                 8cc7.0208.1a64.39ec.84c8.7814.a1f0.ab72.
+                 78a5.636f.4317.2f60.748f.82ee.5def.b2fc.
+                 682e.6ff3.d6b2.b8a3.5b9c.ca4f.7763.e373.
+                 4ed8.aa4a.e341.8acb.391c.0cb3.c5c9.5a63.
+                 34b0.bcb5.e19b.48a8.2748.774c.df8e.eb99.
+                 1e37.6c08.5141.ab53.19a4.c116.b8d2.d0c8.
+                 106a.a070.32bb.d1b8.f40e.3585.5771.202a.
+                 d699.0624.5565.a910.d192.e819.d6ef.5218.
+                 c76c.51a3.0654.be30.c24b.8b70.d0f8.9791.
+                 a81a.664b.bc42.3001.a2bf.e8a1.4cf1.0364.
+                 9272.2c85.1482.353b.81c2.c92e.47ed.aee6.
+                 766a.0abb.3c77.b2a8.650a.7354.8baf.63de.
+                 5338.0d13.9d95.b3df.4d2c.6dfc.5ac4.2aed.
+                 2e1b.2138.5c26.c926.27b7.0a85.46d2.2ffc.
+                 1429.2967.0a0e.6e70.06ca.6351.e003.826f.
+                 d5a7.9147.930a.a725.c6e0.0bf3.3da8.8fc2.
+                 bf59.7fc7.beef.0ee4.b003.27c8.98fb.213f.
+                 a831.c66d.2db4.3210.983e.5152.ee66.dfab.
+                 76f9.88da.8311.53b5.5cb0.a9dc.bd41.fbd4.
+                 4a74.84aa.6ea6.e483.2de9.2c6f.592b.0275.
+                 240c.a1cc.77ac.9c65.0fc1.9dc6.8b8c.d5b5.
+                 efbe.4786.384f.25e3.e49b.69c1.9ef1.4ad2.
+                 c19b.f174.cf69.2694.9bdc.06a7.25c7.1235.
+                 80de.b1fe.3b16.96b1.72be.5d74.f27b.896f.
+                 550c.7dc3.d5ff.b4e2.2431.85be.4ee4.b28c.
+                 1283.5b01.4570.6fbe.d807.aa98.a303.0242.
+                 ab1c.5ed5.da6d.8118.923f.82a4.af19.4f9b.
+                 59f1.11f1.b605.d019.3956.c25b.f348.b538.
+                 e9b5.dba5.8189.dbbc.b5c0.fbcf.ec4d.3b2f.
+                 7137.4491.23ef.65cd.428a.2f98.d728.ae22
+  =+  ^=  hax  0x5be0.cd19.137e.2179.1f83.d9ab.fb41.bd6b.
+                 9b05.688c.2b3e.6c1f.510e.527f.ade6.82d1.
+                 a54f.f53a.5f1d.36f1.3c6e.f372.fe94.f82b.
+                 bb67.ae85.84ca.a73b.6a09.e667.f3bc.c908
+  =+  i=0
+  |-  ^-  @
+  ?:  =(i lex)
+    (run 6 hax net)
+  =+  ^=  wox
+      =+  dux=(cut 10 [i 1] ful)
+      =+  wox=(run 6 dux net)
+      =+  j=16
+      |-  ^-  @
+      ?:  =(80 j)
+        wox
+      =+  :*  l=(wac (sub j 15) wox)
+              m=(wac (sub j 2) wox)
+              n=(wac (sub j 16) wox)
+              o=(wac (sub j 7) wox)
+          ==
+      =+  x=:(mix (ror 0 1 l) (ror 0 8 l) (rsh [0 7] l))
+      =+  y=:(mix (ror 0 19 m) (ror 0 61 m) (rsh [0 6] m))
+      =+  z=:(sum n x o y)
+      $(wox (con (lsh [6 j] z) wox), j +(j))
+  =+  j=0
+  =+  :*  a=(wac 0 hax)
+          b=(wac 1 hax)
+          c=(wac 2 hax)
+          d=(wac 3 hax)
+          e=(wac 4 hax)
+          f=(wac 5 hax)
+          g=(wac 6 hax)
+          h=(wac 7 hax)
+      ==
+  |-  ^-  @
+  ?:  =(80 j)
+    %=  ^$
+      i  +(i)
+      hax  %+  rep  6
+           :~  (sum a (wac 0 hax))
+               (sum b (wac 1 hax))
+               (sum c (wac 2 hax))
+               (sum d (wac 3 hax))
+               (sum e (wac 4 hax))
+               (sum f (wac 5 hax))
+               (sum g (wac 6 hax))
+               (sum h (wac 7 hax))
+           ==
+    ==
+  =+  l=:(mix (ror 0 28 a) (ror 0 34 a) (ror 0 39 a))   ::  S0
+  =+  m=:(mix (dis a b) (dis a c) (dis b c))            ::  maj
+  =+  n=(sum l m)                                       ::  t2
+  =+  o=:(mix (ror 0 14 e) (ror 0 18 e) (ror 0 41 e))   ::  S1
+  =+  p=(mix (dis e f) (dis (inv e) g))                 ::  ch
+  =+  q=:(sum h o p (wac j kbx) (wac j wox))            ::  t1
+  $(j +(j), a (sum q n), b a, c b, d c, e (sum d q), f e, g f, h g)
+::
+++  shan                                                ::  sha-1 (deprecated)
+  |=  ruz=@
+  =+  [few==>(fe .(a 5)) wac=|=([a=@ b=@] (cut 5 [a 1] b))]
+  =+  [sum=sum.few ror=ror.few rol=rol.few net=net.few inv=inv.few]
+  =+  ral=(lsh [0 3] (met 3 ruz))
+  =+  ^=  ful
+      %+  can  0
+      :~  [ral ruz]
+          [8 128]
+          [(mod (sub 960 (mod (add 8 ral) 512)) 512) 0]
+          [64 (~(net fe 6) ral)]
+      ==
+  =+  lex=(met 9 ful)
+  =+  kbx=0xca62.c1d6.8f1b.bcdc.6ed9.eba1.5a82.7999
+  =+  hax=0xc3d2.e1f0.1032.5476.98ba.dcfe.efcd.ab89.6745.2301
+  =+  i=0
+  |-
+  ?:  =(i lex)
+    (rep 5 (flop (rip 5 hax)))
+  =+  ^=  wox
+      =+  dux=(cut 9 [i 1] ful)
+      =+  wox=(rep 5 (turn (rip 5 dux) net))
+      =+  j=16
+      |-  ^-  @
+      ?:  =(80 j)
+        wox
+      =+  :*  l=(wac (sub j 3) wox)
+              m=(wac (sub j 8) wox)
+              n=(wac (sub j 14) wox)
+              o=(wac (sub j 16) wox)
+          ==
+      =+  z=(rol 0 1 :(mix l m n o))
+      $(wox (con (lsh [5 j] z) wox), j +(j))
+  =+  j=0
+  =+  :*  a=(wac 0 hax)
+          b=(wac 1 hax)
+          c=(wac 2 hax)
+          d=(wac 3 hax)
+          e=(wac 4 hax)
+      ==
+  |-  ^-  @
+  ?:  =(80 j)
+    %=  ^$
+      i  +(i)
+      hax  %+  rep  5
+           :~
+               (sum a (wac 0 hax))
+               (sum b (wac 1 hax))
+               (sum c (wac 2 hax))
+               (sum d (wac 3 hax))
+               (sum e (wac 4 hax))
+           ==
+    ==
+  =+  fx=(con (dis b c) (dis (not 5 1 b) d))
+  =+  fy=:(mix b c d)
+  =+  fz=:(con (dis b c) (dis b d) (dis c d))
+  =+  ^=  tem
+      ?:  &((gte j 0) (lte j 19))
+        :(sum (rol 0 5 a) fx e (wac 0 kbx) (wac j wox))
+      ?:  &((gte j 20) (lte j 39))
+        :(sum (rol 0 5 a) fy e (wac 1 kbx) (wac j wox))
+      ?:  &((gte j 40) (lte j 59))
+        :(sum (rol 0 5 a) fz e (wac 2 kbx) (wac j wox))
+      :(sum (rol 0 5 a) fy e (wac 3 kbx) (wac j wox))
+  $(j +(j), a tem, b a, c (rol 0 30 b), d c, e d)
+::
+::  NEVER USE: broken piece of trash
+++  og                                                  ::  shax-powered rng
+  |_  a=@
+  ++  rad                                               ::  random in range
+    |=  b=@  ^-  @
+    !!
+  ::
+  ++  rads                                              ::  random continuation
+    |=  b=@
+    !!
+  ::
+  ++  raw                                               ::  random bits
+    |=  b=@  ^-  @
+    !!
+  ::
+  ++  raws                                              ::  random bits
+    |=  b=@                                             ::  continuation
+    !!
+  --
+::
+++  sha                                                 ::  correct byte-order
+  ~%  %sha  ..sha  ~
+  =>  |%
+      ++  flin  |=(a=@ (swp 3 a))                       ::  flip input
+      ++  flim  |=(byts [wid (rev 3 wid dat)])          ::  flip input w= length
+      ++  flip  |=(w=@u (cury (cury rev 3) w))          ::  flip output of size
+      ++  meet  |=(a=@ [(met 3 a) a])                   ::  measure input size
+      --
+  |%
+  ::
+  ::  use with @
+  ::
+  ++  sha-1     (cork meet sha-1l)
+  ++  sha-256   :(cork flin shax (flip 32))
+  ++  sha-512   :(cork flin shaz (flip 64))
+  ::
+  ::  use with byts
+  ::
+  ++  sha-256l  :(cork flim shay (flip 32))
+  ++  sha-512l  :(cork flim shal (flip 64))
+  ::
+  ++  sha-1l
+    ~/  %sha1
+    |=  byts
+    ^-  @
+    =+  [few==>(fe .(a 5)) wac=|=([a=@ b=@] (cut 5 [a 1] b))]
+    =+  [sum=sum.few ror=ror.few rol=rol.few net=net.few inv=inv.few]
+    =+  ral=(lsh [0 3] wid)
+    =+  ^=  ful
+        %+  can  0
+        :~  [ral (rev 3 wid dat)]
+            [8 128]
+            [(mod (sub 960 (mod (add 8 ral) 512)) 512) 0]
+            [64 (~(net fe 6) ral)]
+        ==
+    =+  lex=(met 9 ful)
+    =+  kbx=0xca62.c1d6.8f1b.bcdc.6ed9.eba1.5a82.7999
+    =+  hax=0xc3d2.e1f0.1032.5476.98ba.dcfe.efcd.ab89.6745.2301
+    =+  i=0
+    |-
+    ?:  =(i lex)
+      (rep 5 (flop (rip 5 hax)))
+    =+  ^=  wox
+        =+  dux=(cut 9 [i 1] ful)
+        =+  wox=(rep 5 (turn (rip 5 dux) net))
+        =+  j=16
+        |-  ^-  @
+        ?:  =(80 j)
+          wox
+        =+  :*  l=(wac (sub j 3) wox)
+                m=(wac (sub j 8) wox)
+                n=(wac (sub j 14) wox)
+                o=(wac (sub j 16) wox)
+            ==
+        =+  z=(rol 0 1 :(mix l m n o))
+        $(wox (con (lsh [5 j] z) wox), j +(j))
+    =+  j=0
+    =+  :*  a=(wac 0 hax)
+            b=(wac 1 hax)
+            c=(wac 2 hax)
+            d=(wac 3 hax)
+            e=(wac 4 hax)
+        ==
+    |-  ^-  @
+    ?:  =(80 j)
+      %=  ^$
+        i  +(i)
+        hax  %+  rep  5
+             :~
+                 (sum a (wac 0 hax))
+                 (sum b (wac 1 hax))
+                 (sum c (wac 2 hax))
+                 (sum d (wac 3 hax))
+                 (sum e (wac 4 hax))
+             ==
+      ==
+    =+  fx=(con (dis b c) (dis (not 5 1 b) d))
+    =+  fy=:(mix b c d)
+    =+  fz=:(con (dis b c) (dis b d) (dis c d))
+    =+  ^=  tem
+        ?:  &((gte j 0) (lte j 19))
+          :(sum (rol 0 5 a) fx e (wac 0 kbx) (wac j wox))
+        ?:  &((gte j 20) (lte j 39))
+          :(sum (rol 0 5 a) fy e (wac 1 kbx) (wac j wox))
+        ?:  &((gte j 40) (lte j 59))
+          :(sum (rol 0 5 a) fz e (wac 2 kbx) (wac j wox))
+        :(sum (rol 0 5 a) fy e (wac 3 kbx) (wac j wox))
+    $(j +(j), a tem, b a, c (rol 0 30 b), d c, e d)
+  --
+::    3f: scrambling
++|  %scrambling
+::
+++  un                                                  ::  =(x (wred (wren x)))
+  |%
+  ++  wren                                              ::  conceal structure
+    |=  pyn=@  ^-  @
+    =+  len=(met 3 pyn)
+    ?:  =(0 len)
+      0
+    =>  .(len (dec len))
+    =+  mig=(zaft (xafo len (cut 3 [len 1] pyn)))
+    %+  can  3
+    %-  flop  ^-  (list [@ @])
+    :-  [1 mig]
+    |-  ^-  (list [@ @])
+    ?:  =(0 len)
+      ~
+    =>  .(len (dec len))
+    =+  mog=(zyft :(mix mig (end 3 len) (cut 3 [len 1] pyn)))
+    [[1 mog] $(mig mog)]
+  ::
+  ++  wred                                              ::  restore structure
+    |=  cry=@  ^-  @
+    =+  len=(met 3 cry)
+    ?:  =(0 len)
+      0
+    =>  .(len (dec len))
+    =+  mig=(cut 3 [len 1] cry)
+    %+  can  3
+    %-  flop  ^-  (list [@ @])
+    :-  [1 (xaro len (zart mig))]
+    |-  ^-  (list [@ @])
+    ?:  =(0 len)
+      ~
+    =>  .(len (dec len))
+    =+  mog=(cut 3 [len 1] cry)
+    [[1 :(mix mig (end 3 len) (zyrt mog))] $(mig mog)]
+  ::
+  ++  xafo  |=([a=@ b=@] +((mod (add (dec b) a) 255)))
+  ++  xaro  |=([a=@ b=@] +((mod (add (dec b) (sub 255 (mod a 255))) 255)))
+  ::
+  ++  zaft                                              ::  forward 255-sbox
+    |=  a=@D
+    =+  ^=  b
+        0xcc.75bc.86c8.2fb1.9a42.f0b3.79a0.92ca.21f6.1e41.cde5.fcc0.
+        7e85.51ae.1005.c72d.1246.07e8.7c64.a914.8d69.d9f4.59c2.8038.
+        1f4a.dca2.6fdf.66f9.f561.a12e.5a16.f7b0.a39f.364e.cb70.7318.
+        1de1.ad31.63d1.abd4.db68.6a33.134d.a760.edee.5434.493a.e323.
+        930d.8f3d.3562.bb81.0b24.43cf.bea5.a6eb.52b4.0229.06b2.6704.
+        78c9.45ec.d75e.58af.c577.b7b9.c40e.017d.90c3.87f8.96fa.1153.
+        0372.7f30.1c32.ac83.ff17.c6e4.d36d.6b55.e2ce.8c71.8a5b.b6f3.
+        9d4b.eab5.8b3c.e7f2.a8fe.9574.5de0.bf20.3f15.9784.9939.5f9c.
+        e609.564f.d8a4.b825.9819.94aa.2c08.8e4c.9b22.477a.2840.3ed6.
+        3750.6ef1.44dd.89ef.6576.d00a.fbda.9ed2.3b6c.7b0c.bde9.2ade.
+        5c88.c182.481a.1b0f.2bfd.d591.2726.57ba
+    (cut 3 [(dec a) 1] b)
+  ::
+  ++  zart                                              ::  reverse 255-sbox
+    |=  a=@D
+    =+  ^=  b
+        0x68.4f07.ea1c.73c9.75c2.efc8.d559.5125.f621.a7a8.8591.5613.
+        dd52.40eb.65a2.60b7.4bcb.1123.ceb0.1bd6.3c84.2906.b164.19b3.
+        1e95.5fec.ffbc.f187.fbe2.6680.7c77.d30e.e94a.9414.fd9a.017d.
+        3a7e.5a55.8ff5.8bf9.c181.e5b6.6ab2.35da.50aa.9293.3bc0.cdc6.
+        f3bf.1a58.4130.f844.3846.744e.36a0.f205.789e.32d8.5e54.5c22.
+        0f76.fce7.4569.0d99.d26e.e879.dc16.2df4.887f.1ffe.4dba.6f5d.
+        bbcc.2663.1762.aed7.af8a.ca20.dbb4.9bc7.a942.834c.105b.c4d4.
+        8202.3e61.a671.90e6.273d.bdab.3157.cfa4.0c2e.df86.2496.f7ed.
+        2b48.2a9d.5318.a343.d128.be9c.a5ad.6bb5.6dfa.c5e1.3408.128d.
+        2c04.0339.97a1.2ff0.49d0.eeb8.6c0a.0b37.b967.c347.d9ac.e072.
+        e409.7b9f.1598.1d3f.33de.8ce3.8970.8e7a
+    (cut 3 [(dec a) 1] b)
+  ::
+  ++  zyft                                              ::  forward 256-sbox
+    |=  a=@D
+    =+  ^=  b
+        0xbb49.b71f.b881.b402.17e4.6b86.69b5.1647.115f.dddb.7ca5.
+          8371.4bd5.19a9.b092.605d.0d9b.e030.a0cc.78ba.5706.4d2d.
+          986a.768c.f8e8.c4c7.2f1c.effe.3cae.01c0.253e.65d3.3872.
+          ce0e.7a74.8ac6.daac.7e5c.6479.44ec.4143.3d20.4af0.ee6c.
+          c828.deca.0377.249f.ffcd.7b4f.eb7d.66f2.8951.042e.595a.
+          8e13.f9c3.a79a.f788.6199.9391.7fab.6200.4ce5.0758.e2f1.
+          7594.c945.d218.4248.afa1.e61a.54fb.1482.bea4.96a2.3473.
+          63c2.e7cb.155b.120a.4ed7.bfd8.b31b.4008.f329.fca3.5380.
+          9556.0cb2.8722.2bea.e96e.3ac5.d1bc.10e3.2c52.a62a.b1d6.
+          35aa.d05e.f6a8.0f3b.31ed.559d.09ad.f585.6d21.fd1d.8d67.
+          370b.26f4.70c1.b923.4684.6fbd.cf8b.5036.0539.9cdc.d93f.
+          9068.1edf.8f33.b632.d427.97fa.9ee1
+    (cut 3 [a 1] b)
+  ::
+  ++  zyrt                                              ::  reverse 256-sbox
+    |=  a=@D
+    =+  ^=  b
+        0x9fc8.2753.6e02.8fcf.8b35.2b20.5598.7caa.c9a9.30b0.9b48.
+          47ce.6371.80f6.407d.00dd.0aa5.ed10.ecb7.0f5a.5c3a.e605.
+          c077.4337.17bd.9eda.62a4.79a7.ccb8.44cd.8e64.1ec4.5b6b.
+          1842.ffd8.1dfb.fd07.f2f9.594c.3be3.73c6.2cb6.8438.e434.
+          8d3d.ea6a.5268.72db.a001.2e11.de8c.88d3.0369.4f7a.87e2.
+          860d.0991.25d0.16b9.978a.4bf4.2a1a.e96c.fa50.85b5.9aeb.
+          9dbb.b2d9.a2d1.7bba.66be.e81f.1946.29a8.f5d2.f30c.2499.
+          c1b3.6583.89e1.ee36.e0b4.6092.937e.d74e.2f6f.513e.9615.
+          9c5d.d581.e7ab.fe74.f01b.78b1.ae75.af57.0ec2.adc7.3245.
+          12bf.2314.3967.0806.31dc.cb94.d43f.493c.54a6.0421.c3a1.
+          1c4a.28ac.fc0b.26ca.5870.e576.f7f1.616d.905f.ef41.33bc.
+          df4d.225e.2d56.7fd6.1395.a3f8.c582
+    (cut 3 [a 1] b)
+  --
+::
+++  ob
+  ~%  %ob  ..ob
+    ==
+      %fein  fein
+      %fynd  fynd
+    ==
+  |%
+  ::
+  ::  +fein: conceal structure, v3.
+  ::
+  ::    +fein conceals planet-sized atoms.  The idea is that it should not be
+  ::    trivial to tell which planet a star has spawned under.
+  ::
+  ++  fein
+    ~/  %fein
+    |=  pyn=@  ^-  @
+    ?:  &((gte pyn 0x1.0000) (lte pyn 0xffff.ffff))
+      (add 0x1.0000 (feis (sub pyn 0x1.0000)))
+    ?:  &((gte pyn 0x1.0000.0000) (lte pyn 0xffff.ffff.ffff.ffff))
+      =/  lo  (dis pyn 0xffff.ffff)
+      =/  hi  (dis pyn 0xffff.ffff.0000.0000)
+      %+  con  hi
+      $(pyn lo)
+    pyn
+  ::
+  ::  +fynd: restore structure, v3.
+  ::
+  ::    Restores obfuscated values that have been enciphered with +fein.
+  ::
+  ++  fynd
+    ~/  %fynd
+    |=  cry=@  ^-  @
+    ?:  &((gte cry 0x1.0000) (lte cry 0xffff.ffff))
+      (add 0x1.0000 (tail (sub cry 0x1.0000)))
+    ?:  &((gte cry 0x1.0000.0000) (lte cry 0xffff.ffff.ffff.ffff))
+      =/  lo  (dis cry 0xffff.ffff)
+      =/  hi  (dis cry 0xffff.ffff.0000.0000)
+      %+  con  hi
+      $(cry lo)
+    cry
+  ::  +feis: a four-round generalised Feistel cipher over the domain
+  ::         [0, 2^32 - 2^16 - 1].
+  ::
+  ::    See: Black & Rogaway (2002), Ciphers for arbitrary finite domains.
+  ::
+  ++  feis
+    |=  m=@
+    ^-  @
+    (fee 4 0xffff 0x1.0000 (mul 0xffff 0x1.0000) eff m)
+  ::
+  ::  +tail: reverse +feis.
+  ::
+  ++  tail
+    |=  m=@
+    ^-  @
+    (feen 4 0xffff 0x1.0000 (mul 0xffff 0x1.0000) eff m)
+  ::
+  ::  +fee: "Fe" in B&R (2002).
+  ::
+  ::    A Feistel cipher given the following parameters:
+  ::
+  ::    r:    number of Feistel rounds
+  ::    a, b: parameters such that ab >= k
+  ::    k:    value such that the domain of the cipher is [0, k - 1]
+  ::    prf:  a gate denoting a family of pseudorandom functions indexed by
+  ::          its first argument and taking its second argument as input
+  ::    m:    an input value in the domain [0, k - 1]
+  ::
+  ++  fee
+    |=  [r=@ a=@ b=@ k=@ prf=$-([j=@ r=@] @) m=@]
+    ^-  @
+    =/  c  (fe r a b prf m)
+    ?:  (lth c k)
+      c
+    (fe r a b prf c)
+  ::
+  ::  +feen: "Fe^-1" in B&R (2002).
+  ::
+  ::    Reverses a Feistel cipher constructed with parameters as described in
+  ::    +fee.
+  ::
+  ++  feen
+    |=  [r=@ a=@ b=@ k=@ prf=$-([j=@ r=@] @) m=@]
+    ^-  @
+    =/  c  (fen r a b prf m)
+    ?:  (lth c k)
+      c
+    (fen r a b prf c)
+  ::
+  ::  +fe:  "fe" in B&R (2002).
+  ::
+  ::    An internal function to +fee.
+  ::
+  ::    Note that this implementation differs slightly from the reference paper
+  ::    to support some legacy behaviour.  See urbit/arvo#1105.
+  ::
+  ++  fe
+    |=  [r=@ a=@ b=@ prf=$-([j=@ r=@] @) m=@]
+    =/  j  1
+    =/  ell  (mod m a)
+    =/  arr  (div m a)
+    |-  ^-  @
+    ::
+    ?:  (gth j r)
+      ?.  =((mod r 2) 0)
+        (add (mul arr a) ell)
+      ::
+      :: Note that +fe differs from B&R (2002)'s "fe" below, as a previous
+      :: implementation of this cipher contained a bug such that certain inputs
+      :: could encipher to the same output.
+      ::
+      :: To correct these problem cases while also preserving the cipher's
+      :: legacy behaviour on most inputs, we check for a problem case (which
+      :: occurs when 'arr' is equal to 'a') and, if detected, use an alternate
+      :: permutation instead.
+      ::
+      ?:  =(arr a)
+        (add (mul arr a) ell)
+      (add (mul ell a) arr)
+    ::
+    =/  f  (prf (sub j 1) arr)
+    ::
+    =/  tmp
+      ?.  =((mod j 2) 0)
+        (mod (add f ell) a)
+      (mod (add f ell) b)
+    ::
+    $(j +(j), ell arr, arr tmp)
+  ::
+  ::  +fen:  "fe^-1" in B&R (2002).
+  ::
+  ::    Note that this implementation differs slightly from the reference paper
+  ::    to support some legacy behaviour.  See urbit/arvo#1105.
+  ::
+  ++  fen
+    |=  [r=@ a=@ b=@ prf=$-([j=@ r=@] @) m=@]
+    =/  j  r
+    ::
+    =/  ahh
+      ?.  =((mod r 2) 0)
+        (div m a)
+      (mod m a)
+    ::
+    =/  ale
+      ?.  =((mod r 2) 0)
+        (mod m a)
+      (div m a)
+    ::
+    :: Similar to the comment in +fe, +fen differs from B&R (2002)'s "fe^-1"
+    :: here in order to preserve the legacy cipher's behaviour on most inputs.
+    ::
+    :: Here problem cases can be identified by 'ahh' equating with 'a'; we
+    :: correct those cases by swapping the values of 'ahh' and 'ale'.
+    ::
+    =/  ell
+      ?:  =(ale a)
+        ahh
+      ale
+    ::
+    =/  arr
+      ?:  =(ale a)
+        ale
+      ahh
+    ::
+    |-  ^-  @
+    ?:  (lth j 1)
+      (add (mul arr a) ell)
+    =/  f  (prf (sub j 1) ell)
+    ::
+    ::  Note that there is a slight deviation here to avoid dealing with
+    ::  negative values.  We add 'a' or 'b' to arr as appropriate and reduce
+    ::  'f' modulo the same number before performing subtraction.
+    ::
+    =/  tmp
+      ?.  =((mod j 2) 0)
+        (mod (sub (add arr a) (mod f a)) a)
+      (mod (sub (add arr b) (mod f b)) b)
+    ::
+    $(j (sub j 1), ell tmp, arr ell)
+  ::
+  ::  +eff: a murmur3-based pseudorandom function.  'F' in B&R (2002).
+  ::
+  ++  eff
+    |=  [j=@ r=@]
+    ^-  @
+    (muk (snag j raku) 2 r)
+  ::
+  ::  +raku: seeds for eff.
+  ::
+  ++  raku
+    ^-  (list @ux)
+    :~  0xb76d.5eed
+        0xee28.1300
+        0x85bc.ae01
+        0x4b38.7af7
+    ==
+  ::
+  --
+::
+::    3g: molds and mold builders
++|  %molds-and-mold-builders
+::
++$  coin  $~  [%$ %ud 0]                                ::  print format
+          $%  [%$ p=dime]                               ::
+              [%blob p=*]                               ::
+              [%many p=(list coin)]                     ::
+          ==                                            ::
++$  dime  [p=@ta q=@]                                   ::
++$  edge  [p=hair q=(unit [p=* q=nail])]                ::  parsing output
++$  hair  [p=@ud q=@ud]                                 ::  parsing trace
+++  like  |*  a=$-(* *)                                 ::  generic edge
+          |:  b=`*`[(hair) ~]                           ::
+          :-  p=(hair -.b)                              ::
+          ^=  q                                         ::
+          ?@  +.b  ~                                    ::
+          :-  ~                                         ::
+          u=[p=(a +>-.b) q=[p=(hair -.b) q=(tape +.b)]] ::
++$  nail  [p=hair q=tape]                               ::  parsing input
++$  pint  [p=[p=@ q=@] q=[p=@ q=@]]                     ::  line+column range
++$  rule  _|:($:nail $:edge)                            ::  parsing rule
++$  spot  [p=path q=pint]                               ::  range in file
++$  tone  $%  [%0 product=*]                            ::  success
+              [%1 block=*]                              ::  single block
+              [%2 trace=(list [@ta *])]                 ::  error report
+          ==                                            ::
++$  toon  $%  [%0 p=*]                                  ::  success
+              [%1 p=*]                                  ::  block
+              [%2 p=(list tank)]                        ::  stack trace
+          ==                                            ::
+++  wonk  |*  veq=_$:edge                                ::  product from edge
+          ?~(q.veq !! p.u.q.veq)             ::
+--  =>
+::
+~%    %qua
+    +
+  ==
+    %mure  mure
+    %mute  mute
+    %show  show
+  ==
+::    layer-4
+::
+|%
+::
+::    4a: exotic bases
++|  %exotic-bases
+::
+++  po                                                  ::  phonetic base
+  ~/  %po
+  =+  :-  ^=  sis                                       ::  prefix syllables
+      'dozmarbinwansamlitsighidfidlissogdirwacsabwissib\
+      /rigsoldopmodfoglidhopdardorlorhodfolrintogsilmir\
+      /holpaslacrovlivdalsatlibtabhanticpidtorbolfosdot\
+      /losdilforpilramtirwintadbicdifrocwidbisdasmidlop\
+      /rilnardapmolsanlocnovsitnidtipsicropwitnatpanmin\
+      /ritpodmottamtolsavposnapnopsomfinfonbanmorworsip\
+      /ronnorbotwicsocwatdolmagpicdavbidbaltimtasmallig\
+      /sivtagpadsaldivdactansidfabtarmonranniswolmispal\
+      /lasdismaprabtobrollatlonnodnavfignomnibpagsopral\
+      /bilhaddocridmocpacravripfaltodtiltinhapmicfanpat\
+      /taclabmogsimsonpinlomrictapfirhasbosbatpochactid\
+      /havsaplindibhosdabbitbarracparloddosbortochilmac\
+      /tomdigfilfasmithobharmighinradmashalraglagfadtop\
+      /mophabnilnosmilfopfamdatnoldinhatnacrisfotribhoc\
+      /nimlarfitwalrapsarnalmoslandondanladdovrivbacpol\
+      /laptalpitnambonrostonfodponsovnocsorlavmatmipfip'
+      ^=  dex                                           ::  suffix syllables
+      'zodnecbudwessevpersutletfulpensytdurwepserwylsun\
+      /rypsyxdyrnuphebpeglupdepdysputlughecryttyvsydnex\
+      /lunmeplutseppesdelsulpedtemledtulmetwenbynhexfeb\
+      /pyldulhetmevruttylwydtepbesdexsefwycburderneppur\
+      /rysrebdennutsubpetrulsynregtydsupsemwynrecmegnet\
+      /secmulnymtevwebsummutnyxrextebfushepbenmuswyxsym\
+      /selrucdecwexsyrwetdylmynmesdetbetbeltuxtugmyrpel\
+      /syptermebsetdutdegtexsurfeltudnuxruxrenwytnubmed\
+      /lytdusnebrumtynseglyxpunresredfunrevrefmectedrus\
+      /bexlebduxrynnumpyxrygryxfeptyrtustyclegnemfermer\
+      /tenlusnussyltecmexpubrymtucfyllepdebbermughuttun\
+      /bylsudpemdevlurdefbusbeprunmelpexdytbyttyplevmyl\
+      /wedducfurfexnulluclennerlexrupnedlecrydlydfenwel\
+      /nydhusrelrudneshesfetdesretdunlernyrsebhulryllud\
+      /remlysfynwerrycsugnysnyllyndyndemluxfedsedbecmun\
+      /lyrtesmudnytbyrsenwegfyrmurtelreptegpecnelnevfes'
+  |%
+  ++  ins  ~/  %ins                                     ::  parse prefix
+           |=  a=@tas
+           =+  b=0
+           |-  ^-  (unit @)
+           ?:(=(256 b) ~ ?:(=(a (tos b)) [~ b] $(b +(b))))
+  ++  ind  ~/  %ind                                     ::  parse suffix
+           |=  a=@tas
+           =+  b=0
+           |-  ^-  (unit @)
+           ?:(=(256 b) ~ ?:(=(a (tod b)) [~ b] $(b +(b))))
+  ++  tos  ~/  %tos                                     ::  fetch prefix
+           |=(a=@ ?>((lth a 256) (cut 3 [(mul 3 a) 3] sis)))
+  ++  tod  ~/  %tod                                     ::  fetch suffix
+           |=(a=@ ?>((lth a 256) (cut 3 [(mul 3 a) 3] dex)))
+  --
+::
+++  fa                                                  ::  base58check
+  =+  key='123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
+  =/  yek=@ux  ~+
+      =-  yek:(roll (rip 3 key) -)
+      =+  [a=*char b=*@ yek=`@ux`(fil 3 256 0xff)]
+      |.
+      [+(b) (mix yek (lsh [3 `@u`a] (~(inv fe 3) b)))]
+  |%
+  ++  cha  |=(a=char `(unit @uF)`=+(b=(cut 3 [`@`a 1] yek) ?:(=(b 0xff) ~ `b)))
+  ++  tok
+    |=  a=@ux  ^-  @ux
+    =+  b=(pad a)
+    =-  (~(net fe 5) (end [3 4] (shay 32 -)))
+    (shay (add b (met 3 a)) (lsh [3 b] (swp 3 a)))
+  ::
+  ++  pad  |=(a=@ =+(b=(met 3 a) ?:((gte b 21) 0 (sub 21 b))))
+  ++  enc  |=(a=@ux `@ux`(mix (lsh [3 4] a) (tok a)))
+  ++  den
+    |=  a=@ux  ^-  (unit @ux)
+    =+  b=(rsh [3 4] a)
+    ?.  =((tok b) (end [3 4] a))
+      ~
+    `b
+  --
+::    4b: text processing
++|  %text-processing
+::
+++  at                                                  ::  basic printing
+  |_  a=@
+  ++  r
+    ?:  ?&  (gte (met 3 a) 2)
+            |-
+            ?:  =(0 a)
+              &
+            =+  vis=(end 3 a)
+            ?&  ?|(=('-' vis) ?&((gte vis 'a') (lte vis 'z')))
+                $(a (rsh 3 a))
+            ==
+        ==
+      rtam
+    ?:  (lte (met 3 a) 2)
+      rud
+    rux
+  ::
+  ++  rf    `tape`[?-(a %& '&', %| '|', * !!) ~]
+  ++  rn    `tape`[?>(=(0 a) '~') ~]
+  ++  rt    `tape`['\'' (weld (mesc (trip a)) `tape`['\'' ~])]
+  ++  rta   rt
+  ++  rtam  `tape`['%' (trip a)]
+  ++  rub   `tape`['0' 'b' (rum 2 ~ |=(b=@ (add '0' b)))]
+  ++  rud   (rum 10 ~ |=(b=@ (add '0' b)))
+  ++  rum
+    |=  [b=@ c=tape d=$-(@ @)]
+    ^-  tape
+    ?:  =(0 a)
+      [(d 0) c]
+    =+  e=0
+    |-  ^-  tape
+    ?:  =(0 a)
+      c
+    =+  f=&(!=(0 e) =(0 (mod e ?:(=(10 b) 3 4))))
+    %=  $
+      a  (div a b)
+      c  [(d (mod a b)) ?:(f [?:(=(10 b) ',' '-') c] c)]
+      e  +(e)
+    ==
+  ::
+  ++  rup
+    =+  b=(met 3 a)
+    ^-  tape
+    :-  '-'
+    |-  ^-  tape
+    ?:  (gth (met 5 a) 1)
+      %+  weld
+        $(a (rsh 5 a), b (sub b 4))
+      `tape`['-' '-' $(a (end 5 a), b 4)]
+    ?:  =(0 b)
+      ['~' ~]
+    ?:  (lte b 1)
+      (trip (tos:po a))
+    |-  ^-  tape
+    ?:  =(2 b)
+      =+  c=(rsh 3 a)
+      =+  d=(end 3 a)
+      (weld (trip (tod:po c)) (trip (tos:po (mix c d))))
+    =+  c=(rsh [3 2] a)
+    =+  d=(end [3 2] a)
+    (weld ^$(a c, b (met 3 c)) `tape`['-' $(a (mix c d), b 2)])
+  ::
+  ++  ruv
+    ^-  tape
+    :+  '0'
+      'v'
+    %^    rum
+        64
+      ~
+    |=  b=@
+    ?:  =(63 b)
+      '+'
+    ?:  =(62 b)
+      '-'
+    ?:((lth b 26) (add 65 b) ?:((lth b 52) (add 71 b) (sub b 4)))
+  ::
+  ++  rux  `tape`['0' 'x' (rum 16 ~ |=(b=@ (add b ?:((lth b 10) 48 87))))]
+  --
+++  cass                                                ::  lowercase
+  |=  vib=tape
+  ^-  tape
+  (turn vib |=(a=@ ?.(&((gte a 'A') (lte a 'Z')) a (add 32 a))))
+::
+++  cuss                                                ::  uppercase
+  |=  vib=tape
+  ^-  tape
+  (turn vib |=(a=@ ?.(&((gte a 'a') (lte a 'z')) a (sub a 32))))
+::
+++  crip  |=(a=tape `@t`(rap 3 a))                      ::  tape to cord
+::
+++  mesc                                                ::  ctrl code escape
+  |=  vib=tape
+  ^-  tape
+  ?~  vib
+    ~
+  ?:  =('\\' i.vib)
+    ['\\' '\\' $(vib t.vib)]
+  ?:  ?|((gth i.vib 126) (lth i.vib 32) =(`@`39 i.vib))
+    ['\\' (welp ~(rux at i.vib) '/' $(vib t.vib))]
+  [i.vib $(vib t.vib)]
+::
+++  runt                                                ::  prepend repeatedly
+  |=  [[a=@ b=@] c=tape]
+  ^-  tape
+  ?:  =(0 a)
+    c
+  [b $(a (dec a))]
+::
+++  sand                                                ::  atom sanity
+  |=  a=@ta
+  (flit (sane a))
+::
+++  sane                                                ::  atom sanity
+  |=  a=@ta
+  |=  b=@  ^-  ?
+  ?.  =(%t (end 3 a))
+    ::  XX more and better sanity
+    ::
+    &
+  =+  [inx=0 len=(met 3 b)]
+  ?:  =(%tas a)
+    |-  ^-  ?
+    ?:  =(inx len)  &
+    =+  cur=(cut 3 [inx 1] b)
+    ?&  ?|  &((gte cur 'a') (lte cur 'z'))
+            &(=('-' cur) !=(0 inx) !=(len inx))
+            &(&((gte cur '0') (lte cur '9')) !=(0 inx))
+        ==
+        $(inx +(inx))
+    ==
+  ?:  =(%ta a)
+    |-  ^-  ?
+    ?:  =(inx len)  &
+    =+  cur=(cut 3 [inx 1] b)
+    ?&  ?|  &((gte cur 'a') (lte cur 'z'))
+            &((gte cur '0') (lte cur '9'))
+            |(=('-' cur) =('~' cur) =('_' cur) =('.' cur))
+        ==
+        $(inx +(inx))
+    ==
+  |-  ^-  ?
+  ?:  =(inx len)  &
+  =+  cur=(cut 3 [inx 1] b)
+  ?:  &((lth cur 32) !=(10 cur))  |
+  =+  tef=(teff cur)
+  ?&  ?|  =(1 tef)
+          =+  i=1
+          |-  ^-  ?
+          ?|  =(i tef)
+              ?&  (gte (cut 3 [(add i inx) 1] b) 128)
+                  $(i +(i))
+      ==  ==  ==
+      $(inx (add inx tef))
+  ==
+::
+++  ruth                                                ::  biblical sanity
+  |=  [a=@ta b=*]
+  ^-  @
+  ?^  b  !!
+  ::  ?.  ((sane a) b)  !!
+  b
+::
+++  trim                                                ::  tape split
+  |=  [a=@ b=tape]
+  ^-  [p=tape q=tape]
+  ?~  b
+    [~ ~]
+  ?:  =(0 a)
+    [~ b]
+  =+  c=$(a (dec a), b t.b)
+  [[i.b p.c] q.c]
+::
+++  trip                                                ::  cord to tape
+  ~/  %trip
+  |=  a=@  ^-  tape
+  ?:  =(0 (met 3 a))
+    ~
+  [^-(@ta (end 3 a)) $(a (rsh 3 a))]
+::
+++  teff                                                ::  length utf8
+  |=  a=@t  ^-  @
+  =+  b=(end 3 a)
+  ?:  =(0 b)
+    ?>(=(`@`0 a) 0)
+  ?>  |((gte b 32) =(10 b))
+  ?:((lte b 127) 1 ?:((lte b 223) 2 ?:((lte b 239) 3 4)))
+::
+++  taft                                                ::  utf8 to utf32
+  |=  a=@t
+  ^-  @c
+  %+  rap  5
+  |-  ^-  (list @c)
+  =+  b=(teff a)
+  ?:  =(0 b)  ~
+  =+  ^=  c
+      %+  can  0
+      %+  turn
+        ^-  (list [p=@ q=@])
+        ?+  b  !!
+          %1  [[0 7] ~]
+          %2  [[8 6] [0 5] ~]
+          %3  [[16 6] [8 6] [0 4] ~]
+          %4  [[24 6] [16 6] [8 6] [0 3] ~]
+        ==
+      |=([p=@ q=@] [q (cut 0 [p q] a)])
+  ?>  =((tuft c) (end [3 b] a))
+  [c $(a (rsh [3 b] a))]
+::
+++  tuba                                                ::  utf8 to utf32 tape
+  |=  a=tape
+  ^-  (list @c)
+  (rip 5 (taft (rap 3 a)))                              ::  XX horrible
+::
+++  tufa                                                ::  utf32 to utf8 tape
+  |=  a=(list @c)
+  ^-  tape
+  ?~  a  ""
+  (weld (rip 3 (tuft i.a)) $(a t.a))
+::
+++  tuft                                                ::  utf32 to utf8 text
+  |=  a=@c
+  ^-  @t
+  %+  rap  3
+  |-  ^-  (list @)
+  ?:  =(`@`0 a)
+    ~
+  =+  b=(end 5 a)
+  =+  c=$(a (rsh 5 a))
+  ?:  (lte b 0x7f)
+    [b c]
+  ?:  (lte b 0x7ff)
+    :*  (mix 0b1100.0000 (cut 0 [6 5] b))
+        (mix 0b1000.0000 (end [0 6] b))
+        c
+    ==
+  ?:  (lte b 0xffff)
+    :*  (mix 0b1110.0000 (cut 0 [12 4] b))
+        (mix 0b1000.0000 (cut 0 [6 6] b))
+        (mix 0b1000.0000 (end [0 6] b))
+        c
+    ==
+  :*  (mix 0b1111.0000 (cut 0 [18 3] b))
+      (mix 0b1000.0000 (cut 0 [12 6] b))
+      (mix 0b1000.0000 (cut 0 [6 6] b))
+      (mix 0b1000.0000 (end [0 6] b))
+      c
+  ==
+::
+++  wack                                                ::  knot escape
+  |=  a=@ta
+  ^-  @ta
+  =+  b=(rip 3 a)
+  %+  rap  3
+  |-  ^-  tape
+  ?~  b
+    ~
+  ?:  =('~' i.b)  ['~' '~' $(b t.b)]
+  ?:  =('_' i.b)  ['~' '-' $(b t.b)]
+  [i.b $(b t.b)]
+::
+++  wick                                                ::  knot unescape
+  |=  a=@
+  ^-  (unit @ta)
+  =+  b=(rip 3 a)
+  =-  ?^(b ~ (some (rap 3 (flop c))))
+  =|  c=tape
+  |-  ^-  [b=tape c=tape]
+  ?~  b  [~ c]
+  ?.  =('~' i.b)
+    $(b t.b, c [i.b c])
+  ?~  t.b  [b ~]
+  ?-  i.t.b
+    %'~'  $(b t.t.b, c ['~' c])
+    %'-'  $(b t.t.b, c ['_' c])
+    @     [b ~]
+  ==
+::
+++  woad                                                ::  cord unescape
+  |=  a=@ta
+  ^-  @t
+  %+  rap  3
+  |-  ^-  (list @)
+  ?:  =(`@`0 a)
+    ~
+  =+  b=(end 3 a)
+  =+  c=(rsh 3 a)
+  ?:  =('.' b)
+    [' ' $(a c)]
+  ?.  =('~' b)
+    [b $(a c)]
+  =>  .(b (end 3 c), c (rsh 3 c))
+  ?+  b  =-  (weld (rip 3 (tuft p.d)) $(a q.d))
+         ^=  d
+         =+  d=0
+         |-  ^-  [p=@ q=@]
+         ?:  =('.' b)
+           [d c]
+         ?<  =(0 c)
+         %=    $
+            b  (end 3 c)
+            c  (rsh 3 c)
+            d  %+  add  (mul 16 d)
+               %+  sub  b
+               ?:  &((gte b '0') (lte b '9'))  48
+               ?>(&((gte b 'a') (lte b 'z')) 87)
+         ==
+    %'.'  ['.' $(a c)]
+    %'~'  ['~' $(a c)]
+  ==
+::
+++  wood                                                ::  cord escape
+  |=  a=@t
+  ^-  @ta
+  %+  rap  3
+  |-  ^-  (list @)
+  ?:  =(`@`0 a)
+    ~
+  =+  b=(teff a)
+  =+  c=(taft (end [3 b] a))
+  =+  d=$(a (rsh [3 b] a))
+  ?:  ?|  &((gte c 'a') (lte c 'z'))
+          &((gte c '0') (lte c '9'))
+          =(`@`'-' c)
+      ==
+    [c d]
+  ?+  c
+    :-  '~'
+    =+  e=(met 2 c)
+    |-  ^-  tape
+    ?:  =(0 e)
+      ['.' d]
+    =.  e  (dec e)
+    =+  f=(rsh [2 e] c)
+    [(add ?:((lte f 9) 48 87) f) $(c (end [2 e] c))]
+  ::
+    %' '  ['.' d]
+    %'.'  ['~' '.' d]
+    %'~'  ['~' '~' d]
+  ==
+::
+::  4c: tank printer
++|  %tank-printer
+::
+++  wash                                                ::  render tank at width
+  |=  [[tab=@ edg=@] tac=tank]  ^-  wall
+  (~(win re tac) tab edg)
+::
+::  +re: tank renderer
+::
+++  re
+  |_  tac=tank
+  ::  +ram: render a tank to one line (flat)
+  ::
+  ++  ram
+    ^-  tape
+    ?@  tac
+      (trip tac)
+    ?-    -.tac
+        %leaf  p.tac
+    ::
+    ::  flat %palm rendered as %rose with welded openers
+    ::
+        %palm
+      =*  mid  p.p.tac
+      =*  for  (weld q.p.tac r.p.tac)
+      =*  end  s.p.tac
+      ram(tac [%rose [mid for end] q.tac])
+    ::
+    ::  flat %rose rendered with open/mid/close
+    ::
+        %rose
+      =*  mid  p.p.tac
+      =*  for  q.p.tac
+      =*  end  r.p.tac
+      =*  lit  q.tac
+      %+  weld
+        for
+      |-  ^-  tape
+      ?~  lit
+        end
+      %+  weld
+        ram(tac i.lit)
+      =*  voz  $(lit t.lit)
+      ?~(t.lit voz (weld mid voz))
+    ==
+  ::  +win: render a tank to multiple lines (tall)
+  ::
+  ::    indented by .tab, soft-wrapped at .edg
+  ::
+  ++  win
+    |=  [tab=@ud edg=@ud]
+    ::  output stack
+    ::
+    =|  lug=wall
+    |^  ^-  wall
+        ?@  tac
+          (rig (trip tac))
+        ?-    -.tac
+            %leaf  (rig p.tac)
+        ::
+            %palm
+          =/  hom  ram
+          ?:  (lte (lent hom) (sub edg tab))
+            (rig hom)
+          ::
+          =*  for  q.p.tac
+          =*  lit  q.tac
+          ?~  lit
+            (rig for)
+          ?~  t.lit
+            =:  tab  (add 2 tab)
+                lug  $(tac i.lit)
+              ==
+            (rig for)
+          ::
+          =>  .(lit `(list tank)`lit)
+          =/  lyn  (mul 2 (lent lit))
+          =.  lug
+            |-  ^-  wall
+            ?~  lit
+              lug
+            =/  nyl  (sub lyn 2)
+            %=  ^$
+              tac  i.lit
+              tab  (add tab nyl)
+              lug  $(lit t.lit, lyn nyl)
+            ==
+          (wig for)
+        ::
+            %rose
+          =/  hom  ram
+          ?:  (lte (lent hom) (sub edg tab))
+            (rig hom)
+          ::
+          =*  for  q.p.tac
+          =*  end  r.p.tac
+          =*  lit  q.tac
+          =.  lug
+            |-  ^-  wall
+            ?~  lit
+              ?~(end lug (rig end))
+            %=  ^$
+              tac  i.lit
+              tab  (mod (add 2 tab) (mul 2 (div edg 3)))
+              lug  $(lit t.lit)
+            ==
+          ?~(for lug (wig for))
+        ==
+    ::  +rig: indent tape and cons with output stack
+    ::
+    ++  rig
+      |=  hom=tape
+      ^-  wall
+      [(runt [tab ' '] hom) lug]
+    ::  +wig: indent tape and cons with output stack
+    ::
+    ::    joined with the top line if whitespace/indentation allow
+    ::
+    ++  wig
+      |=  hom=tape
+      ^-  wall
+      ?~  lug
+        (rig hom)
+      =/  wug  :(add 1 tab (lent hom))
+      ?.  =+  mir=i.lug
+          |-  ^-  ?
+          ?~  mir  |
+          ?|  =(0 wug)
+              ?&(=(' ' i.mir) $(mir t.mir, wug (dec wug)))
+          ==
+        (rig hom)       :: ^ XX regular form?
+      :_  t.lug
+      %+  runt  [tab ' ']
+      (weld hom `tape`[' ' (slag wug i.lug)])
+    --
+  --
+++  show                                                ::  XX deprecated!
+  |=  vem=*
+  |^  ^-  tank
+      ?:  ?=(@ vem)
+        [%leaf (mesc (trip vem))]
+      ?-    vem
+          [s=~ c=*]
+        [%leaf '\'' (weld (mesc (tape +.vem)) `tape`['\'' ~])]
+      ::
+          [s=%a c=@]        [%leaf (mesc (trip c.vem))]
+          [s=%b c=*]        (shop c.vem |=(a=@ ~(rub at a)))
+          [s=[%c p=@] c=*]
+        :+  %palm
+          [['.' ~] ['-' ~] ~ ~]
+        [[%leaf (mesc (trip p.s.vem))] $(vem c.vem) ~]
+      ::
+          [s=%d c=*]        (shop c.vem |=(a=@ ~(rud at a)))
+          [s=%k c=*]        (tank c.vem)
+          [s=%h c=*]
+        :+  %rose
+          [['/' ~] ['/' ~] ~]
+        =+  yol=((list @ta) c.vem)
+        (turn yol |=(a=@ta [%leaf (trip a)]))
+      ::
+          [s=%l c=*]        (shol c.vem)
+          [s=%o c=*]
+        %=    $
+            vem
+          :-  [%m '%h::[%d %d].[%d %d]>']
+          [-.c.vem +<-.c.vem +<+.c.vem +>-.c.vem +>+.c.vem ~]
+        ==
+      ::
+          [s=%p c=*]        (shop c.vem |=(a=@ ~(rup at a)))
+          [s=%q c=*]        (shop c.vem |=(a=@ ~(r at a)))
+          [s=%r c=*]        $(vem [[%r ' ' '{' '}'] c.vem])
+          [s=%t c=*]        (shop c.vem |=(a=@ ~(rt at a)))
+          [s=%v c=*]        (shop c.vem |=(a=@ ~(ruv at a)))
+          [s=%x c=*]        (shop c.vem |=(a=@ ~(rux at a)))
+          [s=[%m p=@] c=*]  (shep p.s.vem c.vem)
+          [s=[%r p=@] c=*]
+        $(vem [[%r ' ' (cut 3 [0 1] p.s.vem) (cut 3 [1 1] p.s.vem)] c.vem])
+      ::
+          [s=[%r p=@ q=@ r=@] c=*]
+        :+  %rose
+          :*  p=(mesc (trip p.s.vem))
+              q=(mesc (trip q.s.vem))
+              r=(mesc (trip r.s.vem))
+          ==
+        |-  ^-  (list tank)
+        ?@  c.vem
+          ~
+        [^$(vem -.c.vem) $(c.vem +.c.vem)]
+      ::
+          [s=%z c=*]        $(vem [[%r %$ %$ %$] c.vem])
+          *                 !!
+      ==
+  ++  shep
+    |=  [fom=@ gar=*]
+    ^-  tank
+    =+  l=(met 3 fom)
+    =+  i=0
+    :-  %leaf
+    |-  ^-  tape
+    ?:  (gte i l)
+      ~
+    =+  c=(cut 3 [i 1] fom)
+    ?.  =(37 c)
+      (weld (mesc [c ~]) $(i +(i)))
+    =+  d=(cut 3 [+(i) 1] fom)
+    ?.  .?(gar)
+      ['\\' '#' $(i (add 2 i))]
+    (weld ~(ram re (show d -.gar)) $(i (add 2 i), gar +.gar))
+  ::
+  ++  shop
+    |=  [aug=* vel=$-(a=@ tape)]
+    ^-  tank
+    ?:  ?=(@ aug)
+      [%leaf (vel aug)]
+    :+  %rose
+      [[' ' ~] ['[' ~] [']' ~]]
+    =>  .(aug `*`aug)
+    |-  ^-  (list tank)
+    ?:  ?=(@ aug)
+      [^$ ~]
+    [^$(aug -.aug) $(aug +.aug)]
+  ::
+  ++  shol
+    |=  lim=*
+    :+  %rose
+      [['.' ~] ~ ~]
+    |-    ^-  (list tank)
+    ?:  ?=(@ lim)  ~
+    :_  $(lim +.lim)
+    ?+  -.lim  (show '#')
+        ~   (show '$')
+        c=@  (show c.lim)
+        [%& %1]  (show '.')
+        [%& c=@]
+      [%leaf '+' ~(rud at c.lim)]
+    ::
+        [%| @ ~]  (show ',')
+        [%| n=@ ~ c=@]
+      [%leaf (weld (reap n.lim '^') ?~(c.lim "$" (trip c.lim)))]
+    ==
+  --
+::
+::    4d: parsing (tracing)
++|  %parsing-tracing
+::
+++  last  |=  [zyc=hair naz=hair]                       ::  farther trace
+          ^-  hair
+          ?:  =(p.zyc p.naz)
+            ?:((gth q.zyc q.naz) zyc naz)
+          ?:((gth p.zyc p.naz) zyc naz)
+::
+++  lust  |=  [weq=char naz=hair]                       ::  detect newline
+          ^-  hair
+          ?:(=(`@`10 weq) [+(p.naz) 1] [p.naz +(q.naz)])
+::
+::    4e: parsing (combinators)
++|  %parsing-combinators
+::
+++  bend                                                ::  conditional comp
+  ~/  %bend
+  |*  raq=_|*([a=* b=*] [~ u=[a b]])
+  ~/  %fun
+  |*  [vex=edge sab=rule]
+  ?~  q.vex
+    vex
+  =+  yit=(sab q.u.q.vex)
+  =+  yur=(last p.vex p.yit)
+  ?~  q.yit
+    [p=yur q=q.vex]
+  =+  vux=(raq p.u.q.vex p.u.q.yit)
+  ?~  vux
+    [p=yur q=q.vex]
+  [p=yur q=[~ u=[p=u.vux q=q.u.q.yit]]]
+::
+++  comp
+  ~/  %comp
+  |*  raq=_|*([a=* b=*] [a b])                           ::  arbitrary compose
+  ~/  %fun
+  |*  [vex=edge sab=rule]
+  ~!  +<
+  ?~  q.vex
+    vex
+  =+  yit=(sab q.u.q.vex)
+  =+  yur=(last p.vex p.yit)
+  ?~  q.yit
+    [p=yur q=q.yit]
+  [p=yur q=[~ u=[p=(raq p.u.q.vex p.u.q.yit) q=q.u.q.yit]]]
+::
+++  fail  |=(tub=nail [p=p.tub q=~])                    ::  never parse
+++  glue                                                ::  add rule
+  ~/  %glue
+  |*  bus=rule
+  ~/  %fun
+  |*  [vex=edge sab=rule]
+  (plug vex ;~(pfix bus sab))
+::
+++  less                                                ::  no first and second
+  |*  [vex=edge sab=rule]
+  ?~  q.vex
+    =+  roq=(sab)
+    [p=(last p.vex p.roq) q=q.roq]
+  (fail +<.sab)
+::
+++  pfix                                                ::  discard first rule
+  ~/  %pfix
+  |*  sam=[vex=edge sab=rule]
+  %.  sam
+  (comp |*([a=* b=*] b))
+::
+++  plug                                                ::  first then second
+  ~/  %plug
+  |*  [vex=edge sab=rule]
+  ?~  q.vex
+    vex
+  =+  yit=(sab q.u.q.vex)
+  =+  yur=(last p.vex p.yit)
+  ?~  q.yit
+    [p=yur q=q.yit]
+  [p=yur q=[~ u=[p=[p.u.q.vex p.u.q.yit] q=q.u.q.yit]]]
+::
+++  pose                                                ::  first or second
+  ~/  %pose
+  |*  [vex=edge sab=rule]
+  ?~  q.vex
+    =+  roq=(sab)
+    [p=(last p.vex p.roq) q=q.roq]
+  vex
+::
+++  simu                                                ::  first and second
+  |*  [vex=edge sab=rule]
+  ?~  q.vex
+    vex
+  =+  roq=(sab)
+  roq
+::
+++  sfix                                                ::  discard second rule
+  ~/  %sfix
+  |*  sam=[vex=edge sab=rule]
+  %.  sam
+  (comp |*([a=* b=*] a))
+::
+::    4f: parsing (rule builders)
++|  %parsing-rule-builders
+::
+++  bass                                                ::  leftmost base
+  |*  [wuc=@ tyd=rule]
+  %+  cook
+    |=  waq=(list @)
+    %+  roll
+      waq
+    =|([p=@ q=@] |.((add p (mul wuc q))))
+  tyd
+::
+++  boss                                                ::  rightmost base
+  |*  [wuc=@ tyd=rule]
+  %+  cook
+    |=  waq=(list @)
+    %+  reel
+      waq
+    =|([p=@ q=@] |.((add p (mul wuc q))))
+  tyd
+::
+++  cold                                                ::  replace w+ constant
+  ~/  %cold
+  |*  [cus=* sef=rule]
+  ~/  %fun
+  |=  tub=nail
+  =+  vex=(sef tub)
+  ?~  q.vex
+    vex
+  [p=p.vex q=[~ u=[p=cus q=q.u.q.vex]]]
+::
+++  cook                                                ::  apply gate
+  ~/  %cook
+  |*  [poq=gate sef=rule]
+  ~/  %fun
+  |=  tub=nail
+  =+  vex=(sef tub)
+  ?~  q.vex
+    vex
+  [p=p.vex q=[~ u=[p=(poq p.u.q.vex) q=q.u.q.vex]]]
+::
+++  easy                                                ::  always parse
+  ~/  %easy
+  |*  huf=*
+  ~/  %fun
+  |=  tub=nail
+  ^-  (like _huf)
+  [p=p.tub q=[~ u=[p=huf q=tub]]]
+::
+++  fuss
+  |=  [sic=@t non=@t]
+  ;~(pose (cold %& (jest sic)) (cold %| (jest non)))
+::
+++  full                                                ::  has to fully parse
+  |*  sef=rule
+  |=  tub=nail
+  =+  vex=(sef tub)
+  ?~(q.vex vex ?:(=(~ q.q.u.q.vex) vex [p=p.vex q=~]))
+::
+++  funk                                                ::  add to tape first
+  |*  [pre=tape sef=rule]
+  |=  tub=nail
+  (sef p.tub (weld pre q.tub))
+::
+++  here                                                ::  place-based apply
+  ~/  %here
+  |*  [hez=_|=([a=pint b=*] [a b]) sef=rule]
+  ~/  %fun
+  |=  tub=nail
+  =+  vex=(sef tub)
+  ?~  q.vex
+    vex
+  [p=p.vex q=[~ u=[p=(hez [p.tub p.q.u.q.vex] p.u.q.vex) q=q.u.q.vex]]]
+::
+++  inde  |*  sef=rule                                  :: indentation block
+  |=  nail  ^+  (sef)
+  =+  [har tap]=[p q]:+<
+  =+  lev=(fil 3 (dec q.har) ' ')
+  =+  eol=(just `@t`10)
+  =+  =-  roq=((star ;~(pose prn ;~(sfix eol (jest lev)) -)) har tap)
+      ;~(simu ;~(plug eol eol) eol)
+  ?~  q.roq  roq
+  =+  vex=(sef har(q 1) p.u.q.roq)
+  =+  fur=p.vex(q (add (dec q.har) q.p.vex))
+  ?~  q.vex  vex(p fur)
+  =-  vex(p fur, u.q -)
+  :+  &3.vex
+    &4.vex(q.p (add (dec q.har) q.p.&4.vex))
+  =+  res=|4.vex
+  |-  ?~  res  |4.roq
+  ?.  =(10 -.res)  [-.res $(res +.res)]
+  (welp [`@t`10 (trip lev)] $(res +.res))
+::
+++  ifix
+  |*  [fel=[rule rule] hof=rule]
+  ~!  +<
+  ~!  +<:-.fel
+  ~!  +<:+.fel
+  ;~(pfix -.fel ;~(sfix hof +.fel))
+::
+++  jest                                                ::  match a cord
+  |=  daf=@t
+  |=  tub=nail
+  =+  fad=daf
+  |-  ^-  (like @t)
+  ?:  =(`@`0 daf)
+    [p=p.tub q=[~ u=[p=fad q=tub]]]
+  ?:  |(?=(~ q.tub) !=((end 3 daf) i.q.tub))
+    (fail tub)
+  $(p.tub (lust i.q.tub p.tub), q.tub t.q.tub, daf (rsh 3 daf))
+::
+++  just                                                ::  XX redundant, jest
+  ~/  %just                                             ::  match a char
+  |=  daf=char
+  ~/  %fun
+  |=  tub=nail
+  ^-  (like char)
+  ?~  q.tub
+    (fail tub)
+  ?.  =(daf i.q.tub)
+    (fail tub)
+  (next tub)
+::
+++  knee                                                ::  callbacks
+  |*  [gar=* sef=_|.(*rule)]
+  |=  tub=nail
+  ^-  (like _gar)
+  ((sef) tub)
+::
+++  mask                                                ::  match char in set
+  ~/  %mask
+  |=  bud=(list char)
+  ~/  %fun
+  |=  tub=nail
+  ^-  (like char)
+  ?~  q.tub
+    (fail tub)
+  ?.  (lien bud |=(a=char =(i.q.tub a)))
+    (fail tub)
+  (next tub)
+::
+++  more                                                ::  separated, *
+  |*  [bus=rule fel=rule]
+  ;~(pose (most bus fel) (easy ~))
+::
+++  most                                                ::  separated, +
+  |*  [bus=rule fel=rule]
+  ;~(plug fel (star ;~(pfix bus fel)))
+::
+++  next                                                ::  consume a char
+  |=  tub=nail
+  ^-  (like char)
+  ?~  q.tub
+    (fail tub)
+  =+  zac=(lust i.q.tub p.tub)
+  [zac [~ i.q.tub [zac t.q.tub]]]
+::
+++  perk                                                ::  parse cube fork
+  |*  a=(pole @tas)
+  ?~  a  fail
+  ;~  pose
+    (cold -.a (jest -.a))
+    $(a +.a)
+  ==
+::
+++  pick                                                ::  rule for ++each
+  |*  [a=rule b=rule]
+  ;~  pose
+    (stag %& a)
+    (stag %| b)
+  ==
+++  plus  |*(fel=rule ;~(plug fel (star fel)))          ::
+++  punt  |*([a=rule] ;~(pose (stag ~ a) (easy ~)))     ::
+++  sear                                                ::  conditional cook
+  |*  [pyq=$-(* (unit)) sef=rule]
+  |=  tub=nail
+  =+  vex=(sef tub)
+  ?~  q.vex
+    vex
+  =+  gey=(pyq p.u.q.vex)
+  ?~  gey
+    [p=p.vex q=~]
+  [p=p.vex q=[~ u=[p=u.gey q=q.u.q.vex]]]
+::
+++  shim                                                ::  match char in range
+  ~/  %shim
+  |=  [les=@ mos=@]
+  ~/  %fun
+  |=  tub=nail
+  ^-  (like char)
+  ?~  q.tub
+    (fail tub)
+  ?.  ?&((gte i.q.tub les) (lte i.q.tub mos))
+    (fail tub)
+  (next tub)
+::
+++  stag                                                ::  add a label
+  ~/  %stag
+  |*  [gob=* sef=rule]
+  ~/  %fun
+  |=  tub=nail
+  =+  vex=(sef tub)
+  ?~  q.vex
+    vex
+  [p=p.vex q=[~ u=[p=[gob p.u.q.vex] q=q.u.q.vex]]]
+::
+++  stet                                                ::
+  |*  leh=(list [?(@ [@ @]) rule])
+  |-
+  ?~  leh
+    ~
+  [i=[p=-.i.leh q=+.i.leh] t=$(leh t.leh)]
+::
+++  stew                                                ::  switch by first char
+  ~/  %stew
+  |*  leh=(list [p=?(@ [@ @]) q=rule])                  ::  char+range keys
+  =+  ^=  wor                                           ::  range complete lth
+      |=  [ort=?(@ [@ @]) wan=?(@ [@ @])]
+      ?@  ort
+        ?@(wan (lth ort wan) (lth ort -.wan))
+      ?@(wan (lth +.ort wan) (lth +.ort -.wan))
+  =+  ^=  hel                                           ::  build parser map
+      =+  hel=`(tree _?>(?=(^ leh) i.leh))`~
+      |-  ^+  hel
+      ?~  leh
+        ~
+      =+  yal=$(leh t.leh)
+      |-  ^+  hel
+      ?~  yal
+        [i.leh ~ ~]
+      ?:  (wor p.i.leh p.n.yal)
+        =+  nuc=$(yal l.yal)
+        ?>  ?=(^ nuc)
+        ?:  (mor p.n.yal p.n.nuc)
+          [n.yal nuc r.yal]
+        [n.nuc l.nuc [n.yal r.nuc r.yal]]
+      =+  nuc=$(yal r.yal)
+      ?>  ?=(^ nuc)
+      ?:  (mor p.n.yal p.n.nuc)
+        [n.yal l.yal nuc]
+      [n.nuc [n.yal l.yal l.nuc] r.nuc]
+  ~%  %fun  ..^$  ~
+  |=  tub=nail
+  ?~  q.tub
+    (fail tub)
+  |-
+  ?~  hel
+    (fail tub)
+  ?:  ?@  p.n.hel
+        =(p.n.hel i.q.tub)
+      ?&((gte i.q.tub -.p.n.hel) (lte i.q.tub +.p.n.hel))
+    ::  (q.n.hel [(lust i.q.tub p.tub) t.q.tub])
+    (q.n.hel tub)
+  ?:  (wor i.q.tub p.n.hel)
+    $(hel l.hel)
+  $(hel r.hel)
+::
+++  slug                                                ::
+  |*  raq=_=>(~ |*([a=* b=*] [a b]))
+  |*  [bus=rule fel=rule]
+  ;~((comp raq) fel (stir +<+.raq raq ;~(pfix bus fel)))
+::
+++  star                                                ::  0 or more times
+  |*  fel=rule
+  (stir `(list _(wonk *fel))`~ |*([a=* b=*] [a b]) fel)
+::
+++  stir
+  ~/  %stir
+  |*  [rud=* raq=_=>(~ |*([a=* b=*] [a b])) fel=rule]
+  ~/  %fun
+  |=  tub=nail
+  ^-  (like _rud)
+  ::
+  ::  lef: successful interim parse results (per .fel)
+  ::  wag: initial accumulator (.rud in .tub at farthest success)
+  ::
+  =+  ^=  [lef wag]
+    =|  lef=(list _(fel tub))
+    |-  ^-  [_lef (pair hair [~ u=(pair _rud nail)])]
+    =+  vex=(fel tub)
+    ?~  q.vex
+      :-  lef
+      [p.vex [~ rud tub]]
+    $(lef [vex lef], tub q.u.q.vex)
+  ::
+  ::  fold .lef into .wag, combining results with .raq
+  ::
+  %+  roll  lef
+  |=  _[vex=(fel tub) wag=wag]  :: q.vex is always (some)
+  ^+  wag
+  :-  (last p.vex p.wag)
+  [~ (raq p.u.+.q.vex p.u.q.wag) q.u.q.wag]
+::
+++  stun                                                ::  parse several times
+  ~/  %stun
+  |*  [lig=[@ @] fel=rule]
+  |=  tub=nail
+  ^-  (like (list _(wonk (fel))))
+  ?:  =(0 +.lig)
+    [p.tub [~ ~ tub]]
+  =+  vex=(fel tub)
+  ?~  q.vex
+    ?:  =(0 -.lig)
+      [p.vex [~ ~ tub]]
+    vex
+  =+  ^=  wag  %=  $
+                 -.lig  ?:(=(0 -.lig) 0 (dec -.lig))
+                 +.lig  ?:(=(0 +.lig) 0 (dec +.lig))
+                 tub  q.u.q.vex
+               ==
+  ?~  q.wag
+    wag
+  [p.wag [~ [p.u.q.vex p.u.q.wag] q.u.q.wag]]
+::
+::    4g: parsing (outside caller)
++|  %parsing-outside-caller
+::
+++  rash  |*([naf=@ sab=rule] (scan (trip naf) sab))
+++  rose  |*  [los=tape sab=rule]
+          =+  vex=(sab [[1 1] los])
+          =+  len=(lent los)
+          ?.  =(+(len) q.p.vex)  [%| p=(dec q.p.vex)]
+          ?~  q.vex
+            [%& p=~]
+          [%& p=[~ u=p.u.q.vex]]
+++  rush  |*([naf=@ sab=rule] (rust (trip naf) sab))
+++  rust  |*  [los=tape sab=rule]
+          =+  vex=((full sab) [[1 1] los])
+          ?~(q.vex ~ [~ u=p.u.q.vex])
+++  scan  |*  [los=tape sab=rule]
+          =+  vex=((full sab) [[1 1] los])
+          ?~  q.vex
+            ~_  (show [%m '{%d %d}'] p.p.vex q.p.vex ~)
+            ~_(leaf+"syntax error" !!)
+          p.u.q.vex
+::
+::    4h: parsing (ascii glyphs)
++|  %parsing-ascii-glyphs
+::
+++  ace  (just ' ')                                     ::  spACE
+++  bar  (just '|')                                     ::  vertical BAR
+++  bas  (just '\\')                                    ::  Back Slash (escaped)
+++  buc  (just '$')                                     ::  dollars BUCks
+++  cab  (just '_')                                     ::  CABoose
+++  cen  (just '%')                                     ::  perCENt
+++  col  (just ':')                                     ::  COLon
+++  com  (just ',')                                     ::  COMma
+++  doq  (just '"')                                     ::  Double Quote
+++  dot  (just '.')                                     ::  dot dot dot ...
+++  fas  (just '/')                                     ::  Forward Slash
+++  gal  (just '<')                                     ::  Greater Left
+++  gar  (just '>')                                     ::  Greater Right
+++  hax  (just '#')                                     ::  Hash
+++  hep  (just '-')                                     ::  HyPhen
+++  kel  (just '{')                                     ::  Curly Left
+++  ker  (just '}')                                     ::  Curly Right
+++  ket  (just '^')                                     ::  CareT
+++  lus  (just '+')                                     ::  pLUS
+++  mic  (just ';')                                     ::  seMIColon
+++  pal  (just '(')                                     ::  Paren Left
+++  pam  (just '&')                                     ::  AMPersand pampersand
+++  par  (just ')')                                     ::  Paren Right
+++  pat  (just '@')                                     ::  AT pat
+++  sel  (just '[')                                     ::  Square Left
+++  ser  (just ']')                                     ::  Square Right
+++  sig  (just '~')                                     ::  SIGnature squiggle
+++  soq  (just '\'')                                    ::  Single Quote
+++  tar  (just '*')                                     ::  sTAR
+++  tic  (just '`')                                     ::  backTiCk
+++  tis  (just '=')                                     ::  'tis tis, it is
+++  wut  (just '?')                                     ::  wut, what?
+++  zap  (just '!')                                     ::  zap! bang! crash!!
+::
+::    4i: parsing (useful idioms)
++|  %parsing-useful-idioms
+::
+++  alf  ;~(pose low hig)                               ::  alphabetic
+++  aln  ;~(pose low hig nud)                           ::  alphanumeric
+++  alp  ;~(pose low hig nud hep)                       ::  alphanumeric and -
+++  bet  ;~(pose (cold 2 hep) (cold 3 lus))             ::  axis syntax - +
+++  bin  (bass 2 (most gon but))                        ::  binary to atom
+++  but  (cook |=(a=@ (sub a '0')) (shim '0' '1'))      ::  binary digit
+++  cit  (cook |=(a=@ (sub a '0')) (shim '0' '7'))      ::  octal digit
+++  dem  (bass 10 (most gon dit))                       ::  decimal to atom
+++  dit  (cook |=(a=@ (sub a '0')) (shim '0' '9'))      ::  decimal digit
+++  dog  ;~(plug dot gay)                               ::  .  number separator
+++  dof  ;~(plug hep gay)                               ::  - @q separator
+++  doh  ;~(plug ;~(plug hep hep) gay)                  ::  --  phon separator
+++  dun  (cold ~ ;~(plug hep hep))                      ::  -- (stop) to ~
+++  duz  (cold ~ ;~(plug tis tis))                      ::  == (stet) to ~
+++  gah  (mask [`@`10 ' ' ~])                           ::  newline or ace
+++  gap  (cold ~ ;~(plug gaq (star ;~(pose vul gah))))  ::  plural space
+++  gaq  ;~  pose                                       ::  end of line
+             (just `@`10)
+             ;~(plug gah ;~(pose gah vul))
+             vul
+         ==
+++  gaw  (cold ~ (star ;~(pose vul gah)))               ::  classic white
+++  gay  ;~(pose gap (easy ~))                          ::
+++  gon  ;~(pose ;~(plug bas gay fas) (easy ~))         ::  long numbers \ /
+++  gul  ;~(pose (cold 2 gal) (cold 3 gar))             ::  axis syntax < >
+++  hex  (bass 16 (most gon hit))                       ::  hex to atom
+++  hig  (shim 'A' 'Z')                                 ::  uppercase
+++  hit  ;~  pose                                       ::  hex digits
+           dit
+           (cook |=(a=char (sub a 87)) (shim 'a' 'f'))
+           (cook |=(a=char (sub a 55)) (shim 'A' 'F'))
+         ==
+++  iny                                                 :: indentation block
+  |*  sef=rule
+  |=  nail  ^+  (sef)
+  =+  [har tap]=[p q]:+<
+  =+  lev=(fil 3 (dec q.har) ' ')
+  =+  eol=(just `@t`10)
+  =+  =-  roq=((star ;~(pose prn ;~(sfix eol (jest lev)) -)) har tap)
+      ;~(simu ;~(plug eol eol) eol)
+  ?~  q.roq  roq
+  =+  vex=(sef har(q 1) p.u.q.roq)
+  =+  fur=p.vex(q (add (dec q.har) q.p.vex))
+  ?~  q.vex  vex(p fur)
+  =-  vex(p fur, u.q -)
+  :+  &3.vex
+    &4.vex(q.p (add (dec q.har) q.p.&4.vex))
+  =+  res=|4.vex
+  |-  ?~  res  |4.roq
+  ?.  =(10 -.res)  [-.res $(res +.res)]
+  (welp [`@t`10 (trip lev)] $(res +.res))
+::
+++  low  (shim 'a' 'z')                                 ::  lowercase
+++  mes  %+  cook                                       ::  hexbyte
+           |=([a=@ b=@] (add (mul 16 a) b))
+         ;~(plug hit hit)
+++  nix  (boss 256 (star ;~(pose aln cab)))             ::
+++  nud  (shim '0' '9')                                 ::  numeric
+++  prn  ;~(less (just `@`127) (shim 32 256))           ::  non-control
+++  qat  ;~  pose                                       ::  chars in blockcord
+             prn
+             ;~(less ;~(plug (just `@`10) soz) (just `@`10))
+         ==
+++  qit  ;~  pose                                       ::  chars in a cord
+             ;~(less bas soq prn)
+             ;~(pfix bas ;~(pose bas soq mes))          ::  escape chars
+         ==
+++  qut  ;~  simu  soq                                  ::  cord
+           ;~  pose
+             ;~  less  soz
+               (ifix [soq soq] (boss 256 (more gon qit)))
+             ==
+             =+  hed=;~(pose ;~(plug (plus ace) vul) (just '\0a'))
+             %-  iny  %+  ifix
+               :-  ;~(plug soz hed)
+               ;~(plug (just '\0a') soz)
+             (boss 256 (star qat))
+           ==
+         ==
+++  soz  ;~(plug soq soq soq)                           ::  delimiting '''
+++  sym                                                 ::  symbol
+  %+  cook
+    |=(a=tape (rap 3 ^-((list @) a)))
+  ;~(plug low (star ;~(pose nud low hep)))
+::
+++  mixed-case-symbol
+  %+  cook
+    |=(a=tape (rap 3 ^-((list @) a)))
+  ;~(plug alf (star alp))
+::
+++  ven  ;~  (comp |=([a=@ b=@] (peg a b)))             ::  +>- axis syntax
+           bet
+           =+  hom=`?`|
+           |=  tub=nail
+           ^-  (like @)
+           =+  vex=?:(hom (bet tub) (gul tub))
+           ?~  q.vex
+             [p.tub [~ 1 tub]]
+           =+  wag=$(p.tub p.vex, hom !hom, tub q.u.q.vex)
+           ?>  ?=(^ q.wag)
+           [p.wag [~ (peg p.u.q.vex p.u.q.wag) q.u.q.wag]]
+         ==
+++  vit                                                 ::  base64 digit
+  ;~  pose
+    (cook |=(a=@ (sub a 65)) (shim 'A' 'Z'))
+    (cook |=(a=@ (sub a 71)) (shim 'a' 'z'))
+    (cook |=(a=@ (add a 4)) (shim '0' '9'))
+    (cold 62 (just '-'))
+    (cold 63 (just '+'))
+  ==
+++  vul  %+  cold   ~                                   ::  comments
+         ;~  plug  col  col
+           (star prn)
+           (just `@`10)
+         ==
+::
+::    4j: parsing (bases and base digits)
++|  %parsing-bases-and-base-digits
+::
+++  ab
+  |%
+  ++  bix  (bass 16 (stun [2 2] six))
+  ++  fem  (sear |=(a=@ (cha:fa a)) aln)
+  ++  haf  (bass 256 ;~(plug tep tiq (easy ~)))
+  ++  hef  %+  sear  |=(a=@ ?:(=(a 0) ~ (some a)))
+           %+  bass  256
+           ;~(plug tip tiq (easy ~))
+  ++  hif  (bass 256 ;~(plug tip tiq (easy ~)))
+  ++  hof  (bass 0x1.0000 ;~(plug hef (stun [1 3] ;~(pfix hep hif))))
+  ++  huf  (bass 0x1.0000 ;~(plug hef (stun [0 3] ;~(pfix hep hif))))
+  ++  hyf  (bass 0x1.0000 ;~(plug hif (stun [3 3] ;~(pfix hep hif))))
+  ++  pev  (bass 32 ;~(plug sev (stun [0 4] siv)))
+  ++  pew  (bass 64 ;~(plug sew (stun [0 4] siw)))
+  ++  piv  (bass 32 (stun [5 5] siv))
+  ++  piw  (bass 64 (stun [5 5] siw))
+  ++  qeb  (bass 2 ;~(plug seb (stun [0 3] sib)))
+  ++  qex  (bass 16 ;~(plug sex (stun [0 3] hit)))
+  ++  qib  (bass 2 (stun [4 4] sib))
+  ++  qix  (bass 16 (stun [4 4] six))
+  ++  seb  (cold 1 (just '1'))
+  ++  sed  (cook |=(a=@ (sub a '0')) (shim '1' '9'))
+  ++  sev  ;~(pose sed sov)
+  ++  sew  ;~(pose sed sow)
+  ++  sex  ;~(pose sed sox)
+  ++  sib  (cook |=(a=@ (sub a '0')) (shim '0' '1'))
+  ++  sid  (cook |=(a=@ (sub a '0')) (shim '0' '9'))
+  ++  siv  ;~(pose sid sov)
+  ++  siw  ;~(pose sid sow)
+  ++  six  ;~(pose sid sox)
+  ++  sov  (cook |=(a=@ (sub a 87)) (shim 'a' 'v'))
+  ++  sow  ;~  pose
+             (cook |=(a=@ (sub a 87)) (shim 'a' 'z'))
+             (cook |=(a=@ (sub a 29)) (shim 'A' 'Z'))
+             (cold 62 (just '-'))
+             (cold 63 (just '~'))
+           ==
+  ++  sox  (cook |=(a=@ (sub a 87)) (shim 'a' 'f'))
+  ++  ted  (bass 10 ;~(plug sed (stun [0 2] sid)))
+  ++  tep  (sear |=(a=@ ?:(=(a 'doz') ~ (ins:po a))) til)
+  ++  tip  (sear |=(a=@ (ins:po a)) til)
+  ++  tiq  (sear |=(a=@ (ind:po a)) til)
+  ++  tid  (bass 10 (stun [3 3] sid))
+  ++  til  (boss 256 (stun [3 3] low))
+  ++  urs  %+  cook
+             |=(a=tape (rap 3 ^-((list @) a)))
+           (star ;~(pose nud low hep dot sig cab))
+  ++  urt  %+  cook
+             |=(a=tape (rap 3 ^-((list @) a)))
+           (star ;~(pose nud low hep dot sig))
+  ++  urx  %+  cook
+             |=(a=tape (rap 3 ^-((list @) a)))
+           %-  star
+           ;~  pose
+             nud
+             low
+             hep
+             cab
+             (cold ' ' dot)
+             (cook tuft (ifix [sig dot] hex))
+             ;~(pfix sig ;~(pose sig dot))
+           ==
+  ++  voy  ;~(pfix bas ;~(pose bas soq bix))
+  --
+++  ag
+  |%
+  ++  ape  |*(fel=rule ;~(pose (cold `@`0 (just '0')) fel))
+  ++  bay  (ape (bass 16 ;~(plug qeb:ab (star ;~(pfix dog qib:ab)))))
+  ++  bip  =+  tod=(ape qex:ab)
+           (bass 0x1.0000 ;~(plug tod (stun [7 7] ;~(pfix dog tod))))
+  ++  dem  (ape (bass 1.000 ;~(plug ted:ab (star ;~(pfix dog tid:ab)))))
+  ++  dim  (ape dip)
+  ++  dip  (bass 10 ;~(plug sed:ab (star sid:ab)))
+  ++  dum  (bass 10 (plus sid:ab))
+  ++  fed  %+  cook  fynd:ob
+           ;~  pose
+             %+  bass  0x1.0000.0000.0000.0000          ::  oversized
+               ;~  plug
+                 huf:ab
+                 (plus ;~(pfix doh hyf:ab))
+               ==
+             hof:ab                                     ::  planet or moon
+             haf:ab                                     ::  star
+             tiq:ab                                     ::  galaxy
+           ==
+  ++  feq  %+  cook  |=(a=(list @) (rep 4 (flop a)))
+           ;~  plug
+             ;~(pose hif:ab tiq:ab)
+             (star ;~(pfix dof hif:ab))
+           ==
+  ++  fim  (sear den:fa (bass 58 (plus fem:ab)))
+  ++  hex  (ape (bass 0x1.0000 ;~(plug qex:ab (star ;~(pfix dog qix:ab)))))
+  ++  lip  =+  tod=(ape ted:ab)
+           (bass 256 ;~(plug tod (stun [3 3] ;~(pfix dog tod))))
+  ++  mot  ;~  pose
+             ;~  pfix
+               (just '1')
+               (cook |=(a=@ (add 10 (sub a '0'))) (shim '0' '2'))
+             ==
+             sed:ab
+           ==
+  ++  viz  (ape (bass 0x200.0000 ;~(plug pev:ab (star ;~(pfix dog piv:ab)))))
+  ++  vum  (bass 32 (plus siv:ab))
+  ++  wiz  (ape (bass 0x4000.0000 ;~(plug pew:ab (star ;~(pfix dog piw:ab)))))
+  --
+++  mu
+  |_  [top=@ bot=@]
+  ++  zag  [p=(end 4 (add top bot)) q=bot]
+  ++  zig  [p=(end 4 (add top (sub 0x1.0000 bot))) q=bot]
+  ++  zug  (mix (lsh 4 top) bot)
+  --
+++  ne
+  |_  tig=@
+  ++  c  (cut 3 [tig 1] key:fa)
+  ++  d  (add tig '0')
+  ++  x  ?:((gte tig 10) (add tig 87) d)
+  ++  v  ?:((gte tig 10) (add tig 87) d)
+  ++  w  ?:(=(tig 63) '~' ?:(=(tig 62) '-' ?:((gte tig 36) (add tig 29) x)))
+  --
+::
+::    4k: atom printing
++|  %atom-printing
+::
+++  co
+  !:
+  ~%  %co  ..co  ~
+  =<  |_  lot=coin
+      ++  rear  |=(rom=tape rend(rep rom))
+      ++  rent  ~+  `@ta`(rap 3 rend)
+      ++  rend
+        ^-  tape
+        ~+
+        ?:  ?=(%blob -.lot)
+          ['~' '0' ((v-co 1) (jam p.lot))]
+        ?:  ?=(%many -.lot)
+          :-  '.'
+          |-  ^-  tape
+          ?~   p.lot
+            ['_' '_' rep]
+          ['_' (weld (trip (wack rent(lot i.p.lot))) $(p.lot t.p.lot))]
+        =+  [yed=(end 3 p.p.lot) hay=(cut 3 [1 1] p.p.lot)]
+        |-  ^-  tape
+        ?+    yed  (z-co q.p.lot)
+            %c   ['~' '-' (weld (rip 3 (wood (tuft q.p.lot))) rep)]
+            %d
+          ?+    hay  (z-co q.p.lot)
+              %a
+            =+  yod=(yore q.p.lot)
+            =?  rep  ?=(^ f.t.yod)  ['.' (s-co f.t.yod)]
+            =?  rep  !&(?=(~ f) =(0 h) =(0 m) =(0 s)):t.yod
+              =.  rep  ['.' (y-co s.t.yod)]
+              =.  rep  ['.' (y-co m.t.yod)]
+              ['.' '.' (y-co h.t.yod)]
+            =.  rep  ['.' (a-co d.t.yod)]
+            =.  rep  ['.' (a-co m.yod)]
+            =?  rep  !a.yod  ['-' rep]
+            ['~' (a-co y.yod)]
+          ::
+              %r
+            =+  yug=(yell q.p.lot)
+            =?  rep  ?=(^ f.yug)  ['.' (s-co f.yug)]
+            :-  '~'
+            ?:  &(=(0 d.yug) =(0 m.yug) =(0 h.yug) =(0 s.yug))
+              ['s' '0' rep]
+            =?  rep  !=(0 s.yug)  ['.' 's' (a-co s.yug)]
+            =?  rep  !=(0 m.yug)  ['.' 'm' (a-co m.yug)]
+            =?  rep  !=(0 h.yug)  ['.' 'h' (a-co h.yug)]
+            =?  rep  !=(0 d.yug)  ['.' 'd' (a-co d.yug)]
+            +.rep
+          ==
+        ::
+            %f
+          ?:  =(& q.p.lot)
+            ['.' 'y' rep]
+          ?:(=(| q.p.lot) ['.' 'n' rep] (z-co q.p.lot))
+        ::
+            %n   ['~' rep]
+            %i
+          ?+  hay  (z-co q.p.lot)
+            %f  ((ro-co [3 10 4] |=(a=@ ~(d ne a))) q.p.lot)
+            %s  ((ro-co [4 16 8] |=(a=@ ~(x ne a))) q.p.lot)
+          ==
+        ::
+            %p
+          =+  sxz=(fein:ob q.p.lot)
+          =+  dyx=(met 3 sxz)
+          :-  '~'
+          ?:  (lte dyx 1)
+            (weld (trip (tod:po sxz)) rep)
+          =+  dyy=(met 4 sxz)
+          =|  imp=@ud
+          |-  ^-  tape
+          ?:  =(imp dyy)
+            rep
+          %=  $
+            imp  +(imp)
+            rep  =/  log  (cut 4 [imp 1] sxz)
+                 ;:  weld
+                   (trip (tos:po (rsh 3 log)))
+                   (trip (tod:po (end 3 log)))
+                   ?:(=((mod imp 4) 0) ?:(=(imp 0) "" "--") "-")
+                   rep
+          ==     ==
+        ::
+            %q
+          :+  '.'  '~'
+          =;  res=(pair ? tape)
+            (weld q.res rep)
+          %+  roll
+            =*  val  q.p.lot
+            ?:(=(0 val) ~[0] (rip 3 val))
+          |=  [q=@ s=? r=tape]
+          :-  !s
+          %+  weld
+           (trip (?:(s tod:po tos:po) q))
+          ?.(&(s !=(r "")) r ['-' r])
+        ::
+            %r
+          ?+  hay  (z-co q.p.lot)
+            %d  ['.' '~' (r-co (rlyd q.p.lot))]
+            %h  ['.' '~' '~' (r-co (rlyh q.p.lot))]
+            %q  ['.' '~' '~' '~' (r-co (rlyq q.p.lot))]
+            %s  ['.' (r-co (rlys q.p.lot))]
+          ==
+        ::
+            %u
+          ?:  ?=(%c hay)
+            %+  welp  ['0' 'c' (reap (pad:fa q.p.lot) '1')]
+            (c-co (enc:fa q.p.lot))
+          ::
+          =;  gam=(pair tape tape)
+            (weld p.gam ?:(=(0 q.p.lot) `tape`['0' ~] q.gam))
+          ?+  hay  [~ ((ox-co [10 3] |=(a=@ ~(d ne a))) q.p.lot)]
+            %b  [['0' 'b' ~] ((ox-co [2 4] |=(a=@ ~(d ne a))) q.p.lot)]
+            %i  [['0' 'i' ~] ((d-co 1) q.p.lot)]
+            %x  [['0' 'x' ~] ((ox-co [16 4] |=(a=@ ~(x ne a))) q.p.lot)]
+            %v  [['0' 'v' ~] ((ox-co [32 5] |=(a=@ ~(x ne a))) q.p.lot)]
+            %w  [['0' 'w' ~] ((ox-co [64 5] |=(a=@ ~(w ne a))) q.p.lot)]
+          ==
+        ::
+            %s
+          %+  weld
+            ?:((syn:si q.p.lot) "--" "-")
+          $(yed 'u', q.p.lot (abs:si q.p.lot))
+        ::
+            %t
+          ?:  =('a' hay)
+            ?:  =('s' (cut 3 [2 1] p.p.lot))
+              (weld (rip 3 q.p.lot) rep)
+            ['~' '.' (weld (rip 3 q.p.lot) rep)]
+          ['~' '~' (weld (rip 3 (wood q.p.lot)) rep)]
+        ==
+      --
+  =|  rep=tape
+  =<  |%
+      ::  rendering idioms, output zero-padded to minimum lengths
+      ::
+      ::  +a-co: decimal
+      ::  +c-co: base58check
+      ::  +d-co: decimal, takes minimum output digits
+      ::  +r-co: floating point
+      ::  +s-co: list of '.'-prefixed base16, 4 digit minimum
+      ::  +v-co: base32, takes minimum output digits
+      ::  +w-co: base64, takes minimum output digits
+      ::  +x-co: base16, takes minimum output digits
+      ::  +y-co: decimal, 2 digit minimum
+      ::  +z-co: '0x'-prefixed base16
+      ::
+      ++  a-co  |=(dat=@ ((d-co 1) dat))
+      ++  c-co  (em-co [58 1] |=([? b=@ c=tape] [~(c ne b) c]))
+      ++  d-co  |=(min=@ (em-co [10 min] |=([? b=@ c=tape] [~(d ne b) c])))
+      ::
+      ++  r-co
+        |=  a=dn
+        ?:  ?=([%i *] a)  (weld ?:(s.a "inf" "-inf") rep)
+        ?:  ?=([%n *] a)  (weld "nan" rep)
+        =;  rep  ?:(s.a rep ['-' rep])
+        =/  f  ((d-co 1) a.a)
+        =^  e  e.a
+          =/  e=@s  (sun:si (lent f))
+          =/  sci  :(sum:si e.a e -1)
+          ?:  (syn:si (dif:si e.a --3))  [--1 sci]  :: 12000 -> 12e3 e>+2
+          ?:  !(syn:si (dif:si sci -2))  [--1 sci]  :: 0.001 -> 1e-3 e<-2
+          [(sum:si sci --1) --0] :: 1.234e2 -> '.'@3 -> 123 .4
+        =?  rep  !=(--0 e.a)
+          :(weld ?:((syn:si e.a) "e" "e-") ((d-co 1) (abs:si e.a)))
+        (weld (ed-co e f) rep)
+      ::
+      ++  s-co
+        |=  esc=(list @)  ^-  tape
+        ?~  esc  rep
+        ['.' =>(.(rep $(esc t.esc)) ((x-co 4) i.esc))]
+      ::
+      ++  v-co  |=(min=@ (em-co [32 min] |=([? b=@ c=tape] [~(v ne b) c])))
+      ++  w-co  |=(min=@ (em-co [64 min] |=([? b=@ c=tape] [~(w ne b) c])))
+      ++  x-co  |=(min=@ (em-co [16 min] |=([? b=@ c=tape] [~(x ne b) c])))
+      ++  y-co  |=(dat=@ ((d-co 2) dat))
+      ++  z-co  |=(dat=@ `tape`['0' 'x' ((x-co 1) dat)])
+      --
+  |%
+  ::  +em-co: format in numeric base
+  ::
+  ::    in .bas, format .min digits of .hol with .par
+  ::
+  ::    - .hol is processed least-significant digit first
+  ::    - all available digits in .hol will be processed, but
+  ::      .min digits can exceed the number available in .hol
+  ::    - .par handles all accumulated output on each call,
+  ::      and can edit it, prepend or append digits, &c
+  ::    - until .hol is exhausted, .par's sample is [| digit output],
+  ::      subsequently, it's [& 0 output]
+  ::
+  ++  em-co
+    |=  [[bas=@ min=@] par=$-([? @ tape] tape)]
+    |=  hol=@
+    ^-  tape
+    ?:  &(=(0 hol) =(0 min))
+      rep
+    =/  [dar=@ rad=@]  (dvr hol bas)
+    %=  $
+      min  ?:(=(0 min) 0 (dec min))
+      hol  dar
+      rep  (par =(0 dar) rad rep)
+    ==
+  ::
+  ::  +ed-co: format in numeric base, with output length
+  ::
+  ::    - like +em-co, but .par's sample will be [| digit output]
+  ::      on the first call, regardless of the available digits in .hol
+  ::    - used only for @r* floats
+  ::
+  ++  ed-co
+    |=  [exp=@s int=tape]  ^-  tape
+    =/  [pos=? dig=@u]  [=(--1 (cmp:si exp --0)) (abs:si exp)]
+    ?.  pos
+      (into (weld (reap +(dig) '0') int) 1 '.')
+    =/  len  (lent int)
+    ?:  (lth dig len)  (into int dig '.')
+    (weld int (reap (sub dig len) '0'))
+  ::
+  ::  +ox-co: format '.'-separated digit sequences in numeric base
+  ::
+  ::    in .bas, format each digit of .hol with .dug,
+  ::    with '.' separators every .gop digits.
+  ::
+  ::    - .hol is processed least-significant digit first
+  ::    - .dug handles individual digits, output is prepended
+  ::    - every segment but the last is zero-padded to .gop
+  ::
+  ++  ox-co
+    |=  [[bas=@ gop=@] dug=$-(@ @)]
+    %+  em-co
+      [(pow bas gop) 0]
+    |=  [top=? seg=@ res=tape]
+    %+  weld
+      ?:(top ~ `tape`['.' ~])
+    %.  seg
+    %+  em-co(rep res)
+      [bas ?:(top 0 gop)]
+    |=([? b=@ c=tape] [(dug b) c])
+  ::
+  ::  +ro-co: format '.'-prefixed bloqs in numeric base
+  ::
+  ::    in .bas, for .buz bloqs 0 to .dop, format at least one
+  ::    digit of .hol, prefixed with '.'
+  ::
+  ::    - used only for @i* addresses
+  ::
+  ++  ro-co
+    |=  [[buz=@ bas=@ dop=@] dug=$-(@ @)]
+    |=  hol=@
+    ^-  tape
+    ?:  =(0 dop)
+      rep
+    :-  '.'
+    =/  pod  (dec dop)
+    %.  (cut buz [pod 1] hol)
+    %+  em-co(rep $(dop pod))
+      [bas 1]
+    |=([? b=@ c=tape] [(dug b) c])
+  --
+::
+::    4l: atom parsing
++|  %atom-parsing
+::
+++  so
+  ~%  %so  +  ~
+  |%
+  ++  bisk
+    ~+
+    ;~  pose
+      ;~  pfix  (just '0')
+        ;~  pose
+          (stag %ub ;~(pfix (just 'b') bay:ag))
+          (stag %uc ;~(pfix (just 'c') fim:ag))
+          (stag %ui ;~(pfix (just 'i') dim:ag))
+          (stag %ux ;~(pfix (just 'x') hex:ag))
+          (stag %uv ;~(pfix (just 'v') viz:ag))
+          (stag %uw ;~(pfix (just 'w') wiz:ag))
+        ==
+      ==
+      (stag %ud dem:ag)
+    ==
+  ++  crub
+    ~+
+    ;~  pose
+      (cook |=(det=date `dime`[%da (year det)]) when)
+    ::
+      %+  cook
+        |=  [a=(list [p=?(%d %h %m %s) q=@]) b=(list @)]
+        =+  rop=`tarp`[0 0 0 0 b]
+        |-  ^-  dime
+        ?~  a
+          [%dr (yule rop)]
+        ?-  p.i.a
+          %d  $(a t.a, d.rop (add q.i.a d.rop))
+          %h  $(a t.a, h.rop (add q.i.a h.rop))
+          %m  $(a t.a, m.rop (add q.i.a m.rop))
+          %s  $(a t.a, s.rop (add q.i.a s.rop))
+        ==
+      ;~  plug
+        %+  most
+          dot
+        ;~  pose
+          ;~(pfix (just 'd') (stag %d dim:ag))
+          ;~(pfix (just 'h') (stag %h dim:ag))
+          ;~(pfix (just 'm') (stag %m dim:ag))
+          ;~(pfix (just 's') (stag %s dim:ag))
+        ==
+        ;~(pose ;~(pfix ;~(plug dot dot) (most dot qix:ab)) (easy ~))
+      ==
+    ::
+      (stag %p fed:ag)
+      ;~(pfix dot (stag %ta urs:ab))
+      ;~(pfix sig (stag %t urx:ab))
+      ;~(pfix hep (stag %c (cook taft urx:ab)))
+    ==
+  ++  nuck
+    ~/  %nuck  |=  a=nail  %.  a
+    %+  knee  *coin  |.  ~+
+    %-  stew
+    ^.  stet  ^.  limo
+    :~  :-  ['a' 'z']  (cook |=(a=@ta [%$ %tas a]) sym)
+        :-  ['0' '9']  (stag %$ bisk)
+        :-  '-'        (stag %$ tash)
+        :-  '.'        ;~(pfix dot perd)
+        :-  '~'        ;~(pfix sig ;~(pose twid (easy [%$ %n 0])))
+    ==
+  ++  nusk
+    ~+
+    :(sear |=(a=@ta (rush a nuck)) wick urt:ab)
+  ++  perd
+    ~+
+    ;~  pose
+      (stag %$ zust)
+      (stag %many (ifix [cab ;~(plug cab cab)] (more cab nusk)))
+    ==
+  ++  royl
+    ~+
+    ;~  pose
+      (stag %rh royl-rh)
+      (stag %rq royl-rq)
+      (stag %rd royl-rd)
+      (stag %rs royl-rs)
+    ==
+  ::
+  ++  royl-rh  (cook rylh ;~(pfix ;~(plug sig sig) (cook royl-cell royl-rn)))
+  ++  royl-rq  (cook rylq ;~(pfix ;~(plug sig sig sig) (cook royl-cell royl-rn)))
+  ++  royl-rd  (cook ryld ;~(pfix sig (cook royl-cell royl-rn)))
+  ++  royl-rs  (cook ryls (cook royl-cell royl-rn))
+  ::
+  ++  royl-rn
+    =/  moo
+      |=  a=tape
+      :-  (lent a)
+      (scan a (bass 10 (plus sid:ab)))
+    ;~  pose
+      ;~  plug
+        (easy %d)
+        ;~(pose (cold | hep) (easy &))
+        ;~  plug  dim:ag
+          ;~  pose
+            ;~(pfix dot (cook moo (plus (shim '0' '9'))))
+            (easy [0 0])
+          ==
+          ;~  pose
+            ;~  pfix
+              (just 'e')
+              ;~(plug ;~(pose (cold | hep) (easy &)) dim:ag)
+            ==
+            (easy [& 0])
+          ==
+        ==
+      ==
+      ::
+      ;~  plug
+        (easy %i)
+        ;~  sfix
+          ;~(pose (cold | hep) (easy &))
+          (jest 'inf')
+        ==
+      ==
+      ::
+      ;~  plug
+        (easy %n)
+        (cold ~ (jest 'nan'))
+      ==
+    ==
+  ::
+  ++  royl-cell
+    |=  rn
+    ^-  dn
+    ?.  ?=([%d *] +<)  +<
+    =+  ^=  h
+      (dif:si (new:si f.b i.b) (sun:si d.b))
+    [%d a h (add (mul c.b (pow 10 d.b)) e.b)]
+  ::
+  ++  tash
+    ~+
+    =+  ^=  neg
+        |=  [syn=? mol=dime]  ^-  dime
+        ?>  =('u' (end 3 p.mol))
+        [(cat 3 's' (rsh 3 p.mol)) (new:si syn q.mol)]
+    ;~  pfix  hep
+      ;~  pose
+        (cook |=(a=dime (neg | a)) bisk)
+        ;~(pfix hep (cook |=(a=dime (neg & a)) bisk))
+      ==
+    ==
+  ::
+  ++  twid
+    ~+
+    ;~  pose
+      %+  stag  %blob
+      %+  sear  |=(a=@ (mole |.((cue a))))
+      ;~(pfix (just '0') vum:ag)
+    ::
+      (stag %$ crub)
+    ==
+  ::
+  ++  when
+    ~+
+    ;~  plug
+      %+  cook
+        |=([a=@ b=?] [b a])
+      ;~(plug dim:ag ;~(pose (cold | hep) (easy &)))
+      ;~(pfix dot mot:ag)   ::  month
+      ;~(pfix dot dip:ag)   ::  day
+      ;~  pose
+        ;~  pfix
+          ;~(plug dot dot)
+          ;~  plug
+            dum:ag
+            ;~(pfix dot dum:ag)
+            ;~(pfix dot dum:ag)
+            ;~(pose ;~(pfix ;~(plug dot dot) (most dot qix:ab)) (easy ~))
+          ==
+        ==
+        (easy [0 0 0 ~])
+      ==
+    ==
+  ::
+  ++  zust
+    ~+
+    ;~  pose
+      (stag %is bip:ag)
+      (stag %if lip:ag)
+      royl
+      (stag %f ;~(pose (cold & (just 'y')) (cold | (just 'n'))))
+      (stag %q ;~(pfix sig feq:ag))
+    ==
+  --
+::
+::    4m: formatting functions
++|  %formatting-functions
+++  scot
+  ~/  %scot
+  |=(mol=dime ~(rent co %$ mol))
+++  scow
+  ~/  %scow
+  |=(mol=dime ~(rend co %$ mol))
+++  slat  |=(mod=@tas |=(txt=@ta (slaw mod txt)))
+++  slav  |=([mod=@tas txt=@ta] (need (slaw mod txt)))
+++  slaw
+  ~/  %slaw
+  |=  [mod=@tas txt=@ta]
+  ^-  (unit @)
+  ?+    mod
+      ::  slow fallback case to the full slay
+      ::
+      =+  con=(slay txt)
+      ?.(&(?=([~ %$ @ @] con) =(p.p.u.con mod)) ~ [~ q.p.u.con])
+  ::
+      %da
+    (rush txt ;~(pfix sig (cook year when:so)))
+  ::
+      %p
+    (rush txt ;~(pfix sig fed:ag))
+  ::
+      %ud
+    (rush txt dem:ag)
+  ::
+      %ux
+    (rush txt ;~(pfix (jest '0x') hex:ag))
+  ::
+      %uv
+    (rush txt ;~(pfix (jest '0v') viz:ag))
+  ::
+      %ta
+    (rush txt ;~(pfix ;~(plug sig dot) urs:ab))
+  ::
+      %tas
+    (rush txt sym)
+  ==
+::
+++  slay
+  |=  txt=@ta  ^-  (unit coin)
+  =+  ^=  vex
+      ?:  (gth 0x7fff.ffff txt)                         ::  XX  petty cache
+        ~+  ((full nuck:so) [[1 1] (trip txt)])
+      ((full nuck:so) [[1 1] (trip txt)])
+  ?~  q.vex
+    ~
+  [~ p.u.q.vex]
+::
+++  smyt                                                ::  pretty print path
+  |=  bon=path  ^-  tank
+  :+  %rose  [['/' ~] ['/' ~] ~]
+  (turn bon |=(a=@ [%leaf (trip a)]))
+::
+++  spat  |=(pax=path (crip (spud pax)))                ::  render path to cord
+++  spud  |=(pax=path ~(ram re (smyt pax)))             ::  render path to tape
+++  stab  |=(zep=@t `path`(rash zep stap))              ::  parse cord to path
+++  stap                                                ::  path parser
+  %+  sear
+    |=  p=path
+    ^-  (unit path)
+    ?:  ?=([~ ~] p)  `~
+    ?.  =(~ (rear p))  `p
+    ~
+  ;~(pfix fas (most fas urs:ab))
+::
+++  stip                                                ::  typed path parser
+  =<  swot
+  |%
+  ++  swot  |=(n=nail (;~(pfix fas (more fas spot)) n))
+  ::
+  ++  spot
+    %+  sear  (soft iota)
+    %-  stew
+    ^.  stet  ^.  limo
+    :~  :-  'a'^'z'  (stag %tas sym)
+        :-  '$'      (cold [%tas %$] buc)
+        :-  '0'^'9'  bisk:so
+        :-  '-'      tash:so
+        :-  '.'      zust:so
+        :-  '~'      ;~(pfix sig ;~(pose crub:so (easy [%n ~])))
+        :-  '\''     (stag %t qut)
+    ==
+  --
+::
+++  pout
+  |=  =pith
+  ^-  path
+  %+  turn  pith
+  |=  i=iota
+  ?@(i i (scot i))
+::
+++  pave
+  |=  =path
+  ^-  pith
+  %+  turn  path
+  |=  i=@ta
+  (fall (rush i spot:stip) [%ta i])
+::
+::    4n: virtualization
++|  %virtualization
+::
+::  +mack: untyped, scry-less, unitary virtualization
+::
+++  mack
+  |=  [sub=* fol=*]
+  ^-  (unit)
+  =/  ton  (mink [sub fol] |~(^ ~))
+  ?.(?=(%0 -.ton) ~ `product.ton)
+::  +mink: raw virtual nock
+::
+++  mink  !.
+  ~/  %mink
+  |=  $:  [subject=* formula=*]
+          scry=$-(^ (unit (unit)))
+      ==
+  =|  trace=(list [@ta *])
+  |^  ^-  tone
+      ?+  formula  [%2 trace]
+          [^ *]
+        =/  head  $(formula -.formula)
+        ?.  ?=(%0 -.head)  head
+        =/  tail  $(formula +.formula)
+        ?.  ?=(%0 -.tail)  tail
+        [%0 product.head product.tail]
+      ::
+          [%0 axis=@]
+        =/  part  (frag axis.formula subject)
+        ?~  part  [%2 trace]
+        [%0 u.part]
+      ::
+          [%1 constant=*]
+        [%0 constant.formula]
+      ::
+          [%2 subject=* formula=*]
+        =/  subject  $(formula subject.formula)
+        ?.  ?=(%0 -.subject)  subject
+        =/  formula  $(formula formula.formula)
+        ?.  ?=(%0 -.formula)  formula
+        %=  $
+          subject  product.subject
+          formula  product.formula
+        ==
+      ::
+          [%3 argument=*]
+        =/  argument  $(formula argument.formula)
+        ?.  ?=(%0 -.argument)  argument
+        [%0 .?(product.argument)]
+      ::
+          [%4 argument=*]
+        =/  argument  $(formula argument.formula)
+        ?.  ?=(%0 -.argument)  argument
+        ?^  product.argument  [%2 trace]
+        [%0 .+(product.argument)]
+      ::
+          [%5 a=* b=*]
+        =/  a  $(formula a.formula)
+        ?.  ?=(%0 -.a)  a
+        =/  b  $(formula b.formula)
+        ?.  ?=(%0 -.b)  b
+        [%0 =(product.a product.b)]
+      ::
+          [%6 test=* yes=* no=*]
+        =/  result  $(formula test.formula)
+        ?.  ?=(%0 -.result)  result
+        ?+  product.result
+              [%2 trace]
+          %&  $(formula yes.formula)
+          %|  $(formula no.formula)
+        ==
+      ::
+          [%7 subject=* next=*]
+        =/  subject  $(formula subject.formula)
+        ?.  ?=(%0 -.subject)  subject
+        %=  $
+          subject  product.subject
+          formula  next.formula
+        ==
+      ::
+          [%8 head=* next=*]
+        =/  head  $(formula head.formula)
+        ?.  ?=(%0 -.head)  head
+        %=  $
+          subject  [product.head subject]
+          formula  next.formula
+        ==
+      ::
+          [%9 axis=@ core=*]
+        =/  core  $(formula core.formula)
+        ?.  ?=(%0 -.core)  core
+        =/  arm  (frag axis.formula product.core)
+        ?~  arm  [%2 trace]
+        %=  $
+          subject  product.core
+          formula  u.arm
+        ==
+      ::
+          [%10 [axis=@ value=*] target=*]
+        ?:  =(0 axis.formula)  [%2 trace]
+        =/  target  $(formula target.formula)
+        ?.  ?=(%0 -.target)  target
+        =/  value  $(formula value.formula)
+        ?.  ?=(%0 -.value)  value
+        =/  mutant=(unit *)
+          (edit axis.formula product.target product.value)
+        ?~  mutant  [%2 trace]
+        [%0 u.mutant]
+      ::
+          [%11 tag=@ next=*]
+        =/  next  $(formula next.formula)
+        ?.  ?=(%0 -.next)  next
+        :-  %0
+        .*  subject
+        [11 tag.formula 1 product.next]
+      ::
+          [%11 [tag=@ clue=*] next=*]
+        =/  clue  $(formula clue.formula)
+        ?.  ?=(%0 -.clue)  clue
+        =/  next
+          =?    trace
+              ?=(?(%hunk %hand %lose %mean %spot) tag.formula)
+            [[tag.formula product.clue] trace]
+          $(formula next.formula)
+        ?.  ?=(%0 -.next)  next
+        :-  %0
+        .*  subject
+        [11 [tag.formula 1 product.clue] 1 product.next]
+      ::
+          [%12 ref=* path=*]
+        =/  ref  $(formula ref.formula)
+        ?.  ?=(%0 -.ref)  ref
+        =/  path  $(formula path.formula)
+        ?.  ?=(%0 -.path)  path
+        =/  result  (scry product.ref product.path)
+        ?~  result
+          [%1 product.path]
+        ?~  u.result
+          [%2 [%hunk product.ref product.path] trace]
+        [%0 u.u.result]
+      ==
+  ::
+  ++  frag
+    |=  [axis=@ noun=*]
+    ^-  (unit)
+    ?:  =(0 axis)  ~
+    |-  ^-  (unit)
+    ?:  =(1 axis)  `noun
+    ?@  noun  ~
+    =/  pick  (cap axis)
+    %=  $
+      axis  (mas axis)
+      noun  ?-(pick %2 -.noun, %3 +.noun)
+    ==
+  ::
+  ++  edit
+    |=  [axis=@ target=* value=*]
+    ^-  (unit)
+    ?:  =(1 axis)  `value
+    ?@  target  ~
+    =/  pick  (cap axis)
+    =/  mutant
+      %=  $
+        axis    (mas axis)
+        target  ?-(pick %2 -.target, %3 +.target)
+      ==
+    ?~  mutant  ~
+    ?-  pick
+      %2  `[u.mutant +.target]
+      %3  `[-.target u.mutant]
+    ==
+  --
+::  +mock: virtual nock
+::
+++  mock
+  |=  [[sub=* fol=*] gul=$-(^ (unit (unit)))]
+  (mook (mink [sub fol] gul))
+::  +mook: convert %tone to %toon, rendering stack frames
+::
+++  mook
+  |=  ton=tone
+  ^-  toon
+  ?.  ?=([%2 *] ton)
+    ton
+  |^  [%2 (turn skip rend)]
+  ::
+  ++  skip
+    ^+  trace.ton
+    =/  yel  (lent trace.ton)
+    ?.  (gth yel 1.024)  trace.ton
+    %+  weld
+      (scag 512 trace.ton)
+    ^+  trace.ton
+    :_  (slag (sub yel 512) trace.ton)
+    :-  %lose
+    (crip "[skipped {(scow %ud (sub yel 1.024))} frames]")
+  ::
+  ::  +rend: raw stack frame to tank
+  ::
+  ::    $%  [%hunk ref=* path]            ::  failed scry ([~ ~])
+  ::        [%lose cord]                  ::  skipped frames
+  ::        [%hand *]                     ::  mug any
+  ::        [%mean $@(cord (trap tank))]  ::  ~_ et al
+  ::        [%spot spot]                  ::  source location
+  ::    ==
+  ::
+  ++  rend
+    |=  [tag=@ta dat=*]
+    ^-  tank
+    ?+    tag
+    ::
+      leaf+"mook.{(rip 3 tag)}"
+    ::
+        %hunk
+      ?@  dat  leaf+"mook.hunk"
+      =/  sof=(unit path)  ((soft path) +.dat)
+      ?~  sof  leaf+"mook.hunk"
+      (smyt u.sof)
+    ::
+        %lose
+      ?^  dat  leaf+"mook.lose"
+      leaf+(rip 3 dat)
+    ::
+        %hand
+      leaf+(scow %p (mug dat))
+    ::
+        %mean
+      ?@  dat  leaf+(rip 3 dat)
+      =/  mac  (mack dat -.dat)
+      ?~  mac  leaf+"####"
+      =/  sof  ((soft tank) u.mac)
+      ?~  sof  leaf+"mook.mean"
+      u.sof
+    ::
+        %spot
+      =/  sof=(unit spot)  ((soft spot) dat)
+      ?~  sof  leaf+"mook.spot"
+      :+  %rose  [":" ~ ~]
+      :~  (smyt p.u.sof)
+          =*  l   p.q.u.sof
+          =*  r   q.q.u.sof
+          =/  ud  |=(a=@u (scow %ud a))
+          leaf+"<[{(ud p.l)} {(ud q.l)}].[{(ud p.r)} {(ud q.r)}]>"
+      ==
+    ==
+  --
+::  +mole: typed unitary virtual
+::
+++  mole
+  ~/  %mole
+  |*  tap=(trap)
+  ^-  (unit _$:tap)
+  =/  mur  (mure tap)
+  ?~(mur ~ `$:tap)
+::  +mong: virtual slam
+::
+++  mong
+  |=  [[gat=* sam=*] gul=$-(^ (unit (unit)))]
+  ^-  toon
+  ?.  ?=([* ^] gat)  [%2 ~]
+  (mock [gat(+< sam) %9 2 %0 1] gul)
+::  +mule: typed virtual
+::
+++  mule
+  ~/  %mule
+  |*  tap=(trap)
+  =/  mud  (mute tap)
+  ?-  -.mud
+    %&  [%& p=$:tap]
+    %|  [%| p=p.mud]
+  ==
+::  +mure: untyped unitary virtual
+::
+++  mure
+  |=  tap=(trap)
+  ^-  (unit)
+  =/  ton  (mink [tap %9 2 %0 1] |=(a=^ ``.*(a [%12 [%0 2] %0 3])))
+  ?.(?=(%0 -.ton) ~ `product.ton)
+::  +mute: untyped virtual
+::
+++  mute
+  |=  tap=(trap)
+  ^-  (each * (list tank))
+  =/  ton  (mock [tap %9 2 %0 1] |=(a=^ ``.*(a [%12 [%0 2] %0 3])))
+  ?-  -.ton
+    %0  [%& p.ton]
+  ::
+    %1  =/  sof=(unit path)  ((soft path) p.ton)
+        [%| ?~(sof leaf+"mute.hunk" (smyt u.sof)) ~]
+  ::
+    %2  [%| p.ton]
+  ==
+::  +slum: slam a gate on a sample using raw nock, untyped
+::
+++  slum
+  ~/  %slum
+  |=  sub=[gat=* sam=*]
+  .*(sub [%9 2 %10 [6 %0 3] %0 2])
+::  +soft: virtual clam
+::
+++  soft
+  |*  han=$-(* *)
+  |=(fud=* (mole |.((han fud))))
+::
+::    4o: molds and mold builders
++|  %molds-and-mold-builders
+::
++$  abel  typo                                          ::  original sin: type
++$  alas  (list (pair term hoon))                       ::  alias list
++$  atom  @                                             ::  just an atom
++$  aura  @ta                                           ::  atom format
++$  base                                                ::  base mold
+  $@  $?  %noun                                         ::  any noun
+          %cell                                         ::  any cell
+          %flag                                         ::  loobean
+          %null                                         ::  ~ == 0
+          %void                                         ::  empty set
+      ==                                                ::
+  [%atom p=aura]                                        ::  atom
+::
++$  woof  $@(@ [~ p=hoon])                              ::  simple embed
++$  chum  $?  lef=term                                  ::  jet name
+              [std=term kel=@]                          ::  kelvin version
+              [ven=term pro=term kel=@]                 ::  vendor and product
+              [ven=term pro=term ver=@ kel=@]           ::  all of the above
+          ==                                            ::
++$  coil  $:  p=garb                                    ::  name, wet=dry, vary
+              q=type                                    ::  context
+              r=(pair seminoun (map term tome))         ::  chapters
+          ==                                            ::
++$  garb  (trel (unit term) poly vair)                  ::  core
++$  poly  ?(%wet %dry)                                  ::  polarity
++$  foot  $%  [%dry p=hoon]                             ::  dry arm, geometric
+              [%wet p=hoon]                             ::  wet arm, generic
+          ==                                            ::
++$  link                                                ::  lexical segment
+          $%  [%chat p=term]                            ::  |chapter
+              [%cone p=aura q=atom]                     ::  %constant
+              [%frag p=term]                            ::  .face
+              [%funk p=term]                            ::  +arm
+              [%plan p=term]                            ::  $spec
+          ==                                            ::
++$  cuff  (list link)                                   ::  parsed lex segments
++$  crib  [summary=cord details=(list sect)]            ::
++$  help  [=cuff =crib]                                 ::  documentation
++$  limb  $@  term                                      ::  wing element
+          $%  [%& p=axis]                               ::  by geometry
+              [%| p=@ud q=(unit term)]                  ::  by name
+          ==                                            ::
+            ::  XX more and better sanity
+            ::
++$  null  ~                                             ::  null, nil, etc
++$  onyx  (list (pair type foot))                       ::  arm activation
++$  opal                                                ::  limb match
+          $%  [%& p=type]                               ::  leg
+              [%| p=axis q=(set [p=type q=foot])]       ::  arm
+          ==                                            ::
++$  pica  (pair ? cord)                                 ::  & prose, | code
++$  palo  (pair vein opal)                              ::  wing trace, match
++$  pock  (pair axis nock)                              ::  changes
++$  port  (each palo (pair type nock))                  ::  successful match
++$  spec                                                ::  structure definition
+          $~  [%base %null]                             ::
+          $%  [%base p=base]                            ::  base type
+              [%dbug p=spot q=spec]                     ::  set debug
+              [%gist p=[%help p=help] q=spec]           ::  formal comment
+              [%leaf p=term q=@]                        ::  constant atom
+              [%like p=wing q=(list wing)]              ::  reference
+              [%loop p=term]                            ::  hygienic reference
+              [%made p=(pair term (list term)) q=spec]  ::  annotate synthetic
+              [%make p=hoon q=(list spec)]              ::  composed spec
+              [%name p=term q=spec]                     ::  annotate simple
+              [%over p=wing q=spec]                     ::  relative to subject
+          ::                                            ::
+              [%bcgr p=spec q=spec]                     ::  $>, filter: require
+              [%bcbc p=spec q=(map term spec)]          ::  $$, recursion
+              [%bcbr p=spec q=hoon]                     ::  $|, verify
+              [%bccb p=hoon]                            ::  $_, example
+              [%bccl p=[i=spec t=(list spec)]]          ::  $:, tuple
+              [%bccn p=[i=spec t=(list spec)]]          ::  $%, head pick
+              [%bcdt p=spec q=(map term spec)]          ::  $., read-write core
+              [%bcgl p=spec q=spec]                     ::  $<, filter: exclude
+              [%bchp p=spec q=spec]                     ::  $-, function core
+              [%bckt p=spec q=spec]                     ::  $^, cons pick
+              [%bcls p=stud q=spec]                     ::  $+, standard
+              [%bcfs p=spec q=(map term spec)]          ::  $/, write-only core
+              [%bcmc p=hoon]                            ::  $;, manual
+              [%bcpm p=spec q=hoon]                     ::  $&, repair
+              [%bcsg p=hoon q=spec]                     ::  $~, default
+              [%bctc p=spec q=(map term spec)]          ::  $`, read-only core
+              [%bcts p=skin q=spec]                     ::  $=, name
+              [%bcpt p=spec q=spec]                     ::  $@, atom pick
+              [%bcwt p=[i=spec t=(list spec)]]          ::  $?, full pick
+              [%bczp p=spec q=(map term spec)]          ::  $!, opaque core
+          ==                                            ::
++$  tent                                                ::  model builder
+          $%  [%| p=wing q=tent r=(list spec)]          ::  ~(p q r...)
+              [%& p=(list wing)]                        ::  a.b:c.d
+          ==                                            ::
++$  tiki                                                ::  test case
+          $%  [%& p=(unit term) q=wing]                 ::  simple wing
+              [%| p=(unit term) q=hoon]                 ::  named wing
+          ==                                            ::
++$  skin                                                ::  texture
+          $@  =term                                     ::  name/~[term %none]
+          $%  [%base =base]                             ::  base match
+              [%cell =skin =skin]                       ::  pair
+              [%dbug =spot =skin]                       ::  trace
+              [%leaf =aura =atom]                       ::  atomic constant
+              [%help =help =skin]                       ::  describe
+              [%name =term =skin]                       ::  apply label
+              [%over =wing =skin]                       ::  relative to
+              [%spec =spec =skin]                       ::  cast to
+              [%wash depth=@ud]                         ::  strip faces
+          ==                                            ::
++$  tome  (pair what (map term hoon))                   ::  core chapter
++$  tope                                                ::  topographic type
+  $@  $?  %&                                            ::  cell or atom
+          %|                                            ::  atom
+      ==                                                ::
+  (pair tope tope)                                      ::  cell
+++  hoot                                                ::  hoon tools
+  |%
+  +$  beer  $@(char [~ p=hoon])                         ::  simple embed
+  +$  mane  $@(@tas [@tas @tas])                        ::  XML name+space
+  +$  manx  $~([[%$ ~] ~] [g=marx c=marl])              ::  dynamic XML node
+  +$  marl  (list tuna)                                 ::  dynamic XML nodes
+  +$  mart  (list [n=mane v=(list beer)])               ::  dynamic XML attrs
+  +$  marx  $~([%$ ~] [n=mane a=mart])                  ::  dynamic XML tag
+  +$  mare  (each manx marl)                            ::  node or nodes
+  +$  maru  (each tuna marl)                            ::  interp or nodes
+  +$  tuna                                              ::  maybe interpolation
+      $~  [[%$ ~] ~]
+      $^  manx
+      $:  ?(%tape %manx %marl %call)
+          p=hoon
+      ==
+  --                                                    ::
++$  hoon                                                ::  hoon AST
+  $+  hoon
+  $~  [%zpzp ~]                                         ::
+  $^  [p=hoon q=hoon]                                   ::
+  $%                                                    ::
+    [%$ p=axis]                                         ::  simple leg
+  ::                                                    ::
+    [%base p=base]                                      ::  base spec
+    [%bust p=base]                                      ::  bunt base
+    [%dbug p=spot q=hoon]                               ::  debug info in trace
+    [%eror p=tape]                                      ::  assembly error
+    [%hand p=type q=nock]                               ::  premade result
+    [%note p=note q=hoon]                               ::  annotate
+    [%fits p=hoon q=wing]                               ::  underlying ?=
+    [%knit p=(list woof)]                               ::  assemble string
+    [%leaf p=(pair term @)]                             ::  symbol spec
+    [%limb p=term]                                      ::  take limb
+    [%lost p=hoon]                                      ::  not to be taken
+    [%rock p=term q=*]                                  ::  fixed constant
+    [%sand p=term q=*]                                  ::  unfixed constant
+    [%tell p=(list hoon)]                               ::  render as tape
+    [%tune p=$@(term tune)]                             ::  minimal face
+    [%wing p=wing]                                      ::  take wing
+    [%yell p=(list hoon)]                               ::  render as tank
+    [%xray p=manx:hoot]                                 ::  ;foo; templating
+  ::                                            ::::::  cores
+    [%brbc sample=(lest term) body=spec]                ::  |$
+    [%brcb p=spec q=alas r=(map term tome)]             ::  |_
+    [%brcl p=hoon q=hoon]                               ::  |:
+    [%brcn p=(unit term) q=(map term tome)]             ::  |%
+    [%brdt p=hoon]                                      ::  |.
+    [%brkt p=hoon q=(map term tome)]                    ::  |^
+    [%brhp p=hoon]                                      ::  |-
+    [%brsg p=spec q=hoon]                               ::  |~
+    [%brtr p=spec q=hoon]                               ::  |*
+    [%brts p=spec q=hoon]                               ::  |=
+    [%brpt p=(unit term) q=(map term tome)]             ::  |@
+    [%brwt p=hoon]                                      ::  |?
+  ::                                            ::::::  tuples
+    [%clcb p=hoon q=hoon]                               ::  :_ [q p]
+    [%clkt p=hoon q=hoon r=hoon s=hoon]                 ::  :^ [p q r s]
+    [%clhp p=hoon q=hoon]                               ::  :- [p q]
+    [%clls p=hoon q=hoon r=hoon]                        ::  :+ [p q r]
+    [%clsg p=(list hoon)]                               ::  :~ [p ~]
+    [%cltr p=(list hoon)]                               ::  :* p as a tuple
+  ::                                            ::::::  invocations
+    [%cncb p=wing q=(list (pair wing hoon))]            ::  %_
+    [%cndt p=hoon q=hoon]                               ::  %.
+    [%cnhp p=hoon q=hoon]                               ::  %-
+    [%cncl p=hoon q=(list hoon)]                        ::  %:
+    [%cntr p=wing q=hoon r=(list (pair wing hoon))]     ::  %*
+    [%cnkt p=hoon q=hoon r=hoon s=hoon]                 ::  %^
+    [%cnls p=hoon q=hoon r=hoon]                        ::  %+
+    [%cnsg p=wing q=hoon r=(list hoon)]                 ::  %~
+    [%cnts p=wing q=(list (pair wing hoon))]            ::  %=
+  ::                                            ::::::  nock
+    [%dtkt p=spec q=hoon]                               ::  .^  nock 11
+    [%dtls p=hoon]                                      ::  .+  nock 4
+    [%dttr p=hoon q=hoon]                               ::  .*  nock 2
+    [%dtts p=hoon q=hoon]                               ::  .=  nock 5
+    [%dtwt p=hoon]                                      ::  .?  nock 3
+  ::                                            ::::::  type conversion
+    [%ktbr p=hoon]                                      ::  ^|  contravariant
+    [%ktdt p=hoon q=hoon]                               ::  ^.  self-cast
+    [%ktls p=hoon q=hoon]                               ::  ^+  expression cast
+    [%kthp p=spec q=hoon]                               ::  ^-  structure cast
+    [%ktpm p=hoon]                                      ::  ^&  covariant
+    [%ktsg p=hoon]                                      ::  ^~  constant
+    [%ktts p=skin q=hoon]                               ::  ^=  label
+    [%ktwt p=hoon]                                      ::  ^?  bivariant
+    [%kttr p=spec]                                      ::  ^*  example
+    [%ktcl p=spec]                                      ::  ^:  filter
+  ::                                            ::::::  hints
+    [%sgbr p=hoon q=hoon]                               ::  ~|  sell on trace
+    [%sgcb p=hoon q=hoon]                               ::  ~_  tank on trace
+    [%sgcn p=chum q=hoon r=tyre s=hoon]                 ::  ~%  general jet hint
+    [%sgfs p=chum q=hoon]                               ::  ~/  function j-hint
+    [%sggl p=$@(term [p=term q=hoon]) q=hoon]           ::  ~<  backward hint
+    [%sggr p=$@(term [p=term q=hoon]) q=hoon]           ::  ~>  forward hint
+    [%sgbc p=term q=hoon]                               ::  ~$  profiler hit
+    [%sgls p=@ q=hoon]                                  ::  ~+  cache=memoize
+    [%sgpm p=@ud q=hoon r=hoon]                         ::  ~&  printf=priority
+    [%sgts p=hoon q=hoon]                               ::  ~=  don't duplicate
+    [%sgwt p=@ud q=hoon r=hoon s=hoon]                  ::  ~?  tested printf
+    [%sgzp p=hoon q=hoon]                               ::  ~!  type on trace
+  ::                                            ::::::  miscellaneous
+    [%mcts p=marl:hoot]                                 ::  ;=  list templating
+    [%mccl p=hoon q=(list hoon)]                        ::  ;:  binary to nary
+    [%mcfs p=hoon]                                      ::  ;/  [%$ [%$ p ~] ~]
+    [%mcgl p=spec q=hoon r=hoon s=hoon]                 ::  ;<  bind
+    [%mcsg p=hoon q=(list hoon)]                        ::  ;~  kleisli arrow
+    [%mcmc p=spec q=hoon]                               ::  ;;  normalize
+  ::                                            ::::::  compositions
+    [%tsbr p=spec q=hoon]                               ::  =|  push bunt
+    [%tscl p=(list (pair wing hoon)) q=hoon]            ::  =:  q w= p changes
+    [%tsfs p=skin q=hoon r=hoon]                        ::  =/  typed variable
+    [%tsmc p=skin q=hoon r=hoon]                        ::  =;  =/(q p r)
+    [%tsdt p=wing q=hoon r=hoon]                        ::  =.  r with p as q
+    [%tswt p=wing q=hoon r=hoon s=hoon]                 ::  =?  conditional =.
+    [%tsgl p=hoon q=hoon]                               ::  =<  =>(q p)
+    [%tshp p=hoon q=hoon]                               ::  =-  =+(q p)
+    [%tsgr p=hoon q=hoon]                               ::  =>  q w=subject p
+    [%tskt p=skin q=wing r=hoon s=hoon]                 ::  =^  state machine
+    [%tsls p=hoon q=hoon]                               ::  =+  q w=[p subject]
+    [%tssg p=(list hoon)]                               ::  =~  hoon stack
+    [%tstr p=(pair term (unit spec)) q=hoon r=hoon]     ::  =*  new style
+    [%tscm p=hoon q=hoon]                               ::  =,  overload p in q
+  ::                                            ::::::  conditionals
+    [%wtbr p=(list hoon)]                               ::  ?|  loobean or
+    [%wthp p=wing q=(list (pair spec hoon))]            ::  ?-  pick case in q
+    [%wtcl p=hoon q=hoon r=hoon]                        ::  ?:  if=then=else
+    [%wtdt p=hoon q=hoon r=hoon]                        ::  ?.  ?:(p r q)
+    [%wtkt p=wing q=hoon r=hoon]                        ::  ?^  if p is a cell
+    [%wtgl p=hoon q=hoon]                               ::  ?<  ?:(p !! q)
+    [%wtgr p=hoon q=hoon]                               ::  ?>  ?:(p q !!)
+    [%wtls p=wing q=hoon r=(list (pair spec hoon))]     ::  ?+  ?-  w=default
+    [%wtpm p=(list hoon)]                               ::  ?&  loobean and
+    [%wtpt p=wing q=hoon r=hoon]                        ::  ?@  if p is atom
+    [%wtsg p=wing q=hoon r=hoon]                        ::  ?~  if p is null
+    [%wthx p=skin q=wing]                               ::  ?#  if q matches p
+    [%wtts p=spec q=wing]                               ::  ?=  if q matches p
+    [%wtzp p=hoon]                                      ::  ?!  loobean not
+  ::                                            ::::::  special
+    [%zpcm p=hoon q=hoon]                               ::  !,
+    [%zpgr p=hoon]                                      ::  !>
+    [%zpgl p=spec q=hoon]                               ::  !<
+    [%zpmc p=hoon q=hoon]                               ::  !;
+    [%zpts p=hoon]                                      ::  !=
+    [%zppt p=(list wing) q=hoon r=hoon]                 ::  !@
+    [%zpwt p=$@(p=@ [p=@ q=@]) q=hoon]                  ::  !?
+    [%zpzp ~]                                           ::  !!
+  ==                                                    ::
++$  tyre  (list [p=term q=hoon])                        ::
++$  tyke  (list (unit hoon))                            ::
+::                                                      ::::::  virtual nock
++$  nock  $^  [p=nock q=nock]                           ::  autocons
+          $%  [%1 p=*]                                  ::  constant
+              [%2 p=nock q=nock]                        ::  compose
+              [%3 p=nock]                               ::  cell test
+              [%4 p=nock]                               ::  increment
+              [%5 p=nock q=nock]                        ::  equality test
+              [%6 p=nock q=nock r=nock]                 ::  if, then, else
+              [%7 p=nock q=nock]                        ::  serial compose
+              [%8 p=nock q=nock]                        ::  push onto subject
+              [%9 p=@ q=nock]                           ::  select arm and fire
+              [%10 p=[p=@ q=nock] q=nock]               ::  edit
+              [%11 p=$@(@ [p=@ q=nock]) q=nock]         ::  hint
+              [%12 p=nock q=nock]                       ::  grab data from sky
+              [%0 p=@]                                  ::  axis select
+          ==                                            ::
++$  note                                                ::  type annotation
+          $%  [%help p=help]                            ::  documentation
+              [%know p=stud]                            ::  global standard
+              [%made p=term q=(unit (list wing))]       ::  structure
+          ==                                            ::
++$  type  $+  type
+          $~  %noun                                     ::
+          $@  $?  %noun                                 ::  any nouns
+                  %void                                 ::  no noun
+              ==                                        ::
+          $%  [%atom p=term q=(unit @)]                 ::  atom / constant
+              [%cell p=type q=type]                     ::  ordered pair
+              [%core p=type q=coil]                     ::  object
+              [%face p=$@(term tune) q=type]            ::  namespace
+              [%fork p=(set type)]                      ::  union
+              [%hint p=(pair type note) q=type]         ::  annotation
+              [%hold p=type q=hoon]                     ::  lazy evaluation
+          ==                                            ::
++$  tony                                                ::  ++tone done right
+          $%  [%0 p=tine q=*]                           ::  success
+              [%1 p=(set)]                              ::  blocks
+              [%2 p=(list [@ta *])]                     ::  error ~_s
+          ==                                            ::
++$  tine                                                ::  partial noun
+          $@  ~                                         ::  open
+          $%  [%& p=tine q=tine]                        ::  half-blocked
+              [%| p=(set)]                              ::  fully blocked
+          ==                                            ::
++$  tool  $@(term tune)                                 ::  type decoration
++$  tune                                                ::  complex
+          $~  [~ ~]                                     ::
+          $:  p=(map term (unit hoon))                  ::  aliases
+              q=(list hoon)                             ::  bridges
+          ==                                            ::
++$  typo  type                                          ::  old type
++$  vase  [p=type q=*]                                  ::  type-value pair
++$  vise  [p=typo q=*]                                  ::  old vase
++$  vial  ?(%read %rite %both %free)                    ::  co/contra/in/bi
++$  vair  ?(%gold %iron %lead %zinc)                    ::  in/contra/bi/co
++$  vein  (list (unit axis))                            ::  search trace
++$  sect  (list pica)                                   ::  paragraph
++$  whit                                                ::  prefix docs parse
+  $:  bat=(map cuff (pair cord (list sect)))            ::  batch comment
+  ==                                                    ::
++$  whiz  cord                                          ::  postfix doc parse
++$  what  (unit (pair cord (list sect)))                ::  help slogan/section
++$  wing  (list limb)                                   ::  search path
+::
+::  +block: abstract identity of resource awaited
+::
++$  block
+  path
+::
+::  +result: internal interpreter result
+::
++$  result
+  $@(~ seminoun)
+::
+::  +thunk: fragment constructor
+::
++$  thunk
+  $-(@ud (unit noun))
+::
+::  +seminoun:
+::
++$  seminoun
+  ::  partial noun; blocked subtrees are ~
+  ::
+  $~  [[%full / ~ ~] ~]
+  [mask=stencil data=noun]
+::
+::  +stencil: noun knowledge map
+::
++$  stencil
+  $%  ::
+      ::  %half: noun has partial block substructure
+      ::
+      [%half left=stencil rite=stencil]
+      ::
+      ::  %full: noun is either fully complete, or fully blocked
+      ::
+      [%full blocks=(set block)]
+      ::
+      ::  %lazy: noun can be generated from virtual subtree
+      ::
+      [%lazy fragment=axis resolve=thunk]
+  ==
+::
++$  output
+  ::  ~: interpreter stopped
+  ::
+  %-  unit
+  $%  ::
+      ::  %done: output is complete
+      ::
+      [%done p=noun]
+      ::
+      ::  %wait: output is waiting for resources
+      ::
+      [%wait p=(list block)]
+  ==
+:: profiling
++$  doss
+  $:  mon=moan                                          ::  sample count
+      hit=(map term @ud)                                ::  hit points
+      cut=(map path hump)                               ::  cut points
+  ==
++$  moan                                                ::  sample metric
+  $:  fun=@ud                                           ::  samples in C
+      noc=@ud                                           ::  samples in nock
+      glu=@ud                                           ::  samples in glue
+      mal=@ud                                           ::  samples in alloc
+      far=@ud                                           ::  samples in frag
+      coy=@ud                                           ::  samples in copy
+      euq=@ud                                           ::  samples in equal
+  ==                                                    ::
+::
++$  hump
+  $:  mon=moan                                          ::  sample count
+      out=(map path @ud)                                ::  calls out of
+      inn=(map path @ud)                                ::  calls into
+  ==
+--
+::
+~%    %pen
+    +
+  ==
+    %ap    ap
+    %ut    ut
+  ==
+::    layer-5
+::
+|%
+::
+::    5aa: new partial nock interpreter
++|  %new-partial-nock-interpreter
+::
+++  musk  !.                                            ::  nock with block set
+  |%
+  ++  abet
+    ::    simplify raw result
+    ::
+    |=  $:  ::  noy: raw result
+            ::
+            noy=result
+        ==
+    ^-  output
+    ::  propagate stop
+    ::
+    ?~  noy  ~
+    :-  ~
+    ::  merge all blocking sets
+    ::
+    =/  blocks  (squash mask.noy)
+    ?:  =(~ blocks)
+      ::  no blocks, data is complete
+      ::
+      done/data.noy
+    ::  reduce block set to block list
+    ::
+    wait/~(tap in blocks)
+  ::
+  ++  araw
+    ::    execute nock on partial subject
+    ::
+    |=  $:  ::  bus: subject, a partial noun
+            ::  fol: formula, a complete noun
+            ::
+            bus=seminoun
+            fol=noun
+        ==
+    ::  interpreter loop
+    ::
+    |-  ^-  result
+    ?@  fol
+      ::  bad formula, stop
+      ::
+      ~
+    ?:  ?=(^ -.fol)
+      ::  hed: interpret head
+      ::
+      =+  hed=$(fol -.fol)
+      ::  propagate stop
+      ::
+      ?~  hed  ~
+      ::  tal: interpret tail
+      ::
+      =+  tal=$(fol +.fol)
+      ::  propagate stop
+      ::
+      ?~  tal  ~
+      ::  combine
+      ::
+      (combine hed tal)
+    ?+    fol
+    ::  bad formula; stop
+    ::
+        ~
+    ::  0; fragment
+    ::
+        [%0 b=@]
+      ::  if bad axis, stop
+      ::
+      ?:  =(0 b.fol)  ~
+      ::  reduce to fragment
+      ::
+      (fragment b.fol bus)
+    ::
+    ::  1; constant
+    ::
+        [%1 b=*]
+      ::  constant is complete
+      ::
+      [full/~ b.fol]
+    ::
+    ::  2; recursion
+    ::
+        [%2 b=* c=*]
+      ::  require complete formula
+      ::
+      %+  require
+        ::  compute formula with current subject
+        ::
+        $(fol c.fol)
+      |=  ::  ryf: next formula
+          ::
+          ryf=noun
+      ::  lub: next subject
+      ::
+      =+  lub=^$(fol b.fol)
+      ::  propagate stop
+      ::
+      ?~  lub  ~
+      ::  recurse
+      ::
+      ^$(fol ryf, bus lub)
+    ::
+    ::  3; probe
+    ::
+        [%3 b=*]
+      %+  require
+        $(fol b.fol)
+      |=  ::  fig: probe input
+          ::
+          fig=noun
+      ::  yes if cell, no if atom
+      ::
+      [full/~ .?(fig)]
+    ::
+    ::  4; increment
+    ::
+        [%4 b=*]
+      %+  require
+        $(fol b.fol)
+      |=  ::  fig: increment input
+          ::
+          fig=noun
+      ::  stop for cells, increment for atoms
+      ::
+      ?^(fig ~ [full/~ +(fig)])
+    ::
+    ::  5; compare
+    ::
+        [%5 b=* c=*]
+      %+  require
+        $(fol b.fol)
+      |=  ::  hed: left input
+          ::
+          hed=noun
+      %+  require
+        ^$(fol c.fol)
+      |=  ::  tal: right input
+          ::
+          tal=noun
+      [full/~ =(hed tal)]
+    ::
+    ::  6; if-then-else
+    ::
+        [%6 b=* c=* d=*]
+      ::  semantic expansion
+      ::
+      %+  require
+        $(fol b.fol)
+      |=  ::  fig: boolean
+          ::
+          fig=noun
+      ::  apply proper booleans
+      ::
+      ?:  =(& fig)  ^$(fol c.fol)
+      ?:  =(| fig)  ^$(fol d.fol)
+      ::  stop on bad test
+      ::
+      ~
+    ::
+    ::  7; composition
+    ::
+        [%7 b=* c=*]
+      ::  one: input
+      ::
+      =+  one=$(fol b.fol)
+      ::  propagate stop
+      ::
+      ?~  one  ~
+      ::  complete composition
+      ::
+      $(fol c.fol, bus one)
+    ::
+    ::  8; introduction
+    ::
+        [%8 b=* c=*]
+      ::  one: input
+      ::
+      =+  one=$(fol b.fol)
+      ::  propagate stop
+      ::
+      ?~  one  ~
+      ::  complete introduction
+      ::
+      $(fol c.fol, bus (combine one bus))
+    ::
+    ::  9; invocation
+    ::
+        [%9 b=* c=*]
+      ::  semantic expansion
+      ::
+      ?^  b.fol  ~
+      ::  one: core
+      ::
+      =+  one=$(fol c.fol)
+      ::  propagate stop
+      ::
+      ?~  one  ~
+      ::  if core is constant
+      ::
+      ?:  ?=([[%full ~] *] one)
+        ::  then call virtual nock directly
+        ::
+        =+  (mack data.one [%9 b.fol %0 1])
+        ::  propagate stop
+        ::
+        ?~  -  ~
+        ::  produce result
+        ::
+        [[%full ~] u.-]
+      ::  else complete call
+      ::
+      %+  require
+        ::  retrieve formula
+        ::
+        (fragment b.fol one)
+      ::  continue
+      ::
+      |=(noun ^$(bus one, fol +<))
+    ::
+    ::  10; edit
+    ::
+        [%10 [b=@ c=*] d=*]
+      ::  tar:  target of edit
+      ::
+      =+  tar=$(fol d.fol)
+      ::  propagate stop
+      ::
+      ?~  tar  ~
+      ::  inn:  inner value
+      ::
+      =+  inn=$(fol c.fol)
+      ::  propagate stop
+      ::
+      ?~  inn  ~
+      (mutate b.fol inn tar)
+    ::
+    ::  11; static hint
+    ::
+        [%11 @ c=*]
+      ::  ignore hint
+      ::
+      $(fol c.fol)
+    ::
+    ::  11; dynamic hint
+    ::
+        [%11 [b=* c=*] d=*]
+      ::  noy: dynamic hint
+      ::
+      =+  noy=$(fol c.fol)
+      ::  propagate stop
+      ::
+      ?~  noy  ~
+      ::  if hint is a fully computed trace
+      ::
+      ?:  &(?=(%spot b.fol) ?=([[%full ~] *] noy))
+        ::  compute within trace
+        ::
+        ~_((show %o +.noy) $(fol d.fol))
+      ::  else ignore hint
+      ::
+      $(fol d.fol)
+    ==
+  ::
+  ++  apex
+    ::    execute nock on partial subject
+    ::
+    |=  $:  ::  bus: subject, a partial noun
+            ::  fol: formula, a complete noun
+            ::
+            bus=seminoun
+            fol=noun
+        ==
+    ~+
+    ^-  output
+    ::  simplify result
+    ::
+    (abet (araw bus fol))
+  ::
+  ++  combine
+    ::    combine a pair of seminouns
+    ::
+    |=  $:  ::  hed: head of pair
+            ::  tal: tail of pair
+            ::
+            hed=seminoun
+            tal=seminoun
+        ==
+    ^-  seminoun
+    ?.  ?&  &(?=(%full -.mask.hed) ?=(%full -.mask.tal))
+            =(=(~ blocks.mask.hed) =(~ blocks.mask.tal))
+        ==
+      ::  default merge
+      ::
+      [half/[mask.hed mask.tal] [data.hed data.tal]]
+    ::  both sides total
+    ::
+    ?:  =(~ blocks.mask.hed)
+      ::  both sides are complete
+      ::
+      [full/~ data.hed data.tal]
+    ::  both sides are blocked
+    ::
+    [full/(~(uni in blocks.mask.hed) blocks.mask.tal) ~]
+  ::
+  ++  complete
+    ::    complete any laziness
+    ::
+    |=  bus=seminoun
+    ^-  seminoun
+    ?-  -.mask.bus
+      %full  bus
+      %lazy  ::  fragment 1 is the whole thing
+             ::
+             ?:  =(1 fragment.mask.bus)
+               ::  blocked; we can't get fragment 1 while compiling it
+               ::
+               [[%full [~ ~ ~]] ~]
+             ::  execute thunk
+             ::
+             =+  (resolve.mask.bus fragment.mask.bus)
+             ::  if product is nil
+             ::
+             ?~  -
+               ::  then blocked
+               ::
+               [[%full [~ ~ ~]] ~]
+             ::  else use value
+             ::
+             [[%full ~] u.-]
+      %half  ::  recursive descent
+             ::
+             %+  combine
+               $(bus [left.mask.bus -.data.bus])
+             $(bus [rite.mask.bus +.data.bus])
+    ==
+  ::
+  ++  fragment
+    ::    seek to an axis in a seminoun
+    ::
+    |=  $:  ::  axe: tree address of subtree
+            ::  bus: partial noun
+            ::
+            axe=axis
+            bus=seminoun
+        ==
+    ^-  result
+    ::  1 is the root
+    ::
+    ?:  =(1 axe)  bus
+    ::  now: top of axis (2 or 3)
+    ::  lat: rest of axis
+    ::
+    =+  [now=(cap axe) lat=(mas axe)]
+    ?-  -.mask.bus
+      %lazy  ::  propagate laziness
+             ::
+             bus(fragment.mask (peg fragment.mask.bus axe))
+    ::
+      %full  ::  if fully blocked, produce self
+             ::
+             ?^  blocks.mask.bus  bus
+             ::  descending into atom, stop
+             ::
+             ?@  data.bus  ~
+             ::  descend into complete cell
+             ::
+             $(axe lat, bus [full/~ ?:(=(2 now) -.data.bus +.data.bus)])
+    ::
+      %half  ::  descend into partial cell
+             ::
+             %=  $
+               axe  lat
+               bus  ?:  =(2 now)
+                      [left.mask.bus -.data.bus]
+                    [rite.mask.bus +.data.bus]
+    ==       ==
+  ::
+  ++  mutate
+    ::    change a single axis in a seminoun
+    ::
+    |=  $:  ::  axe: axis within big to change
+            ::  lit: (little) seminoun to insert within big at axe
+            ::  big: seminoun to mutate
+            ::
+            axe=@
+            lit=seminoun
+            big=seminoun
+        ==
+    ^-  result
+    ::  stop on zero axis
+    ::
+    ?~  axe  ~
+    ::  edit root of big means discard it
+    ::
+    ?:  =(1 axe)  lit
+    ::  decompose axis into path of head-tail
+    ::
+    |-  ^-  result
+    ?:  =(2 axe)
+      ::  mutate head of cell
+      ::
+      =+  tal=(fragment 3 big)
+      ::  propagate stop
+      ::
+      ?~  tal  ~
+      (combine lit tal)
+    ?:  =(3 axe)
+      ::  mutate tail of cell
+      ::
+      =+  hed=(fragment 2 big)
+      ::  propagate stop
+      ::
+      ?~  hed  ~
+      (combine hed lit)
+    ::  deeper axis: keep one side of big and
+    ::  recurse into the other with smaller axe
+    ::
+    =+  mor=(mas axe)
+    =+  hed=(fragment 2 big)
+    ::  propagate stop
+    ::
+    ?~  hed  ~
+    =+  tal=(fragment 3 big)
+    ::  propagate stop
+    ::
+    ?~  tal  ~
+    ?:  =(2 (cap axe))
+      ::  recurse into the head
+      ::
+      =+  mut=$(big hed, axe mor)
+      ::  propagate stop
+      ::
+      ?~  mut  ~
+      (combine mut tal)
+    ::  recurse into the tail
+    ::
+    =+  mut=$(big tal, axe mor)
+    ::  propagate stop
+    ::
+    ?~  mut  ~
+    (combine hed mut)
+  ::
+  ++  require
+    ::    require complete intermediate step
+    ::
+    |=  $:  noy=result
+            yen=$-(* result)
+        ==
+    ^-  result
+    ::  propagate stop
+    ::
+    ?~  noy  ~
+    ::  suppress laziness
+    ::
+    =/  bus=seminoun  (complete noy)
+    ?<  ?=(%lazy -.mask.bus)
+    ::  if partial block, squash blocks and stop
+    ::
+    ?:  ?=(%half -.mask.bus)  [full/(squash mask.bus) ~]
+    ::  if full block, propagate block
+    ::
+    ?:  ?=(^ blocks.mask.bus)  [mask.bus ~]
+    ::  otherwise use complete noun
+    ::
+    (yen data.bus)
+  ::
+  ++  squash
+    ::    convert stencil to block set
+    ::
+    |=  tyn=stencil
+    ^-  (set block)
+    ?-  -.tyn
+      %lazy  $(tyn -:(complete tyn ~))
+      %full  blocks.tyn
+      %half  (~(uni in $(tyn left.tyn)) $(tyn rite.tyn))
+    ==
+  --
+::
+::    5a: compiler utilities
++|  %compiler-utilities
+::
+++  bool                                                ::  make loobean
+  ^-  type
+  (fork [%atom %f `%.y] [%atom %f `%.n] ~)
+::
+++  cell                                                ::  make %cell type
+  ~/  %cell
+  |=  [hed=type tal=type]
+  ^-  type
+  ?:(=(%void hed) %void ?:(=(%void tal) %void [%cell hed tal]))
+::
+++  core                                                ::  make %core type
+  ~/  %core
+  |=  [pac=type con=coil]
+  ^-  type
+  ?:(=(%void pac) %void [%core pac con])
+::
+++  hint
+  |=  [p=(pair type note) q=type]
+  ^-  type
+  ?:  =(%void q)  %void
+  ?:  =(%noun q)  %noun
+  [%hint p q]
+::
+++  face                                                ::  make %face type
+  ~/  %face
+  |=  [giz=$@(term tune) der=type]
+  ^-  type
+  ?:  =(%void der)
+    %void
+  [%face giz der]
+::
+++  fork                                                ::  make %fork type
+  ~/  %fork
+  |=  yed=(list type)
+  =|  lez=(set type)
+  |-  ^-  type
+  ?~  yed
+    ?~  lez  %void
+    ?:  ?=([* ~ ~] lez)  n.lez
+    [%fork lez]
+  %=    $
+      yed  t.yed
+      lez
+    ?:  =(%void i.yed)  lez
+    ?:  ?=([%fork *] i.yed)  (~(uni in lez) p.i.yed)
+    (~(put in lez) i.yed)
+  ==
+::
+++  cove                                                ::  extract [0 *] axis
+  |=  nug=nock
+  ?-    nug
+      [%0 *]   p.nug
+      [%11 *]  $(nug q.nug)
+      *        ~_(leaf+"cove" !!)
+  ==
+++  comb                                                ::  combine two formulas
+  ~/  %comb
+  |=  [mal=nock buz=nock]
+  ^-  nock
+  ?:  ?&(?=([%0 *] mal) !=(0 p.mal))
+    ?:  ?&(?=([%0 *] buz) !=(0 p.buz))
+      [%0 (peg p.mal p.buz)]
+    ?:  ?=([%2 [%0 *] [%0 *]] buz)
+      [%2 [%0 (peg p.mal p.p.buz)] [%0 (peg p.mal p.q.buz)]]
+    [%7 mal buz]
+  ?:  ?=([^ [%0 %1]] mal)
+    [%8 p.mal buz]
+  ?:  =([%0 %1] buz)
+    mal
+  [%7 mal buz]
+::
+++  cond                                                ::  ?:  compile
+  ~/  %cond
+  |=  [pex=nock yom=nock woq=nock]
+  ^-  nock
+  ?:  =([%1 &] pex)  yom
+  ?:  =([%1 |] pex)  woq
+  ?:  =([%0 0] pex)  pex
+  [%6 pex yom woq]
+::
+++  cons                                                ::  make formula cell
+  ~/  %cons
+  |=  [vur=nock sed=nock]
+  ^-  nock
+  ::  this optimization can remove crashes which are essential
+  ::
+  ::  ?:  ?=([[%0 *] [%0 *]] +<)
+  ::  ?:  ?&(=(+(p.vur) p.sed) =((div p.vur 2) (div p.sed 2)))
+  ::    [%0 (div p.vur 2)]
+  ::  [vur sed]
+  ?:  ?=([[%1 *] [%1 *]] +<)
+    [%1 p.vur p.sed]
+  [vur sed]
+::
+++  fitz                                                ::  odor compatibility
+  ~/  %fitz
+  |=  [yaz=term wix=term]
+  =+  ^=  fiz
+      |=  mot=@ta  ^-  [p=@ q=@ta]
+      =+  len=(met 3 mot)
+      ?:  =(0 len)
+        [0 %$]
+      =+  tyl=(rsh [3 (dec len)] mot)
+      ?:  &((gte tyl 'A') (lte tyl 'Z'))
+        [(sub tyl 64) (end [3 (dec len)] mot)]
+      [0 mot]
+  =+  [yoz=(fiz yaz) wux=(fiz wix)]
+  ?&  ?|  =(0 p.yoz)
+          =(0 p.wux)
+          &(!=(0 p.wux) (lte p.wux p.yoz))
+      ==
+      |-  ?|  =(%$ q.yoz)
+              =(%$ q.wux)
+              ?&  =((end 3 q.yoz) (end 3 q.wux))
+                  $(q.yoz (rsh 3 q.yoz), q.wux (rsh 3 q.wux))
+              ==
+          ==
+  ==
+::
+++  flan                                                ::  loobean  &
+  ~/  %flan
+  |=  [bos=nock nif=nock]
+  ^-  nock
+  ?:  ?|  =(bos nif)
+          =([%1 |] bos)
+          =([%1 &] nif)
+          =([%0 0] bos)
+      ==
+    bos
+  ?:  ?|  =([%1 &] bos)
+          =([%1 |] nif)
+          =([%0 0] nif)
+      ==
+    nif
+  [%6 bos nif [%1 |]]
+::
+++  flip                                                ::  loobean negation
+  ~/  %flip
+  |=  dyr=nock
+  ^-  nock
+  ?:  =([%1 &] dyr)  [%1 |]
+  ?:  =([%1 |] dyr)  [%1 &]
+  ?:  =([%0 0] dyr)  dyr
+  [%6 dyr [%1 |] %1 &]
+::
+++  flor                                                ::  loobean  |
+  ~/  %flor
+  |=  [bos=nock nif=nock]
+  ^-  nock
+  ?:  ?|  =(bos nif)
+          =([%1 &] bos)
+          =([%1 |] nif)
+          =([%0 0] bos)
+      ==
+    bos
+  ?:  ?|  =([%1 |] bos)
+          =([%1 &] nif)
+          =([%0 0] nif)
+      ==
+    nif
+  [%6 bos [%1 &] nif]
+::
+++  hike
+  ~/  %hike
+  |=  [a=axis pac=(list (pair axis nock))]
+  |^  =/  rel=(map axis nock)  (roll pac insert)
+      =/  ord=(list axis)      (sort ~(tap in ~(key by rel)) gth)
+      |-  ^-  nock
+      ?~  ord
+        [%0 a]
+      =/  b=axis  i.ord
+      =/  c=nock  (~(got by rel) b)
+      =/  d=nock  $(ord t.ord)
+      [%10 [b c] d]
+  ::
+  ++  contains
+    |=  [container=axis contained=axis]
+    ^-  ?
+    =/  big=@    (met 0 container)
+    =/  small=@  (met 0 contained)
+    ?:  (lte small big)  |
+    =/  dif=@  (sub small big)
+    =(container (rsh [0 dif] contained))
+  ::
+  ++  parent
+    |=  a=axis
+    `axis`(rsh 0 a)
+  ::
+  ++  sibling
+    |=  a=axis
+    ^-  axis
+    ?~  (mod a 2)
+      +(a)
+    (dec a)
+  ::
+  ++  insert
+    |=  [e=[axe=axis fol=nock] n=(map axis nock)]
+    ^-  (map axis nock)
+    ?:  =/  a=axis  axe.e
+        |-  ^-  ?
+        ?:  =(1 a)  |
+        ?:  (~(has by n) a)
+          &
+        $(a (parent a))
+      ::  parent already in
+      n
+    =.  n
+      ::  remove children
+      %+  roll  ~(tap by n)
+      |=  [[axe=axis fol=nock] m=_n]
+      ?.  (contains axe.e axe)  m
+      (~(del by m) axe)
+    =/  sib  (sibling axe.e)
+    =/  un   (~(get by n) sib)
+    ?~  un   (~(put by n) axe.e fol.e)
+    ::  replace sibling with parent
+    %=  $
+      n  (~(del by n) sib)
+      e  :-  (parent sib)
+         ?:  (gth sib axe.e)
+           (cons fol.e u.un)
+         (cons u.un fol.e)
+    ==
+  --
+::
+++  jock
+  |=  rad=?
+  |=  lot=coin  ^-  hoon
+  ?-    -.lot
+      ~
+    ?:(rad [%rock p.lot] [%sand p.lot])
+  ::
+      %blob
+    ?:  rad
+      [%rock %$ p.lot]
+    ?@(p.lot [%sand %$ p.lot] [$(p.lot -.p.lot) $(p.lot +.p.lot)])
+  ::
+      %many
+    [%cltr (turn p.lot |=(a=coin ^$(lot a)))]
+  ==
+::
+++  look
+  ~/  %look
+  |=  [cog=term dab=(map term hoon)]
+  =+  axe=1
+  |-  ^-  (unit [p=axis q=hoon])
+  ?-  dab
+      ~  ~
+  ::
+      [* ~ ~]
+    ?:(=(cog p.n.dab) [~ axe q.n.dab] ~)
+  ::
+      [* ~ *]
+    ?:  =(cog p.n.dab)
+      [~ (peg axe 2) q.n.dab]
+    ?:  (gor cog p.n.dab)
+      ~
+    $(axe (peg axe 3), dab r.dab)
+  ::
+      [* * ~]
+    ?:  =(cog p.n.dab)
+      [~ (peg axe 2) q.n.dab]
+    ?:  (gor cog p.n.dab)
+      $(axe (peg axe 3), dab l.dab)
+    ~
+  ::
+      [* * *]
+    ?:  =(cog p.n.dab)
+      [~ (peg axe 2) q.n.dab]
+    ?:  (gor cog p.n.dab)
+      $(axe (peg axe 6), dab l.dab)
+    $(axe (peg axe 7), dab r.dab)
+  ==
+::
+++  loot
+  ~/  %loot
+  |=  [cog=term dom=(map term tome)]
+  =+  axe=1
+  |-  ^-  (unit [p=axis q=hoon])
+  ?-  dom
+      ~  ~
+  ::
+      [* ~ ~]
+    %+  bind  (look cog q.q.n.dom)
+    |=((pair axis hoon) [(peg axe p) q])
+  ::
+      [* ~ *]
+    =+  yep=(look cog q.q.n.dom)
+    ?^  yep
+      [~ (peg (peg axe 2) p.u.yep) q.u.yep]
+    $(axe (peg axe 3), dom r.dom)
+  ::
+      [* * ~]
+    =+  yep=(look cog q.q.n.dom)
+    ?^  yep
+      [~ (peg (peg axe 2) p.u.yep) q.u.yep]
+    $(axe (peg axe 3), dom l.dom)
+  ::
+      [* * *]
+    =+  yep=(look cog q.q.n.dom)
+    ?^  yep
+      [~ (peg (peg axe 2) p.u.yep) q.u.yep]
+    =+  pey=$(axe (peg axe 6), dom l.dom)
+    ?^  pey  pey
+    $(axe (peg axe 7), dom r.dom)
+  ==
+::
+::    5b: macro expansion
++|  %macro-expansions
+::
+++  ah                                                  ::  tiki engine
+  |_  tik=tiki
+  ++  blue
+    |=  gen=hoon
+    ^-  hoon
+    ?.  &(?=(%| -.tik) ?=(~ p.tik))  gen
+    [%tsgr [%$ 3] gen]
+  ::
+  ++  teal
+    |=  mod=spec
+    ^-  spec
+    ?:  ?=(%& -.tik)  mod
+    [%over [%& 3]~ mod]
+  ::
+  ++  tele
+    |=  syn=skin
+    ^-  skin
+    ?:  ?=(%& -.tik)  syn
+    [%over [%& 3]~ syn]
+  ::
+  ++  gray
+    |=  gen=hoon
+    ^-  hoon
+    ?-  -.tik
+      %&  ?~(p.tik gen [%tstr [u.p.tik ~] [%wing q.tik] gen])
+      %|  [%tsls ?~(p.tik q.tik [%ktts u.p.tik q.tik]) gen]
+    ==
+  ::
+  ++  puce
+    ^-  wing
+    ?-  -.tik
+      %&  ?~(p.tik q.tik [u.p.tik ~])
+      %|  [[%& 2] ~]
+    ==
+  ::
+  ++  wthp  |=  opt=(list (pair spec hoon))
+            %+  gray  %wthp
+            [puce (turn opt |=([a=spec b=hoon] [a (blue b)]))]
+  ++  wtkt  |=([sic=hoon non=hoon] (gray [%wtkt puce (blue sic) (blue non)]))
+  ++  wtls  |=  [gen=hoon opt=(list (pair spec hoon))]
+            %+  gray  %wtls
+            [puce (blue gen) (turn opt |=([a=spec b=hoon] [a (blue b)]))]
+  ++  wtpt  |=([sic=hoon non=hoon] (gray [%wtpt puce (blue sic) (blue non)]))
+  ++  wtsg  |=([sic=hoon non=hoon] (gray [%wtsg puce (blue sic) (blue non)]))
+  ++  wthx  |=(syn=skin (gray [%wthx (tele syn) puce]))
+  ++  wtts  |=(mod=spec (gray [%wtts (teal mod) puce]))
+  --
+::
+++  ax
+  =+  :*  ::  .dom: axis to home
+          ::  .hay: wing to home
+          ::  .cox: hygienic context
+          ::  .bug: debug annotations
+          ::  .nut: annotations
+          ::  .def: default expression
+          ::
+          dom=`axis`1
+          hay=*wing
+          cox=*(map term spec)
+          bug=*(list spot)
+          nut=*(unit note)
+          def=*(unit hoon)
+      ==
+  |_  mod=spec
+  ::
+  ++  autoname
+    ::    derive name from spec
+    ::
+    |-  ^-  (unit term)
+    ?-  -.mod
+      %base  ?.(?=([%atom *] p.mod) ~ ?:(=(%$ p.p.mod) `%atom `p.p.mod))
+      %dbug  $(mod q.mod)
+      %gist  $(mod q.mod)
+      %leaf  `p.mod
+      %loop  `p.mod
+      %like  ?~(p.mod ~ ?^(i.p.mod ?:(?=(%& -.i.p.mod) ~ q.i.p.mod) `i.p.mod))
+      %make  ~(name ap p.mod)
+      %made  $(mod q.mod)
+      %over  $(mod q.mod)
+      %name  $(mod q.mod)
+    ::
+      %bcbc  $(mod p.mod)
+      %bcbr  $(mod p.mod)
+      %bccb  ~(name ap p.mod)
+      %bccl  $(mod i.p.mod)
+      %bccn  $(mod i.p.mod)
+      %bcdt  ~
+      %bcgl  $(mod q.mod)
+      %bcgr  $(mod q.mod)
+      %bchp  $(mod p.mod)
+      %bckt  $(mod q.mod)
+      %bcls  $(mod q.mod)
+      %bcfs  ~
+      %bcmc  ~(name ap p.mod)
+      %bcpm  $(mod p.mod)
+      %bcsg  $(mod q.mod)
+      %bctc  ~
+      %bcts  $(mod q.mod)
+      %bcpt  $(mod q.mod)
+      %bcwt  $(mod i.p.mod)
+      %bczp  ~
+    ==
+  ::
+  ++  function
+    ::    construct a function example
+    ::
+    |=  [fun=spec arg=spec]
+    ^-  hoon
+    ::  minimal context as subject
+    ::
+    :+  %tsgr
+      ::  context is example of both specs
+      ::
+      [example:clear(mod fun) example:clear(mod arg)]
+    ::  produce an %iron (contravariant) core
+    ::
+    :-  %ktbr
+    ::  make an actual gate
+    ::
+    :+  %brcl
+      [%$ 2]
+    [%$ 15]
+  ::
+  ++  interface
+    ::    construct a core example
+    ::
+    |=  [variance=vair payload=spec arms=(map term spec)]
+    ^-  hoon
+    ::  attach proper variance control
+    ::
+    =-  ?-  variance
+          %gold  -
+          %lead  [%ktwt -]
+          %zinc  [%ktpm -]
+          %iron  [%ktbr -]
+        ==
+    ^-  hoon
+    :+  %tsgr  example:clear(mod payload)
+    :+  %brcn  ~
+    =-  [[%$ ~ -] ~ ~]
+    %-  ~(gas by *(map term hoon))
+    %+  turn
+      ~(tap by arms)
+    |=  [=term =spec]
+    ::
+    ::  note that we *don't* make arm specs in an interface
+    ::  hygienic -- we leave them in context, to support
+    ::  maximum programmer flexibility
+    ::
+    [term example:clear(mod spec)]
+  ::
+  ++  home
+    ::    express a hoon against the original subject
+    ::
+    |=  gen=hoon
+    ^-  hoon
+    =/  ,wing
+        ?:  =(1 dom)
+          hay
+        (weld hay `wing`[[%& dom] ~])
+    ?~  -  gen
+    [%tsgr [%wing -] gen]
+  ::
+  ++  clear
+    ::    clear annotations
+    ^+  .
+    .(bug ~, def ~, nut ~)
+  ::
+  ++  basal
+    ::    example base case
+    ::
+    |=  bas=base
+    ?-    bas
+    ::
+        [%atom *]
+      ::  we may want sped
+      ::
+      [%sand p.bas ?:(=(%da p.bas) ~2000.1.1 0)]
+    ::
+        %noun
+      ::  raw nock produces noun type
+      ::
+      =+([%rock %$ 0] [%ktls [%dttr - - [%rock %$ 1]] -])
+    ::
+        %cell
+      ::  reduce to pair of nouns
+      ::
+      =+($(bas %noun) [- -])
+    ::
+        %flag
+      ::  comparison produces boolean type
+      ::
+      =+([%rock %$ 0] [%ktls [%dtts - -] -])
+    ::
+        %null
+      [%rock %n 0]
+    ::
+        %void
+      [%zpzp ~]
+    ==
+  ::
+  ++  unfold
+    |=  [fun=hoon arg=(list spec)]
+    ^-  hoon
+    [%cncl fun (turn arg |=(spec ktcl/+<))]
+  ::
+  ++  unreel
+    |=  [one=wing res=(list wing)]
+    ^-  hoon
+    ?~(res [%wing one] [%tsgl [%wing one] $(one i.res, res t.res)])
+  ::
+  ++  descend
+    ::    record an axis to original subject
+    ::
+    |=  axe=axis
+    +>(dom (peg axe dom))
+  ::
+  ++  decorate
+    ::    apply documentation to expression
+    ::
+    |=  gen=hoon
+    ^-  hoon
+    =-  ?~(nut - [%note u.nut -])
+    |-
+    ?~(bug gen [%dbug i.bug $(bug t.bug)])
+  ::
+  ++  pieces
+    ::    enumerate tuple wings
+    ::
+    |=  =(list term)
+    ^-  (^list wing)
+    (turn list |=(=term `wing`[term ~]))
+  ::
+  ++  spore
+    ::    build default sample
+    ::
+    ^-  hoon
+    ::  sample is always typeless
+    ::
+    :+  %ktls
+      [%bust %noun]
+    ::  consume debugging context
+    ::
+    %-  decorate
+    ::  use home as subject
+    ::
+    %-  home
+    ::  if default is set, use it
+    ::
+    ?^  def  u.def
+    ::  else map structure to expression
+    ::
+    ~+
+    |-  ^-  hoon
+    ?-  mod
+      [%base *]  ?:(=(%void p.mod) [%rock %n 0] (basal p.mod))
+      [%bcbc *]  ::  track hygienic recursion points lexically
+                 ::
+                 %=  $
+                   mod  p.mod
+                   cox  ::  merge lexically and don't forget %$
+                        ::
+                        (~(put by ^+(cox (~(uni by cox) q.mod))) %$ p.mod)
+                 ==
+      [%dbug *]  [%dbug p.mod $(mod q.mod)]
+      [%gist *]  $(mod q.mod)
+      [%leaf *]  [%rock p.mod q.mod]
+      [%loop *]  ~|([%loop p.mod] $(mod (~(got by cox) p.mod)))
+      [%like *]  $(mod bcmc/(unreel p.mod q.mod))
+      [%made *]  $(mod q.mod)
+      [%make *]  $(mod bcmc/(unfold p.mod q.mod))
+      [%name *]  $(mod q.mod)
+      [%over *]  $(hay p.mod, mod q.mod)
+    ::
+      [%bcbr *]  $(mod p.mod)
+      [%bccb *]  [%rock %n 0]
+      [%bccl *]  |-  ^-  hoon
+                 ?~  t.p.mod  ^$(mod i.p.mod)
+                 :-  ^$(mod i.p.mod)
+                 $(i.p.mod i.t.p.mod, t.p.mod t.t.p.mod)
+      [%bccn *]  ::  use last entry
+                 ::
+                 |-  ^-  hoon
+                 ?~  t.p.mod  ^$(mod i.p.mod)
+                 $(i.p.mod i.t.p.mod, t.p.mod t.t.p.mod)
+      [%bchp *]  ::  see under %bccb
+                 ::
+                 [%rock %n 0]
+      [%bcgl *]  $(mod q.mod)
+      [%bcgr *]  $(mod q.mod)
+      [%bckt *]  $(mod q.mod)
+      [%bcls *]  [%note [%know p.mod] $(mod q.mod)]
+      [%bcmc *]  ::  borrow sample
+                 ::
+                 [%tsgl [%$ 6] p.mod]
+      [%bcpm *]  $(mod p.mod)
+      [%bcsg *]  [%kthp q.mod p.mod]
+      [%bcts *]  [%ktts p.mod $(mod q.mod)]
+      [%bcpt *]  $(mod p.mod)
+      [%bcwt *]  ::  use last entry
+                 ::
+                 |-  ^-  hoon
+                 ?~  t.p.mod  ^$(mod i.p.mod)
+                 $(i.p.mod i.t.p.mod, t.p.mod t.t.p.mod)
+      [%bcdt *]  [%rock %n 0]
+      [%bcfs *]  [%rock %n 0]
+      [%bctc *]  [%rock %n 0]
+      [%bczp *]  [%rock %n 0]
+    ==
+  ::
+  ++  example
+    ::    produce a correctly typed default instance
+    ::
+    ~+
+    ^-  hoon
+    ?+  mod
+      ::  in the general case, make and analyze a spore
+      ::
+      :+  %tsls
+        spore
+      ~(relative analyze:(descend 3) 2)
+    ::
+      [%base *]  (decorate (basal p.mod))
+      [%dbug *]  example(mod q.mod, bug [p.mod bug])
+      [%gist *]  example(mod q.mod, nut `p.mod)
+      [%leaf *]  (decorate [%rock p.mod q.mod])
+      [%like *]  example(mod bcmc/(unreel p.mod q.mod))
+      [%loop *]  [%limb p.mod]
+      [%made *]  example(mod q.mod, nut `made/[p.p.mod `(pieces q.p.mod)])
+      [%make *]  example(mod bcmc/(unfold p.mod q.mod))
+      [%name *]  example(mod q.mod, nut `made/[p.mod ~])
+      [%over *]  example(hay p.mod, mod q.mod)
+    ::
+      [%bccb *]  (decorate (home p.mod))
+      [%bccl *]  %-  decorate
+                 |-  ^-  hoon
+                 ?~  t.p.mod
+                   example:clear(mod i.p.mod)
+                 :-  example:clear(mod i.p.mod)
+                 example:clear(i.p.mod i.t.p.mod, t.p.mod t.t.p.mod)
+      [%bchp *]  (decorate (function:clear p.mod q.mod))
+      [%bcmc *]  (decorate (home [%tsgl [%limb %$] p.mod]))
+      [%bcsg *]  [%ktls example(mod q.mod) (home p.mod)]
+      [%bcls *]  (decorate [%note [%know p.mod] example(mod q.mod)])
+      [%bcts *]  (decorate [%ktts p.mod example:clear(mod q.mod)])
+      [%bcdt *]  (decorate (home (interface %gold p.mod q.mod)))
+      [%bcfs *]  (decorate (home (interface %iron p.mod q.mod)))
+      [%bczp *]  (decorate (home (interface %lead p.mod q.mod)))
+      [%bctc *]  (decorate (home (interface %zinc p.mod q.mod)))
+    ==
+  ::
+  ++  factory
+    ::    make a normalizing gate (mold)
+    ::
+    ^-  hoon
+    ::  process annotations outside construct, to catch default
+    ::
+    ::TODO: try seeing if putting %gist in here fixes %brbc
+    ?:  ?=(%dbug -.mod)  factory(mod q.mod, bug [p.mod bug])
+    ?:  ?=(%bcsg -.mod)  factory(mod q.mod, def `[%kthp q.mod p.mod])
+    ^-  hoon
+    ::  if we recognize an indirection
+    ::
+    ?:  &(=(~ def) ?=(?(%bcmc %like %loop %make) -.mod))
+      ::  then short-circuit it
+      ::
+      %-  decorate
+      %-  home
+      ?-  -.mod
+        %bcmc  p.mod
+        %like  (unreel p.mod q.mod)
+        %loop  [%limb p.mod]
+        %make  (unfold p.mod q.mod)
+      ==
+    ::  else build a gate
+    ::
+    :+  %brcl
+      [%ktsg spore]
+    :+  %tsls
+      ~(relative analyze:(descend 7) 6)
+    ::  trigger unifying equality
+    ::
+    :+  %tsls  [%dtts $/14 $/2]
+    $/6
+  ::
+  ++  analyze
+    ::    normalize a fragment of the subject
+    ::
+    |_  $:  ::  axe: axis to fragment
+            ::
+            axe=axis
+        ==
+    ++  basic
+      |=  bas=base
+      ^-  hoon
+      ?-    bas
+          [%atom *]
+        :+  %ktls  example
+        ^-  hoon
+        :^    %zppt
+            [[[%| 0 `%ruth] ~] ~]
+          [%cnls [%limb %ruth] [%sand %ta p.bas] fetch]
+        [%wtpt fetch-wing fetch [%zpzp ~]]
+      ::
+          %cell
+        :+  %ktls  example
+        =+  fetch-wing
+        :-  [%wing [[%& %2] -]]
+            [%wing [[%& %3] -]]
+      ::
+          %flag
+        :^    %wtcl
+            [%dtts [%rock %$ &] [%$ axe]]
+          [%rock %f &]
+        :+  %wtgr
+          [%dtts [%rock %$ |] [%$ axe]]
+        [%rock %f |]
+      ::
+          %noun
+        fetch
+      ::
+          %null
+        :+  %wtgr
+          [%dtts [%bust %noun] [%$ axe]]
+        [%rock %n ~]
+      :::
+          %void
+        [%zpzp ~]
+      ==
+    ++  clear
+      .(..analyze ^clear)
+    ::
+    ++  fetch
+      ::    load the fragment
+      ::
+      ^-  hoon
+      [%$ axe]
+    ::
+    ++  fetch-wing
+      ::    load, as a wing
+      ::
+      ^-  wing
+      [[%& axe] ~]
+    ::
+    ++  choice
+      ::    match full models, by trying them
+      ::
+      |=  $:  ::  one: first option
+              ::  rep: other options
+              ::
+              one=spec
+              rep=(list spec)
+          ==
+      ^-  hoon
+      ::  if no other choices, construct head
+      ::
+      ?~  rep  relative:clear(mod one)
+      ::  build test
+      ::
+      :^    %wtcl
+          ::  if we fit the type of this choice
+          ::
+          [%fits example:clear(mod one) fetch-wing]
+        ::  build with this choice
+        ::
+        relative:clear(mod one)
+      ::  continue through loop
+      ::
+      $(one i.rep, rep t.rep)
+    ::
+    ++  switch
+      |=  $:  ::  one: first format
+              ::  two: more formats
+              ::
+              one=spec
+              rep=(list spec)
+          ==
+      |-  ^-  hoon
+      ::  if no other choices, construct head
+      ::
+      ?~  rep  relative:clear(mod one)
+      ::  fin: loop completion
+      ::
+      =/  fin=hoon  $(one i.rep, rep t.rep)
+      ::  interrogate this instance
+      ::
+      :^    %wtcl
+          ::  test if the head matches this wing
+          ::
+          :+  %fits
+            [%tsgl [%$ 2] example:clear(mod one)]
+          fetch-wing(axe (peg axe 2))
+        ::  if so, use this form
+        ::
+        relative:clear(mod one)
+      ::  continue in the loop
+      ::
+      fin
+    ::
+    ++  relative
+      ::    local constructor
+      ::
+      ~+
+      ^-  hoon
+      ?-    mod
+      ::
+      ::  base
+      ::
+          [%base *]
+        (decorate (basic:clear p.mod))
+      ::
+      ::  debug
+      ::
+          [%dbug *]
+        relative(mod q.mod, bug [p.mod bug])
+      ::
+      ::  formal comment
+      ::
+          [%gist *]
+        relative(mod q.mod, nut `p.mod)
+      ::
+      ::  constant
+      ::
+          [%leaf *]
+        %-  decorate
+        :+  %wtgr
+          [%dtts fetch [%rock %$ q.mod]]
+        [%rock p.mod q.mod]
+      ::
+      ::  composite
+      ::
+          [%make *]
+        relative(mod bcmc/(unfold p.mod q.mod))
+      ::
+      ::  indirect
+      ::
+          [%like *]
+        relative(mod bcmc/(unreel p.mod q.mod))
+      ::
+      ::  loop
+      ::
+          [%loop *]
+        (decorate [%cnhp [%limb p.mod] fetch])
+      ::
+      ::  simple named structure
+      ::
+          [%name *]
+        relative(mod q.mod, nut `made/[p.mod ~])
+      ::
+      ::  synthetic named structure
+      ::
+          [%made *]
+        relative(mod q.mod, nut `made/[p.p.mod `(pieces q.p.mod)])
+      ::
+      ::  subjective
+      ::
+          [%over *]
+        relative(hay p.mod, mod q.mod)
+      ::
+      ::  recursive, $$
+      ::
+          [%bcbc *]
+        ::
+        ::  apply semantically
+        ::
+        :+  %brkt
+          relative(mod p.mod, dom (peg 3 dom))
+        =-  [[%$ ~ -] ~ ~]
+        %-  ~(gas by *(map term hoon))
+        ^-  (list (pair term hoon))
+        %+  turn
+          ~(tap by q.mod)
+        |=  [=term =spec]
+        [term relative(mod spec, dom (peg 3 dom))]
+      ::
+      ::  normalize, $&
+      ::
+          [%bcpm *]
+        ::  push the raw result
+        ::
+        :+  %tsls  relative(mod p.mod)
+        ::  push repair function
+        ::
+        :+  %tsls
+          [%tsgr $/3 q.mod]
+        ::  push repaired product
+        ::
+        :+  %tsls
+          [%cnhp $/2 $/6]
+        ::  sanity-check repaired product
+        ::
+        :+  %wtgr
+          ::  either
+          ::
+          :~  %wtbr
+              ::  the repair did not change anything
+              ::
+              [%dtts $/14 $/2]
+              ::  when we fix it again, it stays fixed
+              ::
+              [%dtts $/2 [%cnhp $/6 $/2]]
+          ==
+        $/2
+      ::
+      ::  verify, $|
+      ::
+          [%bcbr *]
+        ^-  hoon
+        ::  push the raw product
+        ::
+        :+  %tsls  relative(mod p.mod)
+        ^-  hoon
+        ::  assert
+        ::
+        :+  %wtgr
+          ::  run the verifier
+          ::
+          [%cnhp [%tsgr $/3 q.mod] $/2]
+        ::  produce verified product
+        ::
+        $/2
+      ::
+      ::  special, $_
+      ::
+          [%bccb *]
+        (decorate (home p.mod))
+      ::
+      ::  switch, $%
+      ::
+          [%bccn *]
+        (decorate (switch i.p.mod t.p.mod))
+      ::
+      ::  tuple, $:
+      ::
+          [%bccl *]
+        %-  decorate
+        |-  ^-  hoon
+        ?~  t.p.mod
+          relative:clear(mod i.p.mod)
+        :-  relative:clear(mod i.p.mod, axe (peg axe 2))
+        %=  relative
+          i.p.mod  i.t.p.mod
+          t.p.mod  t.t.p.mod
+          axe      (peg axe 3)
+        ==
+      ::
+      ::  exclude, $<
+      ::
+          [%bcgl *]
+        :+  %tsls
+          relative:clear(mod q.mod)
+        :+  %wtgl
+          [%wtts [%over ~[&/3] p.mod] ~[&/4]]
+        $/2
+      ::
+      ::  require, $>
+      ::
+          [%bcgr *]
+        :+  %tsls
+          relative:clear(mod q.mod)
+        :+  %wtgr
+          [%wtts [%over ~[&/3] p.mod] ~[&/4]]
+        $/2
+      ::
+      ::  function
+      ::
+          [%bchp *]
+        %-  decorate
+        =/  fun  (function:clear p.mod q.mod)
+        ?^  def
+          [%ktls fun u.def]
+        fun
+      ::
+      ::  bridge, $^
+      ::
+          [%bckt *]
+        %-  decorate
+        :^    %wtcl
+            [%dtwt fetch(axe (peg axe 2))]
+          relative:clear(mod p.mod)
+        relative:clear(mod q.mod)
+      ::
+      ::  synthesis, $;
+      ::
+          [%bcmc *]
+        (decorate [%cncl (home p.mod) fetch ~])
+      ::
+      ::  default
+      ::
+          [%bcsg *]
+        relative(mod q.mod, def `[%kthp q.mod p.mod])
+      ::
+      ::  choice, $?
+      ::
+          [%bcwt *]
+        (decorate (choice i.p.mod t.p.mod))
+      ::
+      ::  name, $=
+      ::
+          [%bcts *]
+        [%ktts p.mod relative(mod q.mod)]
+      ::
+      ::  branch, $@
+      ::
+          [%bcpt *]
+        %-  decorate
+        :^    %wtcl
+            [%dtwt fetch]
+          relative:clear(mod q.mod)
+        relative:clear(mod p.mod)
+      ::
+        [%bcls *]  [%note [%know p.mod] relative(mod q.mod)]
+        [%bcdt *]  (decorate (home (interface %gold p.mod q.mod)))
+        [%bcfs *]  (decorate (home (interface %iron p.mod q.mod)))
+        [%bczp *]  (decorate (home (interface %lead p.mod q.mod)))
+        [%bctc *]  (decorate (home (interface %zinc p.mod q.mod)))
+      ==
+    --
+  --
+::
+++  ap                                                  ::  hoon engine
+  ~%    %ap
+      +>+
+    ==
+      %open  open
+      %rake  rake
+    ==
+  |_  gen=hoon
+  ::
+  ++  grip
+    |=  =skin
+    =|  rel=wing
+    |-  ^-  hoon
+    ?-    skin
+        @
+      [%tsgl [%tune skin] gen]
+        [%base *]
+      ?:  ?=(%noun base.skin)
+        gen
+      [%kthp skin gen]
+    ::
+        [%cell *]
+      =+  haf=~(half ap gen)
+      ?^  haf
+        :-  $(skin skin.skin, gen p.u.haf)
+        $(skin ^skin.skin, gen q.u.haf)
+      :+  %tsls
+        gen
+      :-  $(skin skin.skin, gen [%$ 4])
+      $(skin ^skin.skin, gen [%$ 5])
+    ::
+        [%dbug *]
+      [%dbug spot.skin $(skin skin.skin)]
+    ::
+        [%leaf *]
+      [%kthp skin gen]
+    ::
+        [%help *]
+      [%note [%help help.skin] $(skin skin.skin)]
+    ::
+        [%name *]
+      [%tsgl [%tune term.skin] $(skin skin.skin)]
+    ::
+        [%over *]
+      $(skin skin.skin, rel (weld wing.skin rel))
+    ::
+        [%spec *]
+      :+  %kthp
+        ?~(rel spec.skin [%over rel spec.skin])
+      $(skin skin.skin)
+    ::
+        [%wash *]
+      :+  %tsgl
+        :-  %wing
+        |-  ^-  wing
+        ?:  =(0 depth.skin)  ~
+        [[%| 0 ~] $(depth.skin (dec depth.skin))]
+      gen
+    ==
+  ::
+  ++  name
+    |-  ^-  (unit term)
+    ?+  gen  ~
+      [%wing *]  ?~  p.gen  ~
+                 ?^  i.p.gen
+                   ?:(?=(%& -.i.p.gen) ~ q.i.p.gen)
+                 `i.p.gen
+      [%limb *]  `p.gen
+      [%dbug *]  $(gen ~(open ap gen))
+      [%tsgl *]  $(gen ~(open ap gen))
+      [%tsgr *]  $(gen q.gen)
+    ==
+  ::
+  ++  feck
+    |-  ^-  (unit term)
+    ?-  gen
+      [%sand %tas @]  [~ q.gen]
+      [%dbug *]       $(gen q.gen)
+      *               ~
+    ==
+  ::
+  ::  not used at present; see comment at %csng in ++open
+::::
+::++  hail
+::  |=  axe=axis
+::  =|  air=(list (pair wing hoon))
+::  |-  ^+  air
+::  =+  hav=half
+::  ?~  hav  [[[[%| 0 ~] [%& axe] ~] gen] air]
+::  $(gen p.u.hav, axe (peg axe 2), air $(gen q.u.hav, axe (peg axe 3)))
+::
+  ++  half
+    |-  ^-  (unit (pair hoon hoon))
+    ?+  gen  ~
+      [^ *]       `[p.gen q.gen]
+      [%dbug *]   $(gen q.gen)
+      [%clcb *]   `[q.gen p.gen]
+      [%clhp *]   `[p.gen q.gen]
+      [%clkt *]   `[p.gen %clls q.gen r.gen s.gen]
+      [%clsg *]   ?~(p.gen ~ `[i.p.gen %clsg t.p.gen])
+      [%cltr *]   ?~  p.gen  ~
+                  ?~(t.p.gen $(gen i.p.gen) `[i.p.gen %cltr t.p.gen])
+    ==
+::::
+  ::  +flay: hoon to skin
+  ::
+  ++  flay
+    |-  ^-  (unit skin)
+    ?+    gen
+      =+(open ?:(=(- gen) ~ $(gen -)))
+    ::
+        [^ *]
+      =+  [$(gen p.gen) $(gen q.gen)]
+      ?~(-< ~ ?~(-> ~ `[%cell -<+ ->+]))
+    ::
+        [%base *]
+      `gen
+    ::
+        [%rock *]
+      ?@(q.gen `[%leaf p.gen q.gen] ~)
+    ::
+        [%cnts [@ ~] ~]
+      `i.p.gen
+    ::
+        [%tsgr *]
+      %+  biff  reek(gen p.gen)
+      |=  =wing
+      (bind ^$(gen q.gen) |=(=skin [%over wing skin]))
+    ::
+        [%limb @]
+      `p.gen
+    ::
+        [%note [%help *] *]
+      (bind $(gen q.gen) |=(=skin [%help p.p.gen skin]))
+    ::
+        [%wing *]
+      ?:  ?=([@ ~] p.gen)
+        `i.p.gen
+      =/  depth  0
+      |-  ^-  (unit skin)
+      ?~  p.gen  `[%wash depth]
+      ?.  =([%| 0 ~] i.p.gen)  ~
+      $(p.gen t.p.gen)
+    ::
+        [%kttr *]
+      `[%spec p.gen %base %noun]
+    ::
+        [%ktts *]
+      %+  biff  $(gen q.gen)
+      |=  =skin
+      ?@  p.gen  `[%name p.gen skin]
+      ?.  ?=([%name @ [%base %noun]] p.gen)  ~
+      `[%name term.p.gen skin]
+    ==
+  ::
+  ::  +open: desugarer
+  ++  open
+    ^-  hoon
+    ?-    gen
+        [~ *]     [%cnts [[%& p.gen] ~] ~]
+    ::
+        [%base *]  ~(factory ax `spec`gen)
+        [%bust *]  ~(example ax %base p.gen)
+        [%ktcl *]  ~(factory ax p.gen)
+        [%dbug *]   q.gen
+        [%eror *]  ~_((crip p.gen) !!)
+    ::
+        [%knit *]                                       ::
+      :+  %tsgr  [%ktts %v %$ 1]                        ::  =>  v=.
+      :-  %brhp                                         ::  |-
+      :+  %ktls                                         ::  ^+
+        :-  %brhp                                       ::  |-
+        :^    %wtcl                                     ::  ?:
+            [%bust %flag]                               ::  ?
+          [%bust %null]                                 ::  ~
+        :-  [%ktts %i [%sand 'tD' *@]]                  ::  :-  i=~~
+        [%ktts %t [%limb %$]]                           ::  t=$
+      |-  ^-  hoon                                      ::
+      ?~  p.gen                                         ::
+        [%bust %null]                                   ::  ~
+      =+  res=$(p.gen t.p.gen)                          ::
+      ^-  hoon                                          ::
+      ?@  i.p.gen                                       ::
+        [[%sand 'tD' i.p.gen] res]                      ::  [~~{i.p.gen} {res}]
+      :+  %tsls                                         ::
+        :-  :+  %ktts                                   ::  ^=
+              %a                                        ::  a
+            :+  %ktls                                   ::  ^+
+              [%limb %$]                                ::  $
+            [%tsgr [%limb %v] p.i.p.gen]                ::  =>(v {p.i.p.gen})
+        [%ktts %b res]                                  ::  b=[res]
+      ^-  hoon                                          ::
+      :-  %brhp                                         ::  |-
+      :^    %wtpt                                       ::  ?@
+          [%a ~]                                        ::  a
+        [%limb %b]                                      ::  b
+      :-  [%tsgl [%$ 2] [%limb %a]]                     ::  :-  -.a
+      :+  %cnts                                         ::  %=
+        [%$ ~]                                          ::  $
+      [[[%a ~] [%tsgl [%$ 3] [%limb %a]]] ~]            ::  a  +.a
+    ::
+        [%leaf *]  ~(factory ax `spec`gen)
+        [%limb *]  [%cnts [p.gen ~] ~]
+        [%tell *]  [%cncl [%limb %noah] [%zpgr [%cltr p.gen]] ~]
+        [%wing *]  [%cnts p.gen ~]
+        [%yell *]  [%cncl [%limb %cain] [%zpgr [%cltr p.gen]] ~]
+        [%note *]  q.gen
+    ::
+    ::TODO: does %gist need to be special cased here?
+        [%brbc *]  =-  ?~  -  !!
+                       :+  %brtr
+                         [%bccl -]
+                       |-
+                       ?.  ?=([%gist *] body.gen)
+                         [%ktcl body.gen]
+                       [%note p.body.gen $(body.gen q.body.gen)]
+                   %+  turn  `(list term)`sample.gen
+                   |=  =term
+                   ^-  spec
+                   =/  tar  [%base %noun]
+                   [%bcts term [%bcsg tar [%bchp tar tar]]]
+        [%brcb *]  :+  %tsls  [%kttr p.gen]
+                   :+  %brcn  ~
+                   %-  ~(run by r.gen)
+                   |=  =tome
+                   :-  p.tome
+                   %-  ~(run by q.tome)
+                   |=  =hoon
+                   ?~  q.gen  hoon
+                   [%tstr [p.i.q.gen ~] q.i.q.gen $(q.gen t.q.gen)]
+        [%brcl *]  [%tsls p.gen [%brdt q.gen]]
+        [%brdt *]  :+  %brcn  ~
+                   =-  [[%$ ~ -] ~ ~]
+                   (~(put by *(map term hoon)) %$ p.gen)
+        [%brkt *]  :+  %tsgl  [%limb %$]
+                   :+  %brcn  ~
+                   =+  zil=(~(get by q.gen) %$)
+                   ?~  zil
+                     %+  ~(put by q.gen)  %$
+                     [*what [[%$ p.gen] ~ ~]]
+                   %+  ~(put by q.gen)  %$
+                   [p.u.zil (~(put by q.u.zil) %$ p.gen)]
+        [%brhp *]  [%tsgl [%limb %$] [%brdt p.gen]]
+        [%brsg *]  [%ktbr [%brts p.gen q.gen]]
+        [%brtr *]  :+  %tsls  [%kttr p.gen]
+                   :+  %brpt  ~
+                   =-  [[%$ ~ -] ~ ~]
+                   (~(put by *(map term hoon)) %$ q.gen)
+        [%brts *]  :+  %brcb  p.gen
+                   =-  [~ [[%$ ~ -] ~ ~]]
+                   (~(put by *(map term hoon)) %$ q.gen)
+        [%brwt *]  [%ktwt %brdt p.gen]
+    ::
+        [%clkt *]  [p.gen q.gen r.gen s.gen]
+        [%clls *]  [p.gen q.gen r.gen]
+        [%clcb *]  [q.gen p.gen]
+        [%clhp *]  [p.gen q.gen]
+        [%clsg *]
+      |-  ^-  hoon
+      ?~  p.gen
+        [%rock %n ~]
+      [i.p.gen $(p.gen t.p.gen)]
+    ::
+        [%cltr *]
+      |-  ^-  hoon
+      ?~  p.gen
+        [%zpzp ~]
+      ?~  t.p.gen
+        i.p.gen
+      [i.p.gen $(p.gen t.p.gen)]
+    ::
+        [%kttr *]  [%ktsg ~(example ax p.gen)]
+        [%cncb *]  [%ktls [%wing p.gen] %cnts p.gen q.gen]
+        [%cndt *]  [%cncl q.gen [p.gen ~]]
+        [%cnkt *]  [%cncl p.gen q.gen r.gen s.gen ~]
+        [%cnls *]  [%cncl p.gen q.gen r.gen ~]
+        [%cnhp *]  [%cncl p.gen q.gen ~]
+        ::  this probably should work, but doesn't
+        ::
+        ::  [%cncl *]  [%cntr [%$ ~] p.gen [[[[%& 6] ~] [%cltr q.gen]] ~]]
+        [%cncl *]  [%cnsg [%$ ~] p.gen q.gen]
+        [%cnsg *]
+      ::  this complex matching system is a leftover from the old
+      ::  "electroplating" era.  %cnsg should be removed and replaced
+      ::  with the commented-out %cncl above.  but something is broken.
+      ::
+      :^  %cntr  p.gen  q.gen
+      =+  axe=6
+      |-  ^-  (list [wing hoon])
+      ?~  r.gen  ~
+      ?~  t.r.gen  [[[[%| 0 ~] [%& axe] ~] i.r.gen] ~]
+      :-  [[[%| 0 ~] [%& (peg axe 2)] ~] i.r.gen]
+      $(axe (peg axe 3), r.gen t.r.gen)
+    ::
+        [%cntr *]
+      ?:  =(~ r.gen)
+        [%tsgr q.gen [%wing p.gen]]
+      :+  %tsls
+        q.gen
+      :+  %cnts
+        (weld p.gen `wing`[[%& 2] ~])
+      (turn r.gen |=([p=wing q=hoon] [p [%tsgr [%$ 3] q]]))
+    ::
+        [%ktdt *]  [%ktls [%cncl p.gen q.gen ~] q.gen]
+        [%kthp *]  [%ktls ~(example ax p.gen) q.gen]
+        [%ktts *]  (grip(gen q.gen) p.gen)
+    ::
+        [%sgbr *]
+      :+  %sggr
+        :-  %mean
+        =+  fek=~(feck ap p.gen)
+        ?^  fek  [%rock %tas u.fek]
+        [%brdt [%cncl [%limb %cain] [%zpgr [%tsgr [%$ 3] p.gen]] ~]]
+      q.gen
+    ::
+        [%sgcb *]  [%sggr [%mean [%brdt p.gen]] q.gen]
+        [%sgcn *]
+      :+  %sggl
+        :-  %fast
+        :-  %clls
+        :+  [%rock %$ p.gen]
+          [%zpts q.gen]
+        :-  %clsg
+        =+  nob=`(list hoon)`~
+        |-  ^-  (list hoon)
+        ?~  r.gen
+          nob
+        [[[%rock %$ p.i.r.gen] [%zpts q.i.r.gen]] $(r.gen t.r.gen)]
+      s.gen
+    ::
+        [%sgfs *]  [%sgcn p.gen [%$ 7] ~ q.gen]
+        [%sggl *]  [%tsgl [%sggr p.gen [%$ 1]] q.gen]
+        [%sgbc *]  [%sggr [%live [%rock %$ p.gen]] q.gen]
+        [%sgls *]  [%sggr [%memo %rock %$ p.gen] q.gen]
+        [%sgpm *]
+      :+  %sggr
+        [%slog [%sand %$ p.gen] [%cncl [%limb %cain] [%zpgr q.gen] ~]]
+      r.gen
+    ::
+        [%sgts *]  [%sggr [%germ p.gen] q.gen]
+        [%sgwt *]
+      :+  %tsls  [%wtdt q.gen [%bust %null] [[%bust %null] r.gen]]
+      :^  %wtsg  [%& 2]~
+        [%tsgr [%$ 3] s.gen]
+      [%sgpm p.gen [%$ 5] [%tsgr [%$ 3] s.gen]]
+    ::
+        [%mcts *]
+      |-
+      ?~  p.gen  [%bust %null]
+      ?-  -.i.p.gen
+        ^      [[%xray i.p.gen] $(p.gen t.p.gen)]
+        %manx  [p.i.p.gen $(p.gen t.p.gen)]
+        %tape  [[%mcfs p.i.p.gen] $(p.gen t.p.gen)]
+        %call  [%cncl p.i.p.gen [$(p.gen t.p.gen)]~]
+        %marl  =-  [%cndt [p.i.p.gen $(p.gen t.p.gen)] -]
+               ^-  hoon
+               :+  %tsbr  [%base %cell]
+               :+  %brpt  ~
+               ^-  (map term tome)
+               =-  [[%$ ~ -] ~ ~]
+               ^-  (map term hoon)
+               :_  [~ ~]
+               =+  sug=[[%& 12] ~]
+               :-  %$
+               :^  %wtsg  sug
+                 [%cnts sug [[[[%& 1] ~] [%$ 13]] ~]]
+               [%cnts sug [[[[%& 3] ~] [%cnts [%$ ~] [[sug [%$ 25]] ~]]] ~]]
+      ==
+    ::
+        [%mccl *]
+      ?-    q.gen
+          ~      [%zpzp ~]
+          [* ~]  i.q.gen
+          ^
+        :+  %tsls
+          p.gen
+        =+  yex=`(list hoon)`q.gen
+        |-  ^-  hoon
+        ?-  yex
+          [* ~]  [%tsgr [%$ 3] i.yex]
+          [* ^]   [%cncl [%$ 2] [%tsgr [%$ 3] i.yex] $(yex t.yex) ~]
+          ~      !!
+        ==
+      ==
+    ::
+        [%mcfs *]  =+(zoy=[%rock %ta %$] [%clsg [zoy [%clsg [zoy p.gen] ~]] ~])
+        [%mcgl *]  [%cnls [%cnhp q ktcl+p] r [%brts p [%tsgr $+3 s]]]:gen
+    ::
+        [%mcsg *]                                       ::                  ;~
+      |-  ^-  hoon
+      ?-  q.gen
+          ~      ~_(leaf+"open-mcsg" !!)
+          ^
+        :+  %tsgr  [%ktts %v %$ 1]                      ::  =>  v=.
+        |-  ^-  hoon                                    ::
+        ?:  ?=(~ t.q.gen)                               ::
+          [%tsgr [%limb %v] i.q.gen]                    ::  =>(v {i.q.gen})
+        :+  %tsls  [%ktts %a $(q.gen t.q.gen)]          ::  =+  ^=  a
+        :+  %tsls                                       ::    {$(q.gen t.q.gen)}
+          [%ktts %b [%tsgr [%limb %v] i.q.gen]]         ::  =+  ^=  b
+        :+  %tsls                                       ::    =>(v {i.q.gen})
+          :+  %ktts  %c                                 ::  =+  c=,.+6.b
+          :+  %tsgl                                     ::
+            [%wing [%| 0 ~] [%& 6] ~]                   ::
+          [%limb %b]                                    ::
+        :-  %brdt                                       ::  |.
+        :^    %cnls                                     ::  %+
+            [%tsgr [%limb %v] p.gen]                    ::      =>(v {p.gen})
+          [%cncl [%limb %b] [%limb %c] ~]               ::    (b c)
+        :+  %cnts  [%a ~]                               ::  a(,.+6 c)
+        [[[[%| 0 ~] [%& 6] ~] [%limb %c]] ~]            ::
+      ==                                                ::
+    ::
+        [%mcmc *]                                       ::                  ;;
+      [%cnhp ~(factory ax p.gen) q.gen]
+    ::
+        [%tsbr *]
+      [%tsls ~(example ax p.gen) q.gen]
+    ::
+        [%tstr *]
+      :+  %tsgl
+        r.gen
+      [%tune [[p.p.gen ~ ?~(q.p.gen q.gen [%kthp u.q.p.gen q.gen])] ~ ~] ~]
+    ::
+        [%tscl *]
+      [%tsgr [%cncb [[%& 1] ~] p.gen] q.gen]
+    ::
+        [%tsfs *]
+      [%tsls [%ktts p.gen q.gen] r.gen]
+    ::
+        [%tsmc *]  [%tsfs p.gen r.gen q.gen]
+        [%tsdt *]
+      [%tsgr [%cncb [[%& 1] ~] [[p.gen q.gen] ~]] r.gen]
+        [%tswt *]                                       ::                  =?
+      [%tsdt p.gen [%wtcl q.gen r.gen [%wing p.gen]] s.gen]
+    ::
+        [%tskt *]                                       ::                  =^
+      =+  wuy=(weld q.gen `wing`[%v ~])                 ::
+      :+  %tsgr  [%ktts %v %$ 1]                        ::  =>  v=.
+      :+  %tsls  [%ktts %a %tsgr [%limb %v] r.gen]      ::  =+  a==>(v \r.gen)
+      :^  %tsdt  wuy  [%tsgl [%$ 3] [%limb %a]]
+      :+  %tsgr  :-  :+  %ktts  [%over [%v ~] p.gen]
+                     [%tsgl [%$ 2] [%limb %a]]
+                 [%limb %v]
+      s.gen
+    ::
+        [%tsgl *]  [%tsgr q.gen p.gen]
+        [%tsls *]  [%tsgr [p.gen [%$ 1]] q.gen]
+        [%tshp *]  [%tsls q.gen p.gen]
+        [%tssg *]
+      |-  ^-  hoon
+      ?~  p.gen    [%$ 1]
+      ?~  t.p.gen  i.p.gen
+      [%tsgr i.p.gen $(p.gen t.p.gen)]
+    ::
+        [%wtbr *]
+      |-
+      ?~(p.gen [%rock %f 1] [%wtcl i.p.gen [%rock %f 0] $(p.gen t.p.gen)])
+    ::
+        [%wtdt *]   [%wtcl p.gen r.gen q.gen]
+        [%wtgl *]   [%wtcl p.gen [%zpzp ~] q.gen]
+        [%wtgr *]   [%wtcl p.gen q.gen [%zpzp ~]]
+        [%wtkt *]   [%wtcl [%wtts [%base %atom %$] p.gen] r.gen q.gen]
+    ::
+        [%wthp *]
+      |-
+      ?~  q.gen
+        [%lost [%wing p.gen]]
+      :^    %wtcl
+          [%wtts p.i.q.gen p.gen]
+        q.i.q.gen
+      $(q.gen t.q.gen)
+    ::
+        [%wtls *]
+      [%wthp p.gen (weld r.gen `_r.gen`[[[%base %noun] q.gen] ~])]
+    ::
+        [%wtpm *]
+      |-
+      ?~(p.gen [%rock %f 0] [%wtcl i.p.gen $(p.gen t.p.gen) [%rock %f 1]])
+    ::
+        [%xray *]
+      |^  :-  [(open-mane n.g.p.gen) %clsg (turn a.g.p.gen open-mart)]
+          [%mcts c.p.gen]
+      ::
+      ++  open-mane
+        |=  a=mane:hoot
+        ?@(a [%rock %tas a] [[%rock %tas -.a] [%rock %tas +.a]])
+      ::
+      ++  open-mart
+        |=  [n=mane:hoot v=(list beer:hoot)]
+        [(open-mane n) %knit v]
+      --
+    ::
+        [%wtpt *]   [%wtcl [%wtts [%base %atom %$] p.gen] q.gen r.gen]
+        [%wtsg *]   [%wtcl [%wtts [%base %null] p.gen] q.gen r.gen]
+        [%wtts *]   [%fits ~(example ax p.gen) q.gen]
+        [%wtzp *]   [%wtcl p.gen [%rock %f 1] [%rock %f 0]]
+        [%zpgr *]
+      [%cncl [%limb %onan] [%zpmc [%kttr [%bcmc %limb %abel]] p.gen] ~]
+    ::
+        [%zpwt *]
+      ?:  ?:  ?=(@ p.gen)
+            (lte hoon-version p.gen)
+          &((lte hoon-version p.p.gen) (gte hoon-version q.p.gen))
+        q.gen
+      ~_(leaf+"hoon-version" !!)
+    ::
+        *           gen
+    ==
+  ::
+  ++  rake  ~>(%mean.'rake-hoon' (need reek))
+  ++  reek
+    ^-  (unit wing)
+    ?+  gen  ~
+      [~ *]        `[[%& p.gen] ~]
+      [%limb *]     `[p.gen ~]
+      [%wing *]     `p.gen
+      [%cnts * ~]  `p.gen
+      [%dbug *]     reek(gen q.gen)
+    ==
+  ++  rusk
+    ^-  term
+    =+  wig=rake
+    ?.  ?=([@ ~] wig)
+      ~>(%mean.'rusk-hoon' !!)
+    i.wig
+  --
+::
+::    5c: compiler backend and prettyprinter
++|  %compiler-backend-and-prettyprinter
+::
+++  ut
+  ~%    %ut
+      +>+
+    ==
+      %ar     ar
+      %fan    fan
+      %rib    rib
+      %vet    vet
+      %blow   blow
+      %burp   burp
+      %busk   busk
+      %buss   buss
+      %crop   crop
+      %duck   duck
+      %dune   dune
+      %dunk   dunk
+      %epla   epla
+      %emin   emin
+      %emul   emul
+      %feel   feel
+      %felt   felt
+      %fine   fine
+      %fire   fire
+      %fish   fish
+      %fond   fond
+      %fund   fund
+      %funk   funk
+      %fuse   fuse
+      %gain   gain
+      %lose   lose
+      %mile   mile
+      %mine   mine
+      %mint   mint
+      %moot   moot
+      %mull   mull
+      %nest   nest
+      %peel   peel
+      %play   play
+      %peek   peek
+      %repo   repo
+      %rest   rest
+      %sink   sink
+      %tack   tack
+      %toss   toss
+      %wrap   wrap
+    ==
+  =+  :*  fan=*(set [type hoon])
+          rib=*(set [type type hoon])
+          vet=`?`&
+      ==
+  =+  sut=`type`%noun
+  |%
+  ++  clip
+    |=  ref=type
+    ?>  ?|(!vet (nest(sut ref) & sut))
+    ref
+  ::
+  ::  +ar: texture engine
+  ::
+  ++  ar  !:
+    ~%    %ar
+        +>
+      ==
+        %fish  fish
+        %gain  gain
+        %lose  lose
+      ==
+    |_  [ref=type =skin]
+    ::
+    ::  +fish: make a $nock that tests a .ref at .axis for .skin
+    ::
+    ++  fish
+      |=  =axis
+      ^-  nock
+      ?@  skin  $(skin spec+[[%like [skin]~ ~] [%base %noun]])
+      ?-    -.skin
+      ::
+          %base
+        ?-  base.skin
+          %cell      $(skin [%cell [%base %noun] [%base %noun]])
+          %flag      ?:  (~(nest ut bool) | ref)
+                       [%1 &]
+                     %+  flan
+                       $(skin [%base %atom %$])
+                     %+  flor
+                       [%5 [%0 axis] [%1 &]]
+                     [%5 [%0 axis] [%1 |]]
+          %noun      [%1 &]
+          %null      $(skin [%leaf %n ~])
+          %void      [%1 |]
+          [%atom *]  ?:  (~(nest ut [%atom %$ ~]) | ref)
+                       [%1 &]
+                     ?:  (~(nest ut [%cell %noun %noun]) | ref)
+                       [%1 |]
+                     (flip [%3 %0 axis])
+        ==
+      ::
+          %cell
+        ?:  (~(nest ut [%atom %$ ~]) | ref)  [%1 |]
+        %+  flan
+          ?:  (~(nest ut [%cell %noun %noun]) | ref)
+            [%1 &]
+          [%3 %0 axis]
+        %+  flan
+          $(ref (peek(sut ref) %free 2), axis (peg axis 2), skin skin.skin)
+        $(ref (peek(sut ref) %free 3), axis (peg axis 3), skin ^skin.skin)
+      ::
+          %leaf
+        ?:  (~(nest ut [%atom %$ `atom.skin]) | ref)
+          [%1 &]
+        [%5 [%1 atom.skin] [%0 axis]]
+      ::
+          %dbug  $(skin skin.skin)
+          %help  $(skin skin.skin)
+          %name  $(skin skin.skin)
+          %over  ::NOTE  might need to guard with +feel, crashing is too strict
+                 =+  ~|  %oops-guess-you-needed-feel-after-all
+                     fid=(fend %read wing.skin)
+                 $(sut p.fid, axis (peg axis q.fid), skin skin.skin)
+          %spec  =/  hit  (~(play ut sut) ~(example ax spec.skin))
+                 ?>  (~(nest ut hit) & ref)
+                 $(skin skin.skin)
+          %wash  [%1 &]
+      ==
+    ::
+    ::  +gain: make a $type by restricting .ref to .skin
+    ::
+    ++  gain
+      |-  ^-  type
+      ?@  skin  $(skin spec+[[%like [skin]~ ~] [%base %noun]])
+      ?-    -.skin
+      ::
+          %base
+        ?-    base.skin
+            %cell      $(skin [%cell [%base %noun] [%base %noun]])
+            %flag      (fork $(skin [%leaf %f &]) $(skin [%leaf %f |]) ~)
+            %null      $(skin [%leaf %n ~])
+            %void      %void
+            %noun      ?:((~(nest ut %void) | ref) %void ref)
+            [%atom *]
+          =|  gil=(set type)
+          |-  ^-  type
+          ?-    ref
+            %void      %void
+            %noun      [%atom p.base.skin ~]
+            [%atom *]  ?.  (fitz p.base.skin p.ref)
+                          ~>(%mean.'atom-mismatch' !!)
+                       :+  %atom
+                         (max p.base.skin p.ref)
+                       q.ref
+            [%cell *]  %void
+            [%core *]  %void
+            [%face *]  $(ref q.ref)
+            [%fork *]  (fork (turn ~(tap in p.ref) |=(=type ^$(ref type))))
+            [%hint *]  (hint p.ref $(ref q.ref))
+            [%hold *]  ?:  (~(has in gil) ref)  %void
+                       $(gil (~(put in gil) ref), ref repo(sut ref))
+          ==
+        ==
+      ::
+          %cell
+        =|  gil=(set type)
+        |-  ^-  type
+        ?-    ref
+            %void      %void
+            %noun      =+  ^$(skin skin.skin)
+                       ?:  =(%void -)  %void
+                       (cell - ^$(skin ^skin.skin))
+            [%atom *]  %void
+            [%cell *]  =+  ^$(skin skin.skin, ref p.ref)
+                       ?:  =(%void -)  %void
+                       (cell - ^$(skin ^skin.skin, ref q.ref))
+            [%core *]  =+  ^$(skin skin.skin, ref p.ref)
+                       ?:  =(%void -)  %void
+                       ?.  =(%noun ^skin.skin)
+                         (cell - ^$(skin ^skin.skin, ref %noun))
+                       [%core - q.ref]
+            [%face *]  $(ref q.ref)
+            [%fork *]  (fork (turn ~(tap in p.ref) |=(=type ^$(ref type))))
+            [%hint *]  (hint p.ref $(ref q.ref))
+            [%hold *]  ?:  (~(has in gil) ref)  %void
+                       $(gil (~(put in gil) ref), ref repo(sut ref))
+        ==
+      ::
+          %leaf
+        =|  gil=(set type)
+        |-  ^-  type
+        ?-  ref
+          %void      %void
+          %noun      [%atom aura.skin `atom.skin]
+          [%atom *]  ?:  &(?=(^ q.ref) !=(atom.skin u.q.ref))
+                       %void
+                     ?.  (fitz aura.skin p.ref)
+                        ~>(%mean.'atom-mismatch' !!)
+                     :+  %atom
+                       (max aura.skin p.ref)
+                     `atom.skin
+          [%cell *]  %void
+          [%core *]  %void
+          [%face *]  $(ref q.ref)
+          [%fork *]  (fork (turn ~(tap in p.ref) |=(=type ^$(ref type))))
+          [%hint *]  (hint p.ref $(ref q.ref))
+          [%hold *]  ?:  (~(has in gil) ref)  %void
+                     $(gil (~(put in gil) ref), ref repo(sut ref))
+        ==
+      ::
+          %dbug  $(skin skin.skin)
+          %help  (hint [sut %help help.skin] $(skin skin.skin))
+          %name  (face term.skin $(skin skin.skin))
+          %over  $(skin skin.skin, sut (~(play ut sut) %wing wing.skin))
+          %spec  =/  hit  (~(play ut sut) ~(example ax spec.skin))
+                 ?>  (~(nest ut hit) & $(skin skin.skin))
+                 (~(fuse ut ref) hit)
+          %wash  =-  $(ref (~(play ut ref) -))
+                 :-  %wing
+                 |-  ^-  wing
+                 ?:  =(0 depth.skin)  ~
+                 [[%| 0 ~] $(depth.skin (dec depth.skin))]
+      ==
+    ::
+    ::  +lose: make a $type by restricting .ref to exclude .skin
+    ::
+    ++  lose
+      |-  ^-  type
+      ?@  skin  $(skin spec+[[%like [skin]~ ~] [%base %noun]])
+      ?-    -.skin
+      ::
+          %base
+        ?-    base.skin
+            %cell      $(skin [%cell [%base %noun] [%base %noun]])
+            %flag      $(ref $(skin [%leaf %f &]), skin [%leaf %f |])
+            %null      $(skin [%leaf %n ~])
+            %void      ref
+            %noun      %void
+            [%atom *]
+          =|  gil=(set type)
+          |-  ^-  type
+          ?-    ref
+            %void      %void
+            %noun      [%cell %noun %noun]
+            [%atom *]  %void
+            [%cell *]  ref
+            [%core *]  ref
+            [%face *]  (face p.ref $(ref q.ref))
+            [%fork *]  (fork (turn ~(tap in p.ref) |=(=type ^$(ref type))))
+            [%hint *]  (hint p.ref $(ref q.ref))
+            [%hold *]  ?:  (~(has in gil) ref)  %void
+                       $(gil (~(put in gil) ref), ref repo(sut ref))
+          ==
+        ==
+      ::
+          %cell
+        =|  gil=(set type)
+        |-  ^-  type
+        ?-    ref
+            %void      %void
+            %noun      ?.  =([%cell [%base %noun] [%base %noun]] skin)
+                         ref
+                       [%atom %$ ~]
+            [%atom *]  ref
+            [%cell *]  =/  lef  ^$(skin skin.skin, ref p.ref)
+                       =/  rig  ^$(skin ^skin.skin, ref q.ref)
+                       (fork (cell lef rig) (cell lef q.ref) (cell p.ref rig) ~)
+            [%core *]  =+  ^$(skin skin.skin, ref p.ref)
+                       ?:  =(%void -)  %void
+                       ?.  =(%noun ^skin.skin)
+                         (cell - ^$(skin ^skin.skin, ref %noun))
+                       [%core - q.ref]
+            [%face *]  $(ref q.ref)
+            [%fork *]  (fork (turn ~(tap in p.ref) |=(=type ^$(ref type))))
+            [%hint *]  (hint p.ref $(ref q.ref))
+            [%hold *]  ?:  (~(has in gil) ref)  %void
+                       $(gil (~(put in gil) ref), ref repo(sut ref))
+        ==
+      ::
+          %leaf
+        =|  gil=(set type)
+        |-  ^-  type
+        ?-  ref
+          %void      %void
+          %noun      %noun
+          [%atom *]  ?:  =(q.ref `atom.skin)
+                       %void
+                     ref
+          [%cell *]  ref
+          [%core *]  ref
+          [%face *]  (face p.ref $(ref q.ref))
+          [%fork *]  (fork (turn ~(tap in p.ref) |=(=type ^$(ref type))))
+          [%hint *]  (hint p.ref $(ref q.ref))
+          [%hold *]  ?:  (~(has in gil) ref)  %void
+                     $(gil (~(put in gil) ref), ref repo(sut ref))
+        ==
+      ::
+          %dbug  $(skin skin.skin)
+          %help  $(skin skin.skin)
+          %name  $(skin skin.skin)
+          %over  ::TODO  if we guard in +fish (+feel), we have to guard again here
+                 $(skin skin.skin, sut (~(play ut sut) %wing wing.skin))
+          %spec  =/  hit  (~(play ut sut) ~(example ax spec.skin))
+                 ?>  (~(nest ut hit) & $(skin skin.skin))
+                 (~(crop ut ref) hit)
+          %wash  ref
+      ==
+    --
+  ::
+  ++  blow
+    |=  [gol=type gen=hoon]
+    ^-  [type nock]
+    =+  pro=(mint gol gen)
+    =+  jon=(apex:musk bran q.pro)
+    ?:  |(?=(~ jon) ?=(%wait -.u.jon))
+      [p.pro q.pro]
+    [p.pro %1 p.u.jon]
+  ::
+  ++  bran
+    ~+
+    =+  gil=*(set type)
+    |-  ~+  ^-  seminoun:musk
+    ?-    sut
+      %noun      [full/[~ ~ ~] ~]
+      %void      [full/[~ ~ ~] ~]
+      [%atom *]  ?~(q.sut [full/[~ ~ ~] ~] [full/~ u.q.sut])
+      [%cell *]  (combine:musk $(sut p.sut) $(sut q.sut))
+      [%core *]  %+  combine:musk
+                   p.r.q.sut
+                 $(sut p.sut)
+      [%face *]  $(sut repo)
+      [%fork *]  [full/[~ ~ ~] ~]
+      [%hint *]  $(sut repo)
+      [%hold *]  ?:  (~(has in gil) sut)
+                   [full/[~ ~ ~] ~]
+                 $(sut repo, gil (~(put in gil) sut))
+    ==
+  ::
+  ++  burp
+    ::    expel undigested seminouns
+    ::
+    ^-  type
+    ~+
+    =-  ?.(=(sut -) - sut)
+    ?+  sut      sut
+      [%cell *]  [%cell burp(sut p.sut) burp(sut q.sut)]
+      [%core *]  :+  %core
+                   burp(sut p.sut)
+                 :*  p.q.sut
+                     burp(sut q.q.sut)
+                     :_  q.r.q.sut
+                     ?:  ?=([[%full ~] *] p.r.q.sut)
+                       p.r.q.sut
+                     [[%full ~ ~ ~] ~]
+                  ==
+      [%face *]  [%face p.sut burp(sut q.sut)]
+      [%fork *]  [%fork (~(run in p.sut) |=(type burp(sut +<)))]
+      [%hint *]  (hint [burp(sut p.p.sut) q.p.sut] burp(sut q.sut))
+      [%hold *]  [%hold burp(sut p.sut) q.sut]
+    ==
+  ::
+  ++  busk
+    ~/  %busk
+    |=  gen=hoon
+    ^-  type
+    [%face [~ [gen ~]] sut]
+  ::
+  ++  buss
+    ~/  %buss
+    |=  [cog=term gen=hoon]
+    ^-  type
+    [%face [[[cog ~ gen] ~ ~] ~] sut]
+  ::
+  ++  crop
+    ~/  %crop
+    |=  ref=type
+    =+  bix=*(set [type type])
+    =<  dext
+    |%
+    ++  dext
+      ^-  type
+      ~_  leaf+"crop"
+      ::  ~_  (dunk 'dext: sut')
+      ::  ~_  (dunk(sut ref) 'dext: ref')
+      ?:  |(=(sut ref) =(%noun ref))
+        %void
+      ?:  =(%void ref)
+        sut
+      ?-    sut
+          [%atom *]
+        ?+  ref      sint
+          [%atom *]  ?^  q.sut
+                       ?^(q.ref ?:(=(q.ref q.sut) %void sut) %void)
+                     ?^(q.ref sut %void)
+          [%cell *]  sut
+        ==
+      ::
+          [%cell *]
+        ?+  ref      sint
+          [%atom *]  sut
+          [%cell *]  ?.  (nest(sut p.ref) | p.sut)  sut
+                     (cell p.sut dext(sut q.sut, ref q.ref))
+        ==
+      ::
+          [%core *]  ?:(?=(?([%atom *] [%cell *]) ref) sut sint)
+          [%face *]  (face p.sut dext(sut q.sut))
+          [%fork *]  (fork (turn ~(tap in p.sut) |=(type dext(sut +<))))
+          [%hint *]  (hint p.sut dext(sut q.sut))
+          [%hold *]  ?<  (~(has in bix) [sut ref])
+                     dext(sut repo, bix (~(put in bix) [sut ref]))
+          %noun      dext(sut repo)
+          %void      %void
+      ==
+    ::
+    ++  sint
+      ^-  type
+      ?+    ref    !!
+        [%core *]  sut
+        [%face *]  dext(ref repo(sut ref))
+        [%fork *]  =+  yed=~(tap in p.ref)
+                   |-  ^-  type
+                   ?~  yed  sut
+                   $(yed t.yed, sut dext(ref i.yed))
+        [%hint *]  dext(ref repo(sut ref))
+        [%hold *]  dext(ref repo(sut ref))
+      ==
+    --
+  ::
+  ++  cool
+    |=  [pol=? hyp=wing ref=type]
+    ^-  type
+    =+  fid=(find %both hyp)
+    ?-  -.fid
+      %|  sut
+      %&  =<  q
+          %+  take  p.p.fid
+          |=(a=type ?:(pol (fuse(sut a) ref) (crop(sut a) ref)))
+    ==
+  ::
+  ++  duck  ^-(tank ~(duck us sut))
+  ++  dune  |.(duck)
+  ++  dunk
+    |=  paz=term  ^-  tank
+    :+  %palm
+      [['.' ~] ['-' ~] ~ ~]
+    [[%leaf (mesc (trip paz))] duck ~]
+  ::
+  ++  elbo
+    |=  [lop=palo rig=(list (pair wing hoon))]
+    ^-  type
+    ?:  ?=(%& -.q.lop)
+      |-  ^-  type
+      ?~  rig
+        p.q.lop
+      =+  zil=(play q.i.rig)
+      =+  dar=(tack(sut p.q.lop) p.i.rig zil)
+      %=  $
+        rig      t.rig
+        p.q.lop  q.dar
+      ==
+    =+  hag=~(tap in q.q.lop)
+    %-  fire
+    |-  ^+  hag
+    ?~  rig
+      hag
+    =+  zil=(play q.i.rig)
+    =+  dix=(toss p.i.rig zil hag)
+    %=  $
+      rig  t.rig
+      hag  q.dix
+    ==
+  ::
+  ++  ergo
+    |=  [lop=palo rig=(list (pair wing hoon))]
+    ^-  (pair type nock)
+    =+  axe=(tend p.lop)
+    =|  hej=(list (pair axis nock))
+    ?:  ?=(%& -.q.lop)
+      =-  [p.- (hike axe q.-)]
+      |-  ^-  (pair type (list (pair axis nock)))
+      ?~  rig
+        [p.q.lop hej]
+      =+  zil=(mint %noun q.i.rig)
+      =+  dar=(tack(sut p.q.lop) p.i.rig p.zil)
+      %=  $
+        rig      t.rig
+        p.q.lop  q.dar
+        hej      [[p.dar q.zil] hej]
+      ==
+    =+  hag=~(tap in q.q.lop)
+    =-  [(fire p.-) [%9 p.q.lop (hike axe q.-)]]
+    |-  ^-  (pair (list (pair type foot)) (list (pair axis nock)))
+    ?~  rig
+      [hag hej]
+    =+  zil=(mint %noun q.i.rig)
+    =+  dix=(toss p.i.rig p.zil hag)
+    %=  $
+      rig  t.rig
+      hag  q.dix
+      hej  [[p.dix q.zil] hej]
+    ==
+  ::
+  ++  endo
+    |=  [lop=(pair palo palo) dox=type rig=(list (pair wing hoon))]
+    ^-  (pair type type)
+    ?:  ?=(%& -.q.p.lop)
+      ?>  ?=(%& -.q.q.lop)
+      |-  ^-  (pair type type)
+      ?~  rig
+        [p.q.p.lop p.q.q.lop]
+      =+  zil=(mull %noun dox q.i.rig)
+      =+  ^=  dar
+          :-  p=(tack(sut p.q.p.lop) p.i.rig p.zil)
+              q=(tack(sut p.q.q.lop) p.i.rig q.zil)
+      ?>  =(p.p.dar p.q.dar)
+      %=  $
+        rig        t.rig
+        p.q.p.lop  q.p.dar
+        p.q.q.lop  q.q.dar
+      ==
+    ?>  ?=(%| -.q.q.lop)
+    ?>  =(p.q.p.lop p.q.q.lop)
+    =+  hag=[p=~(tap in q.q.p.lop) q=~(tap in q.q.q.lop)]
+    =-  [(fire p.-) (fire(vet |) q.-)]
+    |-  ^-  (pair (list (pair type foot)) (list (pair type foot)))
+    ?~  rig
+      hag
+    =+  zil=(mull %noun dox q.i.rig)
+    =+  ^=  dix
+        :-  p=(toss p.i.rig p.zil p.hag)
+            q=(toss p.i.rig q.zil q.hag)
+    ?>  =(p.p.dix p.q.dix)
+    %=  $
+      rig  t.rig
+      hag  [q.p.dix q.q.dix]
+    ==
+  ::
+  ++  et
+    |_  [hyp=wing rig=(list (pair wing hoon))]
+    ::
+    ++  play
+      ^-  type
+      =+  lug=(find %read hyp)
+      ?:  ?=(%| -.lug)  ~>(%mean.'hoon' ?>(?=(~ rig) p.p.lug))
+      (elbo p.lug rig)
+    ::
+    ++  mint
+      |=  gol=type
+      =-  ?>(?|(!vet (nest(sut gol) & p.-)) -)
+      ^-  (pair type nock)
+      =+  lug=(find %read hyp)
+      ?:  ?=(%| -.lug)  ~>(%mean.'hoon' ?>(?=(~ rig) p.lug))
+      (ergo p.lug rig)
+    ::
+    ++  mull
+      |=  [gol=type dox=type]
+      =-  ?>(?|(!vet (nest(sut gol) & p.-)) -)
+      ^-  (pair type type)
+      =+  lug=[p=(find %read hyp) q=(find(sut dox) %read hyp)]
+      ?:  ?=(%| -.p.lug)
+        ?>   &(?=(%| -.q.lug) ?=(~ rig))
+        [p.p.p.lug p.p.q.lug]
+      ?>  ?=(%& -.q.lug)
+      (endo [p.p.lug p.q.lug] dox rig)
+    --
+  ::
+  ++  epla
+    ~/  %epla
+    |=  [hyp=wing rig=(list (pair wing hoon))]
+    ^-  type
+    ~(play et hyp rig)
+  ::
+  ++  emin
+    ~/  %emin
+    |=  [gol=type hyp=wing rig=(list (pair wing hoon))]
+    ^-  (pair type nock)
+    (~(mint et hyp rig) gol)
+  ::
+  ++  emul
+    ~/  %emul
+    |=  [gol=type dox=type hyp=wing rig=(list (pair wing hoon))]
+    ^-  (pair type type)
+    (~(mull et hyp rig) gol dox)
+  ::
+  ++  felt  !!
+  ::                                                    ::
+  ++  feel                                              ::  detect existence
+    |=  rot=(list wing)
+    ^-  ?
+    =.  rot  (flop rot)
+    |-  ^-  ?
+    ?~  rot  &
+    =/  yep  (fond %free i.rot)
+    ?~  yep  |
+    ?-    -.yep
+      %&  %=  $
+            rot  t.rot
+            sut  p:(fine %& p.yep)
+          ==
+      %|  ?-  -.p.yep
+            %&  |
+            %|  %=  $
+                  rot  t.rot
+                  sut  p:(fine %| p.p.yep)
+                ==
+    ==    ==
+  ::
+  ++  fond
+    ~/  %fond
+    |=  [way=vial hyp=wing]
+    =>  |%
+        ++  pony                                        ::  raw match
+                  $@  ~                                 ::  void
+                  %+  each                              ::  natural/abnormal
+                    palo                                ::  arm or leg
+                  %+  each                              ::  abnormal
+                    @ud                                 ::  unmatched
+                  (pair type nock)                      ::  synthetic
+        --
+    ^-  pony
+    ?~  hyp
+      [%& ~ %& sut]
+    =+  mor=$(hyp t.hyp)
+    ?-    -.mor
+        %|
+      ?-    -.p.mor
+          %&  mor
+          %|
+        =+  fex=(mint(sut p.p.p.mor) %noun [%wing i.hyp ~])
+        [%| %| p.fex (comb q.p.p.mor q.fex)]
+      ==
+    ::
+        %&
+      =.  sut
+        =*  lap  q.p.mor
+        ?-  -.lap
+          %&  p.lap
+          %|  (fork (turn ~(tap in q.lap) head))
+        ==
+      =>  :_  +
+          :*  axe=`axis`1
+              lon=p.p.mor
+              heg=?^(i.hyp i.hyp [%| p=0 q=(some i.hyp)])
+          ==
+      ?:  ?=(%& -.heg)
+        [%& [`p.heg lon] %& (peek way p.heg)]
+      =|  gil=(set type)
+      =<  $
+      |%  ++  here  ?:  =(0 p.heg)
+                      [%& [~ `axe lon] %& sut]
+                    [%| %& (dec p.heg)]
+          ++  lose  [%| %& p.heg]
+          ++  stop  ?~(q.heg here lose)
+          ++  twin  |=  [hax=pony yor=pony]
+                    ^-  pony
+                    ~_  leaf+"find-fork"
+                    ?:  =(hax yor)  hax
+                    ?~  hax  yor
+                    ?~  yor  hax
+                    ?:  ?=(%| -.hax)
+                      ?>  ?&  ?=(%| -.yor)
+                              ?=(%| -.p.hax)
+                              ?=(%| -.p.yor)
+                              =(q.p.p.hax q.p.p.yor)
+                          ==
+                      :+  %|
+                        %|
+                      [(fork p.p.p.hax p.p.p.yor ~) q.p.p.hax]
+                    ?>  ?=(%& -.yor)
+                    ?>  =(p.p.hax p.p.yor)
+                    ?:  &(?=(%& -.q.p.hax) ?=(%& -.q.p.yor))
+                      :+  %&  p.p.hax
+                      [%& (fork p.q.p.hax p.q.p.yor ~)]
+                    ?>  &(?=(%| -.q.p.hax) ?=(%| -.q.p.yor))
+                    ?>  =(p.q.p.hax p.q.p.yor)
+                    =+  wal=(~(uni in q.q.p.hax) q.q.p.yor)
+                    :+  %&  p.p.hax
+                    [%| p.q.p.hax wal]
+          ++  $
+            ^-  pony
+            ?-    sut
+                %void       ~
+                %noun       stop
+                [%atom *]   stop
+                [%cell *]
+              ?~  q.heg  here
+              =+  taf=$(axe (peg axe 2), sut p.sut)
+              ?~  taf  ~
+              ?:  |(?=(%& -.taf) ?=(%| -.p.taf))
+                taf
+              $(axe (peg axe 3), p.heg p.p.taf, sut q.sut)
+            ::
+                [%core *]
+              ?~  q.heg  here
+              =^  zem  p.heg
+                  =+  zem=(loot u.q.heg q.r.q.sut)
+                  ?~  zem  [~ p.heg]
+                  ?:(=(0 p.heg) [zem 0] [~ (dec p.heg)])
+              ?^  zem
+                :+  %&
+                  [`axe lon]
+                =/  zut  ^-  foot
+                         ?-  q.p.q.sut
+                           %wet  [%wet q.u.zem]
+                           %dry  [%dry q.u.zem]
+                         ==
+                [%| (peg 2 p.u.zem) [[sut zut] ~ ~]]
+              =+  pec=(peel way r.p.q.sut)
+              ?.  sam.pec  lose
+              ?:  con.pec  $(sut p.sut, axe (peg axe 3))
+              $(sut (peek(sut p.sut) way 2), axe (peg axe 6))
+            ::
+                [%hint *]
+              $(sut repo)
+            ::
+                [%face *]
+              ?:  ?=(~ q.heg)  here(sut q.sut)
+              =*  zot  p.sut
+              ?@  zot
+                ?:(=(u.q.heg zot) here(sut q.sut) lose)
+              =<  main
+              |%
+              ++  main
+                ^-  pony
+                =+  tyr=(~(get by p.zot) u.q.heg)
+                ?~  tyr
+                  next
+                ?~  u.tyr
+                  $(sut q.sut, lon [~ lon], p.heg +(p.heg))
+                ?.  =(0 p.heg)
+                  next(p.heg (dec p.heg))
+                =+  tor=(fund way u.u.tyr)
+                ?-  -.tor
+                  %&  [%& (weld p.p.tor `vein`[~ `axe lon]) q.p.tor]
+                  %|  [%| %| p.p.tor (comb [%0 axe] q.p.tor)]
+                ==
+              ++  next
+                |-  ^-  pony
+                ?~  q.zot
+                  ^$(sut q.sut, lon [~ lon])
+                =+  tiv=(mint(sut q.sut) %noun i.q.zot)
+                =+  fid=^$(sut p.tiv, lon ~, axe 1, gil ~)
+                ?~  fid  ~
+                ?:  ?=([%| %& *] fid)
+                  $(q.zot t.q.zot, p.heg p.p.fid)
+                =/  vat=(pair type nock)
+                    ?-    -.fid
+                      %&  (fine %& p.fid)
+                      %|  (fine %| p.p.fid)
+                    ==
+                [%| %| p.vat (comb (comb [%0 axe] q.tiv) q.vat)]
+              --
+            ::
+                [%fork *]
+              =+  wiz=(turn ~(tap in p.sut) |=(a=type ^$(sut a)))
+              ?~  wiz  ~
+              |-  ^-  pony
+              ?~  t.wiz  i.wiz
+              (twin i.wiz $(wiz t.wiz))
+            ::
+                [%hold *]
+              ?:  (~(has in gil) sut)
+                ~
+              $(gil (~(put in gil) sut), sut repo)
+            ==
+      --
+    ==
+  ::
+  ++  find
+    ~/  %find
+    |=  [way=vial hyp=wing]
+    ^-  port
+    ~_  (show [%c %find] %l hyp)
+    =-  ?@  -  !!
+        ?-    -<
+          %&  [%& p.-]
+          %|  ?-  -.p.-
+                %|  [%| p.p.-]
+                %&  !!
+        ==    ==
+    (fond way hyp)
+  ::
+  ++  fend
+    |=  [way=vial hyp=wing]
+    ^-  (pair type axis)
+    =+  fid=(find way hyp)
+    ~>  %mean.'fend-fragment'
+    ?>  &(?=(%& -.fid) ?=(%& -.q.p.fid))
+    [p.q.p.fid (tend p.p.fid)]
+  ::
+  ++  fund
+    ~/  %fund
+    |=  [way=vial gen=hoon]
+    ^-  port
+    =+  hup=~(reek ap gen)
+    ?~  hup
+      [%| (mint %noun gen)]
+    (find way u.hup)
+  ::
+  ++  fine
+    ~/  %fine
+    |=  tor=port
+    ^-  (pair type nock)
+    ?-  -.tor
+      %|  p.tor
+      %&  =+  axe=(tend p.p.tor)
+          ?-  -.q.p.tor
+            %&  [`type`p.q.p.tor %0 axe]
+            %|  [(fire ~(tap in q.q.p.tor)) [%9 p.q.p.tor %0 axe]]
+    ==    ==
+  ::
+  ++  fire
+    |=  hag=(list [p=type q=foot])
+    ^-  type
+    ?:  ?=([[* [%wet ~ %1]] ~] hag)
+      p.i.hag
+    %-  fork
+    %+  turn
+      hag.$
+    |=  [p=type q=foot]
+    ?.  ?=([%core *] p)
+      ~_  (dunk %fire-type)
+      ~_  leaf+"expected-fork-to-be-core"
+      ~_  (dunk(sut p) %fork-type)
+      ~>(%mean.'fire-core' !!)
+    :-  %hold
+    =+  dox=[%core q.q.p q.p(r.p %gold)]
+    ?:  ?=(%dry -.q)
+      ::  ~_  (dunk(sut [%cell q.q.p p.p]) %fire-dry)
+      ?>  ?|(!vet (nest(sut q.q.p) & p.p))
+      [dox p.q]
+    ?>  ?=(%wet -.q)
+    ::  ~_  (dunk(sut [%cell q.q.p p.p]) %fire-wet)
+    =.  p.p  (redo(sut p.p) q.q.p)
+    ?>  ?|  !vet
+            (~(has in rib) [sut dox p.q])
+            !=(** (mull(sut p, rib (~(put in rib) sut dox p.q)) %noun dox p.q))
+        ==
+    [p p.q]
+  ::
+  ++  fish
+    ~/  %fish
+    |=  axe=axis
+    =+  vot=*(set type)
+    |-  ^-  nock
+    ?-  sut
+        %void       [%1 1]
+        %noun       [%1 0]
+        [%atom *]   ?~  q.sut
+                      (flip [%3 %0 axe])
+                    [%5 [%1 u.q.sut] [%0 axe]]
+        [%cell *]
+      %+  flan
+        [%3 %0 axe]
+      (flan $(sut p.sut, axe (peg axe 2)) $(sut q.sut, axe (peg axe 3)))
+    ::
+        [%core *]   ~>(%mean.'fish-core' !!)
+        [%face *]   $(sut q.sut)
+        [%fork *]   =+  yed=~(tap in p.sut)
+                    |-  ^-  nock
+                    ?~(yed [%1 1] (flor ^$(sut i.yed) $(yed t.yed)))
+        [%hint *]   $(sut q.sut)
+        [%hold *]
+      ?:  (~(has in vot) sut)
+        ~>(%mean.'fish-loop' !!)
+      =>  %=(. vot (~(put in vot) sut))
+      $(sut repo)
+    ==
+  ::
+  ++  fuse
+    ~/  %fuse
+    |=  ref=type
+    =+  bix=*(set [type type])
+    |-  ^-  type
+    ?:  ?|(=(sut ref) =(%noun ref))
+      sut
+    ?-    sut
+        [%atom *]
+      ?-    ref
+          [%atom *]   =+  foc=?:((fitz p.ref p.sut) p.sut p.ref)
+                      ?^  q.sut
+                        ?^  q.ref
+                          ?:  =(q.sut q.ref)
+                            [%atom foc q.sut]
+                          %void
+                        [%atom foc q.sut]
+                      [%atom foc q.ref]
+          [%cell *]   %void
+          *           $(sut ref, ref sut)
+      ==
+        [%cell *]
+      ?-  ref
+        [%cell *]   (cell $(sut p.sut, ref p.ref) $(sut q.sut, ref q.ref))
+        *           $(sut ref, ref sut)
+      ==
+    ::
+        [%core *]  $(sut repo)
+        [%face *]  (face p.sut $(sut q.sut))
+        [%fork *]  (fork (turn ~(tap in p.sut) |=(type ^$(sut +<))))
+        [%hint *]  (hint p.sut $(sut q.sut))
+        [%hold *]
+      ?:  (~(has in bix) [sut ref])
+        ~>(%mean.'fuse-loop' !!)
+      $(sut repo, bix (~(put in bix) [sut ref]))
+    ::
+        %noun       ref
+        %void       %void
+    ==
+  ::
+  ++  gain
+    ~/  %gain
+    |=  gen=hoon  ^-  type
+    (chip & gen)
+  ::
+  ++  hemp
+    ::    generate formula from foot
+    ::
+    |=  [hud=poly gol=type gen=hoon]
+    ^-  nock
+    ~+
+    =+  %hemp-141
+    ?-  hud
+      %dry  q:(mint gol gen)
+      %wet  q:(mint(vet |) gol gen)
+    ==
+  ::
+  ++  laze
+    ::    produce lazy core generator for static execution
+    ::
+    |=  [nym=(unit term) hud=poly dom=(map term tome)]
+    ~+
+    ^-  seminoun
+    =+  %hemp-141
+    ::  tal: map from battery axis to foot
+    ::
+    =;  tal=(map @ud hoon)
+      ::  produce lazy battery
+      ::
+      :_  ~
+      :+  %lazy  1
+      |=  axe=@ud
+      ^-  (unit noun)
+      %+  bind  (~(get by tal) axe)
+      |=  gen=hoon
+      %.  [hud %noun gen]
+      hemp(sut (core sut [nym hud %gold] sut [[%lazy 1 ..^$] ~] dom))
+    ::
+    %-  ~(gas by *(map @ud hoon))
+    =|  yeb=(list (pair @ud hoon))
+    =+  axe=1
+    |^  ?-  dom
+          ~        yeb
+          [* ~ ~]  (chapter q.q.n.dom)
+          [* * ~]  %=  $
+                     dom  l.dom
+                     axe  (peg axe 3)
+                     yeb  (chapter(axe (peg axe 2)) q.q.n.dom)
+                   ==
+          [* ~ *]  %=  $
+                     dom  r.dom
+                     axe  (peg axe 3)
+                     yeb  (chapter(axe (peg axe 2)) q.q.n.dom)
+                   ==
+          [* * *]  %=  $
+                     dom  r.dom
+                     axe  (peg axe 7)
+                     yeb  %=  $
+                            dom  l.dom
+                            axe  (peg axe 6)
+                            yeb  (chapter(axe (peg axe 2)) q.q.n.dom)
+        ==         ==     ==
+    ++  chapter
+      |=  dab=(map term hoon)
+      ^+  yeb
+      ?-  dab
+        ~        yeb
+        [* ~ ~]  [[axe q.n.dab] yeb]
+        [* * ~]  %=  $
+                   dab  l.dab
+                   axe  (peg axe 3)
+                   yeb  [[(peg axe 2) q.n.dab] yeb]
+                 ==
+        [* ~ *]  %=  $
+                   dab  r.dab
+                   axe  (peg axe 3)
+                   yeb  [[(peg axe 2) q.n.dab] yeb]
+                 ==
+        [* * *]  %=  $
+                   dab  r.dab
+                   axe  (peg axe 7)
+                   yeb  %=  $
+                          dab  l.dab
+                          axe  (peg axe 6)
+                          yeb  [[(peg axe 2) q.n.dab] yeb]
+      ==         ==     ==
+    --
+  ::
+  ++  lose
+    ~/  %lose
+    |=  gen=hoon  ^-  type
+    (chip | gen)
+  ::
+  ++  chip
+    ~/  %chip
+    |=  [how=? gen=hoon]  ^-  type
+    ?:  ?=([%wtts *] gen)
+      (cool how q.gen (play ~(example ax p.gen)))
+    ?:  ?=([%wthx *] gen)
+      =+  fid=(find %both q.gen)
+      ?-  -.fid
+        %|  sut
+        %&  =<  q
+            %+  take  p.p.fid
+            |=(a=type ?:(how ~(gain ar a p.gen) ~(lose ar a p.gen)))
+      ==
+    ?:  ?&(how ?=([%wtpm *] gen))
+      |-(?~(p.gen sut $(p.gen t.p.gen, sut ^$(gen i.p.gen))))
+    ?:  ?&(!how ?=([%wtbr *] gen))
+      |-(?~(p.gen sut $(p.gen t.p.gen, sut ^$(gen i.p.gen))))
+    =+  neg=~(open ap gen)
+    ?:(=(neg gen) sut $(gen neg))
+  ::
+  ++  bake
+    |=  [dox=type hud=poly dab=(map term hoon)]
+    ^-  *
+    ?:  ?=(~ dab)
+      ~
+    =+  ^=  dov
+        ::  this seems wrong but it's actually right
+        ::
+        ?-  hud
+          %dry  (mull %noun dox q.n.dab)
+          %wet  ~
+        ==
+    ?-  dab
+      [* ~ ~]  dov
+      [* ~ *]  [dov $(dab r.dab)]
+      [* * ~]  [dov $(dab l.dab)]
+      [* * *]  [dov $(dab l.dab) $(dab r.dab)]
+    ==
+  ::
+  ++  balk
+    |=  [dox=type hud=poly dom=(map term tome)]
+    ^-  *
+    ?:  ?=(~ dom)
+      ~
+    =+  dov=(bake dox hud q.q.n.dom)
+    ?-    dom
+      [* ~ ~]   dov
+      [* ~ *]   [dov $(dom r.dom)]
+      [* * ~]   [dov $(dom l.dom)]
+      [* * *]   [dov $(dom l.dom) $(dom r.dom)]
+    ==
+  ::
+  ++  mile
+    ::    mull all chapters and feet in a core
+    ::
+    |=  [dox=type mel=vair nym=(unit term) hud=poly dom=(map term tome)]
+    ^-  (pair type type)
+    =+  yet=(core sut [nym hud %gold] sut (laze nym hud dom) dom)
+    =+  hum=(core dox [nym hud %gold] dox (laze nym hud dom) dom)
+    =+  (balk(sut yet) hum hud dom)
+    [yet hum]
+  ::
+  ++  mine
+    ::    mint all chapters and feet in a core
+    ::
+    |=  [gol=type mel=vair nym=(unit term) hud=poly dom=(map term tome)]
+    ^-  (pair type nock)
+    |^
+    =/  log  (chapters-check (core-check gol))
+    =/  dog  (get-tomes log)
+    =-  :_  [%1 dez]
+        (core sut [nym hud mel] sut [[%full ~] dez] dom)
+    ^=  dez
+    =.  sut  (core sut [nym hud %gold] sut (laze nym hud dom) dom)
+    |-  ^-  ?(~ ^)
+    ?:  ?=(~ dom)
+      ~
+    =/  dov=?(~ ^)
+      =/  dab=(map term hoon)  q.q.n.dom
+      =/  dag  (arms-check dab (get-arms dog p.n.dom))
+      |-  ^-  ?(~ ^)
+      ?:  ?=(~ dab)
+        ~
+      =/  gog  (get-arm-type log dag p.n.dab)
+      =+  vad=(hemp hud gog q.n.dab)
+      ?-    dab
+        [* ~ ~]   vad
+        [* ~ *]   [vad $(dab r.dab)]
+        [* * ~]   [vad $(dab l.dab)]
+        [* * *]   [vad $(dab l.dab) $(dab r.dab)]
+      ==
+    ?-    dom
+      [* ~ ~]   dov
+      [* ~ *]   [dov $(dom r.dom)]
+      [* * ~]   [dov $(dom l.dom)]
+      [* * *]   [dov $(dom l.dom) $(dom r.dom)]
+    ==
+    ::
+    ::  all the below arms are used for gol checking and should have no
+    ::  effect other than giving more specific errors
+    ::
+    ::  +gol-type: all the possible types we could be expecting.
+    ::
+    +$  gol-type
+      $~  %noun
+      $@  %noun
+      $%  [%cell p=type q=type]
+          [%core p=type q=coil]
+          [%fork p=(set gol-type)]
+      ==
+    ::  +core-check: check that we're looking for a core
+    ::
+    ++  core-check
+      |=  log=type
+      |-  ^-  gol-type
+      ?+    log  $(log repo(sut log))
+          %noun      (nice log &)
+          %void      (nice %noun |)
+          [%atom *]  (nice %noun |)
+          [%cell *]  (nice log (nest(sut p.log) & %noun))
+          [%core *]  (nice log(r.p.q %gold) &)
+          [%fork *]
+        =/  tys  ~(tap in p.log)
+        :-  %fork
+        |-  ^-  (set gol-type)
+        ?~  tys
+          ~
+        =/  a  ^$(log i.tys)
+        =/  b  $(tys t.tys)
+        (~(put in b) a)
+      ==
+    ::  +chapters-check: check we have the expected number of chapters
+    ::
+    ++  chapters-check
+      |=  log=gol-type
+      |-  ^-  gol-type
+      ?-    log
+          %noun      (nice log &)
+          [%cell *]  (nice log &)
+          [%core *]  ~_  leaf+"core-number-of-chapters"
+                     (nice log =(~(wyt by dom) ~(wyt by q.r.q.log)))
+          [%fork *]
+        =/  tys  ~(tap in p.log)
+        |-  ^-  gol-type
+        ?~  tys
+          log
+        =/  a  ^$(log i.tys)
+        =/  b  $(tys t.tys)
+        log
+      ==
+    ::  +get-tomes: get map of tomes if exists
+    ::
+    ++  get-tomes
+      |=  log=gol-type
+      ^-  (unit (map term tome))
+      ?-    log
+          %noun      ~
+          [%cell *]  ~
+          [%fork *]  ~  ::  maybe could be more aggressive
+          [%core *]  `q.r.q.log
+      ==
+    ::  +get-arms: get arms in tome
+    ::
+    ++  get-arms
+      |=  [dog=(unit (map term tome)) nam=term]
+      ^-  (unit (map term hoon))
+      %+  bind  dog
+      |=  a=(map term tome)
+      ~_  leaf+"unexpcted-chapter.{(trip nam)}"
+      q:(~(got by a) nam)
+    ::  +arms-check: check we have the expected number of arms
+    ::
+    ++  arms-check
+      |=  [dab=(map term hoon) dag=(unit (map term hoon))]
+      ?~  dag
+        dag
+      =/  a
+        =/  exp  ~(wyt by u.dag)
+        =/  hav  ~(wyt by dab)
+        ~_  =/  expt  (scow %ud exp)
+            =/  havt  (scow %ud hav)
+            leaf+"core-number-of-arms.exp={expt}.hav={havt}"
+        ~_  =/  missing  ~(tap in (~(dif in ~(key by u.dag)) ~(key by dab)))
+            leaf+"missing.{<missing>}"
+        ~_  =/  extra  ~(tap in (~(dif in ~(key by dab)) ~(key by u.dag)))
+            leaf+"extra.{<extra>}"
+        ~_  =/  have  ~(tap in ~(key by dab))
+            leaf+"have.{<have>}"
+        (nice dag =(exp hav))
+      a
+    ::  +get-arm-type: get expected type of this arm
+    ::
+    ++  get-arm-type
+      |=  [log=gol-type dag=(unit (map term hoon)) nam=term]
+      ^-  type
+      %-  fall  :_  %noun
+      %+  bind  dag
+      |=  a=(map term hoon)
+      =/  gen=hoon
+        ~_  leaf+"unexpected-arm.{(trip nam)}"
+        (~(got by a) nam)
+      (play(sut log) gen)
+    ::
+    ++  nice
+      |*  [typ=* gud=?]
+      ?:  gud
+        typ
+      ~_  leaf+"core-nice"
+      !!
+    --
+  ::
+  ++  mint
+    ~/  %mint
+    |=  [gol=type gen=hoon]
+    ^-  [p=type q=nock]
+    ::~&  %pure-mint
+    |^  ^-  [p=type q=nock]
+    ?:  ?&(=(%void sut) !?=([%dbug *] gen))
+      ?.  |(!vet ?=([%lost *] gen) ?=([%zpzp *] gen))
+        ~>(%mean.'mint-vain' !!)
+      [%void %0 0]
+    ?-    gen
+    ::
+        [^ *]
+      =+  hed=$(gen p.gen, gol %noun)
+      =+  tal=$(gen q.gen, gol %noun)
+      [(nice (cell p.hed p.tal)) (cons q.hed q.tal)]
+    ::
+        [%brcn *]  (grow %gold p.gen %dry [%$ 1] q.gen)
+        [%brpt *]  (grow %gold p.gen %wet [%$ 1] q.gen)
+    ::
+        [%cnts *]  (~(mint et p.gen q.gen) gol)
+    ::
+        [%dtkt *]
+      =+  nef=$(gen [%kttr p.gen])
+      [p.nef [%12 [%1 hoon-version p.nef] q:$(gen q.gen, gol %noun)]]
+    ::
+        [%dtls *]  [(nice [%atom %$ ~]) [%4 q:$(gen p.gen, gol [%atom %$ ~])]]
+        [%sand *]  [(nice (play gen)) [%1 q.gen]]
+        [%rock *]  [(nice (play gen)) [%1 q.gen]]
+    ::
+        [%dttr *]
+      [(nice %noun) [%2 q:$(gen p.gen, gol %noun) q:$(gen q.gen, gol %noun)]]
+    ::
+        [%dtts *]
+      [(nice bool) [%5 q:$(gen p.gen, gol %noun) q:$(gen q.gen, gol %noun)]]
+    ::
+        [%dtwt *]  [(nice bool) [%3 q:$(gen p.gen, gol %noun)]]
+        [%hand *]  [p.gen q.gen]
+        [%ktbr *]  =+(vat=$(gen p.gen) [(nice (wrap(sut p.vat) %iron)) q.vat])
+    ::
+        [%ktls *]
+      =+(hif=(nice (play p.gen)) [hif q:$(gen q.gen, gol hif)])
+    ::
+        [%ktpm *]  =+(vat=$(gen p.gen) [(nice (wrap(sut p.vat) %zinc)) q.vat])
+        [%ktsg *]  (blow gol p.gen)
+        [%tune *]  [(face p.gen sut) [%0 %1]]
+        [%ktwt *]  =+(vat=$(gen p.gen) [(nice (wrap(sut p.vat) %lead)) q.vat])
+    ::
+        [%note *]
+      =+  hum=$(gen q.gen)
+      [(hint [sut p.gen] p.hum) q.hum]
+    ::
+        [%sgzp *]  ~_(duck(sut (play p.gen)) $(gen q.gen))
+        [%sggr *]
+      =+  hum=$(gen q.gen)
+      :: ?:  &(huz !?=(%|(@ [?(%sgcn %sgls) ^]) p.gen))
+      ::  hum
+      :-  p.hum
+      :+  %11
+        ?-    p.gen
+            @   p.gen
+            ^   [p.p.gen q:$(gen q.p.gen, gol %noun)]
+        ==
+      q.hum
+    ::
+        [%tsgr *]
+      =+  fid=$(gen p.gen, gol %noun)
+      =+  dov=$(sut p.fid, gen q.gen)
+      [p.dov (comb q.fid q.dov)]
+    ::
+        [%tscm *]
+      $(gen q.gen, sut (busk p.gen))
+    ::
+        [%wtcl *]
+      =+  nor=$(gen p.gen, gol bool)
+      =+  [fex=(gain p.gen) wux=(lose p.gen)]
+      ::
+      ::  if either branch is impossible, eliminate it
+      ::  (placing the conditional in a dynamic hint to preserve crashes)
+      ::
+      =+  ^=  [ned duy]
+        ?-  -
+          [%void %void]  |+[%0 0]
+          [%void *]      &+[%1 |]
+          [* %void]      &+[%1 &]
+          *              |+q.nor
+        ==
+      =+  hiq=$(sut fex, gen q.gen)
+      =+  ran=$(sut wux, gen r.gen)
+      =+  fol=(cond duy q.hiq q.ran)
+      [(fork p.hiq p.ran ~) ?.(ned fol [%11 [%toss q.nor] fol])]
+    ::
+        [%wthx *]
+      :-  (nice bool)
+      =+  fid=(fend %read [[%& 1] q.gen])
+      (~(fish ar `type`p.fid `skin`p.gen) q.fid)
+    ::
+        [%fits *]
+      :-  (nice bool)
+      =+  ref=(play p.gen)
+      =+  fid=(find %read q.gen)
+      ~|  [%test q.gen]
+      |-  ^-  nock
+      ?-  -.fid
+        %&  ?-  -.q.p.fid
+              %&  (fish(sut ref) (tend p.p.fid))
+              %|  $(fid [%| (fine fid)])
+            ==
+        %|  [%7 q.p.fid (fish(sut ref) 1)]
+      ==
+    ::
+        [%dbug *]
+      ~_  (show %o p.gen)
+      =+  hum=$(gen q.gen)
+      [p.hum [%11 [%spot %1 p.gen] q.hum]]
+    ::
+        [%zpcm *]   [(nice (play p.gen)) [%1 q.gen]]    ::  XX validate!
+        [%lost *]
+      ?:  vet
+        ~_  (dunk(sut (play p.gen)) 'lost')
+        ~>(%mean.'mint-lost' !!)
+      [%void [%0 0]]
+    ::
+        [%zpmc *]
+      =+  vos=$(gol %noun, gen q.gen)
+      =+  ref=p:$(gol %noun, gen p.gen)
+      [(nice (cell ref p.vos)) (cons [%1 burp(sut p.vos)] q.vos)]
+    ::
+        [%zpgl *]
+      =/  typ  (nice (play [%kttr p.gen]))
+      =/  val
+        =<  q
+        %_    $
+            gol  %noun
+            gen
+          :^    %wtcl
+              :+  %cncl  [%limb %levi]
+              :~  [%tsgr [%zpgr [%kttr p.gen]] [%$ 2]]
+                  [%tsgr q.gen [%$ 2]]
+              ==
+            [%tsgr q.gen [%$ 3]]
+          [%zpzp ~]
+        ==
+      [typ val]
+    ::
+        [%zpts *]   [(nice %noun) [%1 q:$(vet |, gen p.gen)]]
+        [%zppt *]   ?:((feel p.gen) $(gen q.gen) $(gen r.gen))
+    ::
+        [%zpzp ~]  [%void [%0 0]]
+        *
+      =+  doz=~(open ap gen)
+      ?:  =(doz gen)
+        ~_  (show [%c 'hoon'] [%q gen])
+        ~>(%mean.'mint-open' !!)
+      $(gen doz)
+    ==
+    ::
+    ++  nice
+      |=  typ=type
+      ~_  leaf+"mint-nice"
+      ?>  ?|(!vet (nest(sut gol) & typ))
+      typ
+    ::
+    ++  grow
+      |=  [mel=vair nym=(unit term) hud=poly ruf=hoon dom=(map term tome)]
+      ^-  [p=type q=nock]
+      =+  dan=^$(gen ruf, gol %noun)
+      =+  pul=(mine gol mel nym hud dom)
+      [(nice p.pul) (cons q.pul q.dan)]
+    --
+  ::
+  ++  moot
+    =+  gil=*(set type)
+    |-  ^-  ?
+    ?-  sut
+      [%atom *]  |
+      [%cell *]  |($(sut p.sut) $(sut q.sut))
+      [%core *]  $(sut p.sut)
+      [%face *]  $(sut q.sut)
+      [%fork *]  (levy ~(tap in p.sut) |=(type ^$(sut +<)))
+      [%hint *]  $(sut q.sut)
+      [%hold *]  |((~(has in gil) sut) $(gil (~(put in gil) sut), sut repo))
+      %noun      |
+      %void      &
+    ==
+  ::
+  ++  mull
+    ~/  %mull
+    |=  [gol=type dox=type gen=hoon]
+    |^  ^-  [p=type q=type]
+    ?:  =(%void sut)
+      ~>(%mean.'mull-none' !!)
+    ?-    gen
+    ::
+        [^ *]
+      =+  hed=$(gen p.gen, gol %noun)
+      =+  tal=$(gen q.gen, gol %noun)
+      [(nice (cell p.hed p.tal)) (cell q.hed q.tal)]
+    ::
+        [%brcn *]  (grow %gold p.gen %dry [%$ 1] q.gen)
+        [%brpt *]  (grow %gold p.gen %wet [%$ 1] q.gen)
+        [%cnts *]  (~(mull et p.gen q.gen) gol dox)
+        [%dtkt *]  =+($(gen q.gen, gol %noun) $(gen [%kttr p.gen]))
+        [%dtls *]  =+($(gen p.gen, gol [%atom %$ ~]) (beth [%atom %$ ~]))
+        [%sand *]  (beth (play gen))
+        [%rock *]  (beth (play gen))
+    ::
+        [%dttr *]
+      =+([$(gen p.gen, gol %noun) $(gen q.gen, gol %noun)] (beth %noun))
+    ::
+        [%dtts *]
+      =+([$(gen p.gen, gol %noun) $(gen q.gen, gol %noun)] (beth bool))
+    ::
+        [%dtwt *]  =+($(gen p.gen, gol %noun) (beth bool)) ::  XX  =|
+        [%hand *]  [p.gen p.gen]
+        [%ktbr *]
+      =+(vat=$(gen p.gen) [(wrap(sut p.vat) %iron) (wrap(sut q.vat) %iron)])
+    ::
+        [%ktls *]
+      =+  hif=[p=(nice (play p.gen)) q=(play(sut dox) p.gen)]
+      =+($(gen q.gen, gol p.hif) hif)
+    ::
+        [%ktpm *]
+      =+(vat=$(gen p.gen) [(wrap(sut p.vat) %zinc) (wrap(sut q.vat) %zinc)])
+    ::
+        [%tune *]
+      [(face p.gen sut) (face p.gen dox)]
+    ::
+        [%ktwt *]
+      =+(vat=$(gen p.gen) [(wrap(sut p.vat) %lead) (wrap(sut q.vat) %lead)])
+    ::
+        [%note *]
+      =+  vat=$(gen q.gen)
+      [(hint [sut p.gen] p.vat) (hint [dox p.gen] q.vat)]
+    ::
+        [%ktsg *]  $(gen p.gen)
+        [%sgzp *]  ~_(duck(sut (play p.gen)) $(gen q.gen))
+        [%sggr *]  $(gen q.gen)
+        [%tsgr *]
+      =+  lem=$(gen p.gen, gol %noun)
+      $(gen q.gen, sut p.lem, dox q.lem)
+    ::
+        [%tscm *]
+      =/  boc  (busk p.gen)
+      =/  nuf  (busk(sut dox) p.gen)
+      $(gen q.gen, sut boc, dox nuf)
+    ::
+        [%wtcl *]
+      =+  nor=$(gen p.gen, gol bool)
+      =+  ^=  hiq  ^-  [p=type q=type]
+          =+  fex=[p=(gain p.gen) q=(gain(sut dox) p.gen)]
+          ?:  =(%void p.fex)
+            :-  %void
+            ?:  =(%void q.fex)
+              %void
+            ~>(%mean.'if-z' (play(sut q.fex) q.gen))
+          ?:  =(%void q.fex)
+            ~>(%mean.'mull-bonk-b' !!)
+          $(sut p.fex, dox q.fex, gen q.gen)
+      =+  ^=  ran  ^-  [p=type q=type]
+          =+  wux=[p=(lose p.gen) q=(lose(sut dox) p.gen)]
+          ?:  =(%void p.wux)
+            :-  %void
+            ?:  =(%void q.wux)
+              %void
+            ~>(%mean.'if-a' (play(sut q.wux) r.gen))
+          ?:  =(%void q.wux)
+            ~>(%mean.'mull-bonk-c' !!)
+          $(sut p.wux, dox q.wux, gen r.gen)
+      [(nice (fork p.hiq p.ran ~)) (fork q.hiq q.ran ~)]
+    ::
+        [%fits *]
+      =+  waz=[p=(play p.gen) q=(play(sut dox) p.gen)]
+      =+  ^=  syx  :-  p=(cove q:(mint %noun [%wing q.gen]))
+                   q=(cove q:(mint(sut dox) %noun [%wing q.gen]))
+      =+  pov=[p=(fish(sut p.waz) p.syx) q=(fish(sut q.waz) q.syx)]
+      ?.  &(=(p.syx q.syx) =(p.pov q.pov))
+        ~>(%mean.'mull-bonk-a' !!)
+      (beth bool)
+    ::
+        [%wthx *]
+      ~>  %mean.'mull-bonk-x'
+      =+  :-  new=[type=p axis=q]:(fend %read [[%& 1] q.gen])
+          old=[type=p axis=q]:(fend(sut dox) %read [[%& 1] q.gen])
+      ?>  =(axis.old axis.new)
+      ?>  (nest(sut type.old) & type.new)
+      (beth bool)
+    ::
+        [%dbug *]  ~_((show %o p.gen) $(gen q.gen))
+        [%zpcm *]  [(nice (play p.gen)) (play(sut dox) p.gen)]
+        [%lost *]
+      ?:  vet
+        ::  ~_  (dunk(sut (play p.gen)) 'also')
+        ~>(%mean.'mull-skip' !!)
+      (beth %void)
+    ::
+        [%zpts *]  (beth %noun)
+    ::
+        [%zpmc *]
+      =+  vos=$(gol %noun, gen q.gen)       ::  XX validate!
+      [(nice (cell (play p.gen) p.vos)) (cell (play(sut dox) p.gen) q.vos)]
+    ::
+        [%zpgl *]
+      ::  XX is this right?
+      (beth (play [%kttr p.gen]))
+    ::
+        [%zppt *]
+      =+  [(feel p.gen) (feel(sut dox) p.gen)]
+      ?.  =(-< ->)
+        ~>(%mean.'mull-bonk-f' !!)
+      ?:  -<
+        $(gen q.gen)
+      $(gen r.gen)
+    ::
+        [%zpzp *]  (beth %void)
+        *
+      =+  doz=~(open ap gen)
+      ?:  =(doz gen)
+        ~_  (show [%c 'hoon'] [%q gen])
+        ~>(%mean.'mull-open' !!)
+      $(gen doz)
+    ==
+    ::
+    ++  beth
+      |=  typ=type
+      [(nice typ) typ]
+    ::
+    ++  nice
+      |=  typ=type
+      ::  ~_  (dunk(sut gol) 'need')
+      ::  ~_  (dunk(sut typ) 'have')
+      ~_  leaf+"mull-nice"
+      ?>  ?|(!vet (nest(sut gol) & typ))
+      typ
+    ::
+    ++  grow
+      |=  [mel=vair nym=(unit term) hud=poly ruf=hoon dom=(map term tome)]
+      ::  make al
+      ~_  leaf+"mull-grow"
+      ^-  [p=type q=type]
+      =+  dan=^$(gen ruf, gol %noun)
+      =+  yaz=(mile(sut p.dan) q.dan mel nym hud dom)
+      [(nice p.yaz) q.yaz]
+    --
+  ++  meet  |=(ref=type &((nest | ref) (nest(sut ref) | sut)))
+  ::                                                    ::
+  ++  miss                                              ::  nonintersection
+    |=  $:  ::  ref: symmetric type
+            ::
+            ref=type
+        ==
+    ::  intersection of sut and ref is empty
+    ::
+    ^-  ?
+    =|  gil=(set (set type))
+    =<  dext
+    |%
+    ++  dext
+      ^-  ?
+      ::
+      ?:  =(ref sut)
+        (nest(sut %void) | sut)
+      ?-  sut
+        %void      &
+        %noun      (nest(sut %void) | ref)
+        [%atom *]  sint
+        [%cell *]  sint
+        [%core *]  sint(sut [%cell %noun %noun])
+        [%fork *]  %+  levy  ~(tap in p.sut)
+                   |=(type dext(sut +<))
+        [%face *]  dext(sut q.sut)
+        [%hint *]  dext(sut q.sut)
+        [%hold *]  =+  (~(gas in *(set type)) `(list type)`[sut ref ~])
+                   ?:  (~(has in gil) -)
+                      &
+                   %=  dext
+                     sut  repo
+                     gil  (~(put in gil) -)
+      ==           ==
+    ++  sint
+      ?+  ref      dext(sut ref, ref sut)
+        [%atom *]  ?.  ?=([%atom *] sut)  &
+                   ?&  ?=(^ q.ref)
+                       ?=(^ q.sut)
+                       !=(q.ref q.sut)
+                   ==
+        [%cell *]  ?.  ?=([%cell *] sut)  &
+                   ?|  dext(sut p.sut, ref p.ref)
+                       dext(sut q.sut, ref q.ref)
+      ==           ==
+    --
+  ++  mite  |=(ref=type |((nest | ref) (nest(sut ref) & sut)))
+  ++  nest
+    ~/  %nest
+    |=  [tel=? ref=type]
+    =|  $:  seg=(set type)                              ::  degenerate sut
+            reg=(set type)                              ::  degenerate ref
+            gil=(set [p=type q=type])                   ::  assume nest
+        ==
+    =<  dext
+    ~%  %nest-in  ..$  ~
+    |%
+    ++  deem
+      |=  [mel=vair ram=vair]
+      ^-  ?
+      ?.  |(=(mel ram) =(%lead mel) =(%gold ram))  |
+      ?-  mel
+        %lead  &
+        %gold  meet
+        %iron  dext(sut (peek(sut ref) %rite 2), ref (peek %rite 2))
+        %zinc  dext(sut (peek %read 2), ref (peek(sut ref) %read 2))
+      ==
+    ::
+    ++  deep
+      |=  $:  dom=(map term tome)
+              vim=(map term tome)
+          ==
+      ^-  ?
+      ?:  ?=(~ dom)  =(vim ~)
+      ?:  ?=(~ vim)  |
+      ?&  =(p.n.dom p.n.vim)
+          $(dom l.dom, vim l.vim)
+          $(dom r.dom, vim r.vim)
+      ::
+          =+  [dab hem]=[q.q.n.dom q.q.n.vim]
+          |-  ^-  ?
+          ?:  ?=(~ dab)  =(hem ~)
+          ?:  ?=(~ hem)  |
+          ?&  =(p.n.dab p.n.hem)
+              $(dab l.dab, hem l.hem)
+              $(dab r.dab, hem r.hem)
+              %=  dext
+                sut  (play q.n.dab)
+                ref  (play(sut ref) q.n.hem)
+      ==  ==  ==
+    ::
+    ++  dext
+      =<  $
+      ~%  %nest-dext  +  ~
+      |.
+      ^-  ?
+      =-  ?:  -  &
+          ?.  tel  |
+          ~_  (dunk %need)
+          ~_  (dunk(sut ref) %have)
+          ~>  %mean.'nest-fail'
+          !!
+      ?:  =(sut ref)  &
+      ?-  sut
+        %void      sint
+        %noun      &
+        [%atom *]  ?.  ?=([%atom *] ref)  sint
+                   ?&  (fitz p.sut p.ref)
+                       |(?=(~ q.sut) =(q.sut q.ref))
+                   ==
+        [%cell *]  ?.  ?=([%cell *] ref)  sint
+                   ?&  dext(sut p.sut, ref p.ref, seg ~, reg ~)
+                       dext(sut q.sut, ref q.ref, seg ~, reg ~)
+                   ==
+        [%core *]  ?.  ?=([%core *] ref)  sint
+                   ?:  =(q.sut q.ref)  dext(sut p.sut, ref p.ref)
+                   ?&  =(q.p.q.sut q.p.q.ref)  ::  same wet/dry
+                       meet(sut q.q.sut, ref p.sut)
+                       dext(sut q.q.ref, ref p.ref)
+                       (deem(sut q.q.sut, ref q.q.ref) r.p.q.sut r.p.q.ref)
+                       ?:  =(%wet q.p.q.sut)  =(q.r.q.sut q.r.q.ref)
+                       ?|  (~(has in gil) [sut ref])
+                           %.  [q.r.q.sut q.r.q.ref]
+                           %=  deep
+                             gil  (~(put in gil) [sut ref])
+                             sut  sut(p q.q.sut, r.p.q %gold)
+                             ref  ref(p q.q.ref, r.p.q %gold)
+                       ==  ==
+                   ==
+        [%face *]  dext(sut q.sut)
+        [%fork *]  ?.  ?=(?([%atom *] %noun [%cell *] [%core *]) ref)  sint
+                   (lien ~(tap in p.sut) |=(type dext(tel |, sut +<)))
+        [%hint *]  dext(sut q.sut)
+        [%hold *]  ?:  (~(has in seg) sut)  |
+                   ?:  (~(has in gil) [sut ref])  &
+                   %=  dext
+                     sut  repo
+                     seg  (~(put in seg) sut)
+                     gil  (~(put in gil) [sut ref])
+      ==           ==
+    ::
+    ++  meet  &(dext dext(sut ref, ref sut))
+    ++  sint
+      ^-  ?
+      ?-  ref
+        %noun       |
+        %void       &
+        [%atom *]   |
+        [%cell *]   |
+        [%core *]   dext(ref repo(sut ref))
+        [%face *]   dext(ref q.ref)
+        [%fork *]   (levy ~(tap in p.ref) |=(type dext(ref +<)))
+        [%hint *]   dext(ref q.ref)
+        [%hold *]   ?:  (~(has in reg) ref)  &
+                    ?:  (~(has in gil) [sut ref])  &
+                    %=  dext
+                      ref  repo(sut ref)
+                      reg  (~(put in reg) ref)
+                      gil  (~(put in gil) [sut ref])
+      ==            ==
+    --
+  ::
+  ++  peek
+    ~/  %peek
+    |=  [way=?(%read %rite %both %free) axe=axis]
+    ^-  type
+    ?:  =(1 axe)
+      sut
+    =+  [now=(cap axe) lat=(mas axe)]
+    =+  gil=*(set type)
+    |-  ^-  type
+    ?-    sut
+        [%atom *]   %void
+        [%cell *]   ?:(=(2 now) ^$(sut p.sut, axe lat) ^$(sut q.sut, axe lat))
+        [%core *]
+      ?.  =(3 now)  %noun
+      =+  pec=(peel way r.p.q.sut)
+      =/  tow
+        ?:  =(1 lat)  1
+        (cap lat)
+      %=    ^$
+          axe  lat
+          sut
+        ?:  ?|  =([& &] pec)
+                &(sam.pec =(tow 2))
+                &(con.pec =(tow 3))
+            ==
+          p.sut
+        ~_  leaf+"payload-block"
+        ?.  =(way %read)  !!
+        %+  cell
+          ?.(sam.pec %noun ^$(sut p.sut, axe 2))
+        ?.(con.pec %noun ^$(sut p.sut, axe 3))
+      ==
+    ::
+        [%fork *]   (fork (turn ~(tap in p.sut) |=(type ^$(sut +<))))
+        [%hold *]
+      ?:  (~(has in gil) sut)
+        %void
+      $(gil (~(put in gil) sut), sut repo)
+    ::
+        %void       %void
+        %noun       %noun
+        *           $(sut repo)
+    ==
+  ::
+  ++  peel
+    |=  [way=vial met=?(%gold %iron %lead %zinc)]
+    ^-  [sam=? con=?]
+    ?:  ?=(%gold met)  [& &]
+    ?-  way
+      %both  [| |]
+      %free  [& &]
+      %read  [?=(%zinc met) |]
+      %rite  [?=(%iron met) |]
+    ==
+  ::
+  ++  play
+    ~/  %play
+    =>  .(vet |)
+    |=  gen=hoon
+    ^-  type
+    ?-  gen
+      [^ *]      (cell $(gen p.gen) $(gen q.gen))
+      [%brcn *]  (core sut [p.gen %dry %gold] sut *seminoun q.gen)
+      [%brpt *]  (core sut [p.gen %wet %gold] sut *seminoun q.gen)
+      [%cnts *]  ~(play et p.gen q.gen)
+      [%dtkt *]  $(gen [%kttr p.gen])
+      [%dtls *]  [%atom %$ ~]
+      [%rock *]  |-  ^-  type
+                 ?@  q.gen  [%atom p.gen `q.gen]
+                 [%cell $(q.gen -.q.gen) $(q.gen +.q.gen)]
+      [%sand *]  ?@  q.gen
+                   ?:  =(%n p.gen)  ?>(=(0 q.gen) [%atom p.gen `q.gen])
+                   ?:  =(%f p.gen)  ?>((lte q.gen 1) bool)
+                   [%atom p.gen ~]
+                 $(-.gen %rock)
+      [%tune *]  (face p.gen sut)
+      [%dttr *]  %noun
+      [%dtts *]  bool
+      [%dtwt *]  bool
+      [%hand *]  p.gen
+      [%ktbr *]  (wrap(sut $(gen p.gen)) %iron)
+      [%ktls *]  $(gen p.gen)
+      [%ktpm *]  (wrap(sut $(gen p.gen)) %zinc)
+      [%ktsg *]  $(gen p.gen)
+      [%ktwt *]  (wrap(sut $(gen p.gen)) %lead)
+      [%note *]  (hint [sut p.gen] $(gen q.gen))
+      [%sgzp *]  ~_(duck(sut ^$(gen p.gen)) $(gen q.gen))
+      [%sggr *]  $(gen q.gen)
+      [%tsgr *]  $(gen q.gen, sut $(gen p.gen))
+      [%tscm *]  $(gen q.gen, sut (busk p.gen))
+      [%wtcl *]  =+  [fex=(gain p.gen) wux=(lose p.gen)]
+                 %-  fork  :~
+                   ?:(=(%void fex) %void $(sut fex, gen q.gen))
+                   ?:(=(%void wux) %void $(sut wux, gen r.gen))
+                 ==
+      [%fits *]  bool
+      [%wthx *]  bool
+      [%dbug *]  ~_((show %o p.gen) $(gen q.gen))
+      [%zpcm *]  $(gen p.gen)
+      [%lost *]  %void
+      [%zpmc *]  (cell $(gen p.gen) $(gen q.gen))
+      [%zpgl *]  (play [%kttr p.gen])
+      [%zpts *]  %noun
+      [%zppt *]  ?:((feel p.gen) $(gen q.gen) $(gen r.gen))
+      [%zpzp *]  %void
+      *          =+  doz=~(open ap gen)
+                 ?:  =(doz gen)
+                   ~_  (show [%c 'hoon'] [%q gen])
+                   ~>  %mean.'play-open'
+                   !!
+                 $(gen doz)
+    ==
+  ::                                                    ::
+  ++  redo                                              ::  refurbish faces
+    ~/  %redo
+    |=  $:  ::  ref: raw payload
+            ::
+            ref=type
+        ==
+    ::  :type: subject refurbished to reference namespace
+    ::
+    ^-  type
+    ::  hos: subject tool stack
+    ::  wec: reference tool stack set
+    ::  gil: repetition set
+    ::
+    =|  hos=(list tool)
+    =/  wec=(set (list tool))  [~ ~ ~]
+    =|  gil=(set (pair type type))
+    =<  ::  errors imply subject/reference mismatch
+        ::
+        ~|  %redo-match
+        ::  reduce by subject
+        ::
+        dext
+    |%
+    ::                                                  ::
+    ++  dear                                            ::  resolve tool stack
+      ::  :(unit (list tool)): unified tool stack
+      ::
+      ^-  (unit (list tool))
+      ::  empty implies void
+      ::
+      ?~  wec  `~
+      ::  any reference faces must be clear
+      ::
+      ?.  ?=([* ~ ~] wec)
+        ~&  [%dear-many wec]
+        ~
+      :-  ~
+      ::  har: single reference tool stack
+      ::
+      =/  har  n.wec
+      ::  len: lengths of [sut ref] face stacks
+      ::
+      =/  len  [p q]=[(lent hos) (lent har)]
+      ::  lip: length of sut-ref face stack overlap
+      ::
+      ::      AB
+      ::       BC
+      ::
+      ::    +lip is (lent B), where +hay is forward AB
+      ::    and +liv is forward BC (stack BA and CB).
+      ::
+      ::    overlap is a weird corner case.  +lip is
+      ::    almost always 0.  brute force is fine.
+      ::
+      =/  lip
+        =|  lup=(unit @ud)
+        =|  lip=@ud
+        |-  ^-  @ud
+        ?:  |((gth lip p.len) (gth lip q.len))
+          (fall lup 0)
+        ::  lep: overlap candidate: suffix of subject face stack
+        ::
+        =/  lep  (slag (sub p.len lip) hos)
+        ::  lap: overlap candidate: prefix of reference face stack
+        ::
+        =/  lap  (scag lip har)
+        ::  save any match and continue
+        ::
+        $(lip +(lip), lup ?.(=(lep lap) lup `lip))
+      ::  ~&  [har+har hos+hos len+len lip+lip]
+      ::  produce combined face stack (forward ABC, stack CBA)
+      ::
+      (weld hos (slag lip har))
+    ::                                                  ::
+    ++  dext                                            ::  subject traverse
+      ::  :type: refurbished subject
+      ::
+      ^-  type
+      ::  check for trivial cases
+      ::
+      ?:  ?|  =(sut ref)
+              ?=(?(%noun %void [?(%atom %core) *]) ref)
+          ==
+        done
+      ::  ~_  (dunk 'redo: dext: sut')
+      ::  ~_  (dunk(sut ref) 'redo: dext: ref')
+      ?-    sut
+          ?(%noun %void [?(%atom %core) *])
+        ::  reduce reference and reassemble leaf
+        ::
+        done:(sint &)
+      ::
+          [%cell *]
+        ::  reduce reference to match subject
+        ::
+        =>  (sint &)
+        ?>  ?=([%cell *] sut)
+        ::  leaf with possible recursive descent
+        ::
+        %=    done
+            sut
+          ::  clear face stacks for descent
+          ::
+          =:  hos  ~
+              wec  [~ ~ ~]
+            ==
+          ::  descend into cell
+          ::
+          :+  %cell
+            dext(sut p.sut, ref (peek(sut ref) %free 2))
+          dext(sut q.sut, ref (peek(sut ref) %free 3))
+        ==
+      ::
+          [%face *]
+        ::  push face on subject stack, and descend
+        ::
+        dext(hos [p.sut hos], sut q.sut)
+      ::
+          [%hint *]
+        ::  work through hint
+        ::
+        (hint p.sut dext(sut q.sut))
+      ::
+          [%fork *]
+        ::  reconstruct each case in fork
+        ::
+        (fork (turn ~(tap in p.sut) |=(type dext(sut +<))))
+      ::
+          [%hold *]
+        ::  reduce to hard
+        ::
+        =>  (sint |)
+        ?>  ?=([%hold *] sut)
+        ?:  (~(has in fan) [p.sut q.sut])
+          ::  repo loop; redo depends on its own product
+          ::
+          done:(sint &)
+        ?:  (~(has in gil) [sut ref])
+          ::  type recursion, stop renaming
+          ::
+          done:(sint |)
+        ::  restore unchanged holds
+        ::
+        =+  repo
+        =-  ?:(=(- +<) sut -)
+        dext(sut -, gil (~(put in gil) sut ref))
+      ==
+    ::                                                  ::
+    ++  done                                            ::  complete assembly
+      ^-  type
+      ::  :type: subject refurbished
+      ::
+      ::  lov: combined face stack
+      ::
+      =/  lov
+          =/  lov  dear
+          ?~  lov
+            ::  ~_  (dunk 'redo: dear: sut')
+            ::  ~_  (dunk(sut ref) 'redo: dear: ref')
+            ~&  [%wec wec]
+            !!
+          (need lov)
+      ::  recompose faces
+      ::
+      |-  ^-  type
+      ?~  lov  sut
+      $(lov t.lov, sut (face i.lov sut))
+    ::
+    ++  sint                                            ::  reduce by reference
+      |=  $:  ::  hod: expand holds
+              ::
+              hod=?
+          ==
+      ::  ::.: reference with face/fork/hold reduced
+      ::
+      ^+  .
+      ::  =-  ~>  %slog.[0 (dunk 'sint: sut')]
+      ::      ~>  %slog.[0 (dunk(sut ref) 'sint: ref')]
+      ::      ~>  %slog.[0 (dunk(sut =>(- ref)) 'sint: pro')]
+      ::      -
+      ?+    ref  .
+          [%hint *]  $(ref q.ref)
+          [%face *]
+        ::  extend all stacks in set
+        ::
+        %=  $
+          ref  q.ref
+          wec  (~(run in wec) |=((list tool) [p.ref +<]))
+        ==
+      ::
+          [%fork *]
+        ::  reconstruct all relevant cases
+        ::
+        =-  ::  ~>  %slog.[0 (dunk 'fork: sut')]
+            ::  ~>  %slog.[0 (dunk(sut ref) 'fork: ref')]
+            ::  ~>  %slog.[0 (dunk(sut (fork ->)) 'fork: pro')]
+            +(wec -<, ref (fork ->))
+        =/  moy  ~(tap in p.ref)
+        |-  ^-  (pair (set (list tool)) (list type))
+        ?~  moy  [~ ~]
+        ::  head recurse
+        ::
+        =/  mor  $(moy t.moy)
+        ::  prune reference cases outside subject
+        ::
+        ?:  (miss i.moy)  mor
+        ::  unify all cases
+        ::
+        =/  dis  ^$(ref i.moy)
+        [(~(uni in p.mor) wec.dis) [ref.dis q.mor]]
+      ::
+          [%hold *]
+        ?.  hod  .
+        $(ref repo(sut ref))
+      ==
+    --
+  ::
+  ++  repo
+    ^-  type
+    ?-  sut
+      [%core *]   [%cell %noun p.sut]
+      [%face *]   q.sut
+      [%hint *]   q.sut
+      [%hold *]   (rest [[p.sut q.sut] ~])
+      %noun       (fork [%atom %$ ~] [%cell %noun %noun] ~)
+      *           ~>(%mean.'repo-fltt' !!)
+    ==
+  ::
+  ++  rest
+    ~/  %rest
+    |=  leg=(list [p=type q=hoon])
+    ^-  type
+    ?:  (lien leg |=([p=type q=hoon] (~(has in fan) [p q])))
+      ~>(%mean.'rest-loop' !!)
+    =>  .(fan (~(gas in fan) leg))
+    %-  fork
+    %~  tap  in
+    %-  ~(gas in *(set type))
+    (turn leg |=([p=type q=hoon] (play(sut p) q)))
+  ::
+  ++  sink
+    ~/  %sink
+    |^  ^-  cord
+    ?-  sut
+      %void      'void'
+      %noun      'noun'
+      [%atom *]  (rap 3 'atom ' p.sut ' ' ?~(q.sut '~' u.q.sut) ~)
+      [%cell *]  (rap 3 'cell ' (mup p.sut) ' ' (mup q.sut) ~)
+      [%face *]  (rap 3 'face ' ?@(p.sut p.sut (mup p.sut)) ' ' (mup q.sut) ~)
+      [%fork *]  (rap 3 'fork ' (mup p.sut) ~)
+      [%hint *]  (rap 3 'hint ' (mup p.sut) ' ' (mup q.sut) ~)
+      [%hold *]  (rap 3 'hold ' (mup p.sut) ' ' (mup q.sut) ~)
+    ::
+        [%core *]
+      %+  rap  3
+      :~  'core '
+          (mup p.sut)
+          ' '
+          ?~(p.p.q.sut '~' u.p.p.q.sut)
+          ' '
+          q.p.q.sut
+          ' '
+          r.p.q.sut
+          ' '
+          (mup q.q.sut)
+          ' '
+          (mup p.r.q.sut)
+      ==
+    ==
+    ::
+    ++  mup  |=(* (scot %p (mug +<)))
+    --
+  ::
+  ++  take
+    |=  [vit=vein duz=$-(type type)]
+    ^-  (pair axis type)
+    :-  (tend vit)
+    =.  vit  (flop vit)
+    |-  ^-  type
+    ?~  vit  (duz sut)
+    ?~  i.vit
+      |-  ^-  type
+      ?+  sut      ^$(vit t.vit)
+        [%face *]  (face p.sut ^$(vit t.vit, sut q.sut))
+        [%hint *]  (hint p.sut ^$(sut q.sut))
+        [%fork *]  (fork (turn ~(tap in p.sut) |=(type ^$(sut +<))))
+        [%hold *]  $(sut repo)
+      ==
+    =+  vil=*(set type)
+    |-  ^-  type
+    ?:  =(1 u.i.vit)
+      ^$(vit t.vit)
+    =+  [now lat]=(cap u.i.vit)^(mas u.i.vit)
+    ?-  sut
+      %noun      $(sut [%cell %noun %noun])
+      %void      %void
+      [%atom *]  %void
+      [%cell *]  ?:  =(2 now)
+                   (cell $(sut p.sut, u.i.vit lat) q.sut)
+                  (cell p.sut $(sut q.sut, u.i.vit lat))
+      [%core *]  ?:  =(2 now)
+                   $(sut repo)
+                 (core $(sut p.sut, u.i.vit lat) q.sut)
+      [%face *]  (face p.sut $(sut q.sut))
+      [%fork *]  (fork (turn ~(tap in p.sut) |=(type ^$(sut +<))))
+      [%hint *]  (hint p.sut $(sut q.sut))
+      [%hold *]  ?:  (~(has in vil) sut)
+                   %void
+                 $(sut repo, vil (~(put in vil) sut))
+    ==
+  ::
+  ++  tack
+    |=  [hyp=wing mur=type]
+    ~_  (show [%c %tack] %l hyp)
+    =+  fid=(find %rite hyp)
+    ?>  ?=(%& -.fid)
+    (take p.p.fid |=(type mur))
+  ::
+  ++  tend
+    |=  vit=vein
+    ^-  axis
+    ?~(vit 1 (peg $(vit t.vit) ?~(i.vit 1 u.i.vit)))
+  ::
+  ++  toss
+    ~/  %toss
+    |=  [hyp=wing mur=type men=(list [p=type q=foot])]
+    ^-  [p=axis q=(list [p=type q=foot])]
+    =-  [(need p.wib) q.wib]
+    ^=  wib
+    |-  ^-  [p=(unit axis) q=(list [p=type q=foot])]
+    ?~  men
+      [*(unit axis) ~]
+    =+  geq=(tack(sut p.i.men) hyp mur)
+    =+  mox=$(men t.men)
+    [(mate p.mox `_p.mox`[~ p.geq]) [[q.geq q.i.men] q.mox]]
+  ::
+  ++  wrap
+    ~/  %wrap
+    |=  yoz=?(%lead %iron %zinc)
+    ~_  leaf+"wrap"
+    ^-  type
+    ?+  sut  sut
+      [%cell *]  (cell $(sut p.sut) $(sut q.sut))
+      [%core *]  ?>(|(=(%gold r.p.q.sut) =(%lead yoz)) sut(r.p.q yoz))
+      [%face *]  (face p.sut $(sut q.sut))
+      [%fork *]  (fork (turn ~(tap in p.sut) |=(type ^$(sut +<))))
+      [%hint *]  (hint p.sut $(sut q.sut))
+      [%hold *]  $(sut repo)
+    ==
+  --
+++  us                                                  ::  prettyprinter
+  =>  |%
+      +$  cape  [p=(map @ud wine) q=wine]               ::
+      +$  wine                                          ::
+                $@  $?  %noun                           ::
+                        %path                           ::
+                        %type                           ::
+                        %void                           ::
+                        %wall                           ::
+                        %wool                           ::
+                        %yarn                           ::
+                    ==                                  ::
+                $%  [%mato p=term]                      ::
+                    [%core p=(list @ta) q=wine]         ::
+                    [%face p=term q=wine]               ::
+                    [%list p=term q=wine]               ::
+                    [%pear p=term q=@]                  ::
+                    [%bcwt p=(list wine)]               ::
+                    [%plot p=(list wine)]               ::
+                    [%stop p=@ud]                       ::
+                    [%tree p=term q=wine]               ::
+                    [%unit p=term q=wine]               ::
+                    [%name p=stud q=wine]               ::
+                ==                                      ::
+      --
+  |_  sut=type
+  ++  dash
+    |=  [mil=tape lim=char lam=tape]
+    ^-  tape
+    =/  esc  (~(gas in *(set @tD)) lam)
+    :-  lim
+    |-  ^-  tape
+    ?~  mil  [lim ~]
+    ?:  ?|  =(lim i.mil)
+            =('\\' i.mil)
+            (~(has in esc) i.mil)
+        ==
+      ['\\' i.mil $(mil t.mil)]
+    ?:  (lte ' ' i.mil)
+      [i.mil $(mil t.mil)]
+    ['\\' ~(x ne (rsh 2 i.mil)) ~(x ne (end 2 i.mil)) $(mil t.mil)]
+  ::
+  ++  deal  |=(lum=* (dish dole lum))
+  ++  dial
+    |=  ham=cape
+    =+  gid=*(set @ud)
+    =<  `tank`-:$
+    |%
+    ++  many
+      |=  haz=(list wine)
+      ^-  [(list tank) (set @ud)]
+      ?~  haz  [~ gid]
+      =^  mor  gid  $(haz t.haz)
+      =^  dis  gid  ^$(q.ham i.haz)
+      [[dis mor] gid]
+    ::
+    ++  $
+      ^-  [tank (set @ud)]
+      ?-    q.ham
+          %noun      :_(gid [%leaf '*' ~])
+          %path      :_(gid [%leaf '/' ~])
+          %type      :_(gid [%leaf '#' 't' ~])
+          %void      :_(gid [%leaf '#' '!' ~])
+          %wool      :_(gid [%leaf '*' '"' '"' ~])
+          %wall      :_(gid [%leaf '*' '\'' '\'' ~])
+          %yarn      :_(gid [%leaf '"' '"' ~])
+          [%mato *]  :_(gid [%leaf '@' (trip p.q.ham)])
+          [%core *]
+        =^  cox  gid  $(q.ham q.q.ham)
+        :_  gid
+        :+  %rose
+          [[' ' ~] ['<' ~] ['>' ~]]
+        |-  ^-  (list tank)
+        ?~  p.q.ham  [cox ~]
+        [[%leaf (rip 3 i.p.q.ham)] $(p.q.ham t.p.q.ham)]
+      ::
+          [%face *]
+        =^  cox  gid  $(q.ham q.q.ham)
+        :_(gid [%palm [['=' ~] ~ ~ ~] [%leaf (trip p.q.ham)] cox ~])
+      ::
+          [%list *]
+        =^  cox  gid  $(q.ham q.q.ham)
+        :_(gid [%rose [" " (weld (trip p.q.ham) "(") ")"] cox ~])
+      ::
+          [%bcwt *]
+        =^  coz  gid  (many p.q.ham)
+        :_(gid [%rose [[' ' ~] ['?' '(' ~] [')' ~]] coz])
+      ::
+          [%plot *]
+        =^  coz  gid  (many p.q.ham)
+        :_(gid [%rose [[' ' ~] ['[' ~] [']' ~]] coz])
+      ::
+          [%pear *]
+        :_(gid [%leaf '%' ~(rend co [%$ p.q.ham q.q.ham])])
+      ::
+          [%stop *]
+        =+  num=~(rend co [%$ %ud p.q.ham])
+        ?:  (~(has in gid) p.q.ham)
+          :_(gid [%leaf '#' num])
+        =^  cox  gid
+            %=  $
+              gid    (~(put in gid) p.q.ham)
+              q.ham  (~(got by p.ham) p.q.ham)
+            ==
+        :_(gid [%palm [['.' ~] ~ ~ ~] [%leaf ['^' '#' num]] cox ~])
+      ::
+          [%tree *]
+        =^  cox  gid  $(q.ham q.q.ham)
+        :_(gid [%rose [" " (weld (trip p.q.ham) "(") ")"] cox ~])
+      ::
+          [%unit *]
+        =^  cox  gid  $(q.ham q.q.ham)
+        :_(gid [%rose [" " (weld (trip p.q.ham) "(") ")"] cox ~])
+      ::
+          [%name *]
+        :_  gid
+        ?@  p.q.ham  (cat 3 '#' mark.p.q.ham)
+        (rap 3 '#' auth.p.q.ham '+' (spat type.p.q.ham) ~)
+      ==
+    --
+  ::
+  ++  dish  !:
+    |=  [ham=cape lum=*]  ^-  tank
+    ~|  [%dish-h ?@(q.ham q.ham -.q.ham)]
+    ~|  [%lump lum]
+    ~|  [%ham ham]
+    %-  need
+    =|  gil=(set [@ud *])
+    |-  ^-  (unit tank)
+    ?-    q.ham
+        %noun
+      %=    $
+          q.ham
+        ?:  ?=(@ lum)
+          [%mato %$]
+        :-  %plot
+        |-  ^-  (list wine)
+        [%noun ?:(?=(@ +.lum) [[%mato %$] ~] $(lum +.lum))]
+      ==
+    ::
+        %path
+      :-  ~
+      :+  %rose
+        [['/' ~] ['/' ~] ~]
+      |-  ^-  (list tank)
+      ?~  lum  ~
+      ?@  lum  !!
+      ?>  ?=(@ -.lum)
+      [[%leaf (rip 3 -.lum)] $(lum +.lum)]
+    ::
+        %type
+      =+  tyr=|.((dial dole))
+      =+  vol=tyr(sut lum)
+      =+  cis=;;(tank .*(vol [%9 2 %0 1]))
+      :^  ~   %palm
+        [~ ~ ~ ~]
+      [[%leaf '#' 't' '/' ~] cis ~]
+    ::
+        %wall
+      :-  ~
+      :+  %rose
+        [[' ' ~] ['<' '|' ~] ['|' '>' ~]]
+      |-  ^-  (list tank)
+      ?~  lum  ~
+      ?@  lum  !!
+      [[%leaf (trip ;;(@ -.lum))] $(lum +.lum)]
+    ::
+        %wool
+      :-  ~
+      :+  %rose
+        [[' ' ~] ['<' '<' ~] ['>' '>' ~]]
+      |-  ^-  (list tank)
+      ?~  lum  ~
+      ?@  lum  !!
+      [(need ^$(q.ham %yarn, lum -.lum)) $(lum +.lum)]
+    ::
+        %yarn
+      [~ %leaf (dash (tape lum) '"' "\{")]
+    ::
+        %void
+      ~
+    ::
+        [%mato *]
+      ?.  ?=(@ lum)
+        ~
+      :+  ~
+        %leaf
+      ?+    (rash p.q.ham ;~(sfix (cook crip (star low)) (star hig)))
+          ~(rend co [%$ p.q.ham lum])
+        %$    ~(rend co [%$ %ud lum])
+        %t    (dash (rip 3 lum) '\'' ~)
+        %tas  ['%' ?.(=(0 lum) (rip 3 lum) ['$' ~])]
+      ==
+    ::
+        [%core *]
+      ::  XX  needs rethinking for core metal
+      ::  ?.  ?=(^ lum)  ~
+      ::  =>  .(lum `*`lum)
+      ::  =-  ?~(tok ~ [~ %rose [[' ' ~] ['<' ~] ['>' ~]] u.tok])
+      ::  ^=  tok
+      ::  |-  ^-  (unit (list tank))
+      ::  ?~  p.q.ham
+      ::    =+  den=^$(q.ham q.q.ham)
+      ::    ?~(den ~ [~ u.den ~])
+      ::  =+  mur=$(p.q.ham t.p.q.ham, lum +.lum)
+      ::  ?~(mur ~ [~ [[%leaf (rip 3 i.p.q.ham)] u.mur]])
+      [~ (dial ham)]
+    ::
+        [%face *]
+      =+  wal=$(q.ham q.q.ham)
+      ?~  wal
+        ~
+      [~ %palm [['=' ~] ~ ~ ~] [%leaf (trip p.q.ham)] u.wal ~]
+    ::
+        [%list *]
+      ?:  =(~ lum)
+        [~ %leaf '~' ~]
+      =-  ?~  tok
+            ~
+          [~ %rose [[' ' ~] ['~' '[' ~] [']' ~]] u.tok]
+      ^=  tok
+      |-  ^-  (unit (list tank))
+      ?:  ?=(@ lum)
+        ?.(=(~ lum) ~ [~ ~])
+      =+  [for=^$(q.ham q.q.ham, lum -.lum) aft=$(lum +.lum)]
+      ?.  &(?=(^ for) ?=(^ aft))
+        ~
+      [~ u.for u.aft]
+    ::
+        [%bcwt *]
+      |-  ^-  (unit tank)
+      ?~  p.q.ham
+        ~
+      =+  wal=^$(q.ham i.p.q.ham)
+      ?~  wal
+        $(p.q.ham t.p.q.ham)
+      wal
+    ::
+        [%plot *]
+      =-  ?~  tok
+            ~
+          [~ %rose [[' ' ~] ['[' ~] [']' ~]] u.tok]
+      ^=  tok
+      |-  ^-  (unit (list tank))
+      ?~  p.q.ham
+        ~
+      ?:  ?=([* ~] p.q.ham)
+        =+  wal=^$(q.ham i.p.q.ham)
+        ?~(wal ~ [~ [u.wal ~]])
+      ?@  lum
+        ~
+      =+  gim=^$(q.ham i.p.q.ham, lum -.lum)
+      ?~  gim
+        ~
+      =+  myd=$(p.q.ham t.p.q.ham, lum +.lum)
+      ?~  myd
+        ~
+      [~ u.gim u.myd]
+    ::
+        [%pear *]
+      ?.  =(lum q.q.ham)
+        ~
+      =.  p.q.ham
+        (rash p.q.ham ;~(sfix (cook crip (star low)) (star hig)))
+      =+  fox=$(q.ham [%mato p.q.ham])
+      ?>  ?=([~ %leaf ^] fox)
+      ?:  ?=(?(%n %tas) p.q.ham)
+        fox
+      [~ %leaf '%' p.u.fox]
+    ::
+        [%stop *]
+      ?:  (~(has in gil) [p.q.ham lum])  ~
+      =+  kep=(~(get by p.ham) p.q.ham)
+      ?~  kep
+        ~|([%stop-loss p.q.ham] !!)
+      $(gil (~(put in gil) [p.q.ham lum]), q.ham u.kep)
+    ::
+        [%tree *]
+      =-  ?~  tok
+            ~
+          [~ %rose [[' ' ~] ['{' ~] ['}' ~]] u.tok]
+      ^=  tok
+      =+  tuk=*(list tank)
+      |-  ^-  (unit (list tank))
+      ?:  =(~ lum)
+        [~ tuk]
+      ?.  ?=([n=* l=* r=*] lum)
+        ~
+      =+  rol=$(lum r.lum)
+      ?~  rol
+        ~
+      =+  tim=^$(q.ham q.q.ham, lum n.lum)
+      ?~  tim
+        ~
+      $(lum l.lum, tuk [u.tim u.rol])
+    ::
+        [%unit *]
+      ?@  lum
+        ?.(=(~ lum) ~ [~ %leaf '~' ~])
+      ?.  =(~ -.lum)
+        ~
+      =+  wal=$(q.ham q.q.ham, lum +.lum)
+      ?~  wal
+        ~
+      [~ %rose [[' ' ~] ['[' ~] [']' ~]] [%leaf '~' ~] u.wal ~]
+    ::
+        [%name *]
+      $(q.ham q.q.ham)
+    ==
+  ::
+  ++  doge
+    |=  ham=cape
+    =-  ?+  woz  woz
+          [%list * [%mato %'ta']]  %path
+          [%list * [%mato %'t']]   %wall
+          [%list * [%mato %'tD']]  %yarn
+          [%list * %yarn]          %wool
+        ==
+    ^=  woz
+    ^-  wine
+    ?.  ?=([%stop *] q.ham)
+      ?:  ?&  ?=  [%bcwt [%pear %n %0] [%plot [%pear %n %0] [%face *] ~] ~]
+                q.ham
+              =(1 (met 3 p.i.t.p.i.t.p.q.ham))
+          ==
+        [%unit =<([p q] i.t.p.i.t.p.q.ham)]
+      q.ham
+    =+  may=(~(get by p.ham) p.q.ham)
+    ?~  may
+      q.ham
+    =+  nul=[%pear %n 0]
+    ?.  ?&  ?=([%bcwt *] u.may)
+            ?=([* * ~] p.u.may)
+            |(=(nul i.p.u.may) =(nul i.t.p.u.may))
+        ==
+      q.ham
+    =+  din=?:(=(nul i.p.u.may) i.t.p.u.may i.p.u.may)
+    ?:  ?&  ?=([%plot [%face *] [%face * %stop *] ~] din)
+            =(p.q.ham p.q.i.t.p.din)
+            =(1 (met 3 p.i.p.din))
+            =(1 (met 3 p.i.t.p.din))
+        ==
+      :+  %list
+        (cat 3 p.i.p.din p.i.t.p.din)
+      q.i.p.din
+    ?:  ?&  ?=  $:  %plot
+                    [%face *]
+                    [%face * %stop *]
+                    [[%face * %stop *] ~]
+                ==
+                din
+            =(p.q.ham p.q.i.t.p.din)
+            =(p.q.ham p.q.i.t.t.p.din)
+            =(1 (met 3 p.i.p.din))
+            =(1 (met 3 p.i.t.p.din))
+            =(1 (met 3 p.i.t.t.p.din))
+        ==
+      :+  %tree
+        %^    cat
+            3
+          p.i.p.din
+        (cat 3 p.i.t.p.din p.i.t.t.p.din)
+      q.i.p.din
+    q.ham
+  ::
+  ++  dole
+    ^-  cape
+    =+  gil=*(set type)
+    =+  dex=[p=*(map type @) q=*(map @ wine)]
+    =<  [q.p q]
+    |-  ^-  [p=[p=(map type @) q=(map @ wine)] q=wine]
+    =-  [p.tez (doge q.p.tez q.tez)]
+    ^=  tez
+    ^-  [p=[p=(map type @) q=(map @ wine)] q=wine]
+    ?:  (~(meet ut sut) -:!>(*type))
+      [dex %type]
+    ?-    sut
+        %noun      [dex sut]
+        %void      [dex sut]
+        [%atom *]  [dex ?~(q.sut [%mato p.sut] [%pear p.sut u.q.sut])]
+        [%cell *]
+      =+  hin=$(sut p.sut)
+      =+  yon=$(dex p.hin, sut q.sut)
+      :-  p.yon
+      :-  %plot
+      ?:(?=([%plot *] q.yon) [q.hin p.q.yon] [q.hin q.yon ~])
+    ::
+        [%core *]
+      =+  yad=$(sut p.sut)
+      :-  p.yad
+      =+  ^=  doy  ^-  [p=(list @ta) q=wine]
+          ?:  ?=([%core *] q.yad)
+            [p.q.yad q.q.yad]
+          [~ q.yad]
+      :-  %core
+      :_  q.doy
+      :_  p.doy
+      %^  cat  3
+        %~  rent  co
+        :+  %$  %ud
+        %-  ~(rep by (~(run by q.r.q.sut) |=(tome ~(wyt by q.+<))))
+        |=([[@ a=@u] b=@u] (add a b))
+      %^  cat  3
+        ?-(r.p.q.sut %gold '.', %iron '|', %lead '?', %zinc '&')
+      =+  gum=(mug q.r.q.sut)
+      %+  can  3
+      :~  [1 (add 'a' (mod gum 26))]
+          [1 (add 'a' (mod (div gum 26) 26))]
+          [1 (add 'a' (mod (div gum 676) 26))]
+      ==
+    ::
+        [%hint *]
+      =+  yad=$(sut q.sut)
+      ?.  ?=(%know -.q.p.sut)  yad
+      [p.yad [%name p.q.p.sut q.yad]]
+    ::
+        [%face *]
+      =+  yad=$(sut q.sut)
+      ?^(p.sut yad [p.yad [%face p.sut q.yad]])
+    ::
+        [%fork *]
+      =+  yed=(sort ~(tap in p.sut) aor)
+      =-  [p [%bcwt q]]
+      |-  ^-  [p=[p=(map type @) q=(map @ wine)] q=(list wine)]
+      ?~  yed
+        [dex ~]
+      =+  mor=$(yed t.yed)
+      =+  dis=^$(dex p.mor, sut i.yed)
+      [p.dis q.dis q.mor]
+    ::
+        [%hold *]
+      =+  hey=(~(get by p.dex) sut)
+      ?^  hey
+        [dex [%stop u.hey]]
+      ?:  (~(has in gil) sut)
+        =+  dyr=+(~(wyt by p.dex))
+        [[(~(put by p.dex) sut dyr) q.dex] [%stop dyr]]
+      =+  rom=$(gil (~(put in gil) sut), sut ~(repo ut sut))
+      =+  rey=(~(get by p.p.rom) sut)
+      ?~  rey
+        rom
+      [[p.p.rom (~(put by q.p.rom) u.rey q.rom)] [%stop u.rey]]
+    ==
+  ::
+  ++  duck  (dial dole)
+  --
+++  cain  sell                                          ::  $-(vase tank)
+++  noah  text                                          ::  $-(vase tape)
+++  onan  seer                                          ::  $-(vise vase)
+++  levi                                                ::  $-([type type] ?)
+  |=  [a=type b=type]
+  (~(nest ut a) & b)
+::
+++  text                                                ::  tape pretty-print
+  |=  vax=vase  ^-  tape
+  ~(ram re (sell vax))
+::
+++  seem  |=(toy=typo `type`toy)                        ::  promote typo
+++  seer  |=(vix=vise `vase`vix)                        ::  promote vise
+::
+::  +sell: pretty-print a vase to a tank using +deal.
+::
+++  sell
+  ~/  %sell
+  |=  vax=vase
+  ^-  tank
+  ~|  %sell
+  (~(deal us p.vax) q.vax)
+::
+::  +skol:  $-(type tank) using duck.
+::
+++  skol
+  |=  typ=type
+  ^-  tank
+  ~(duck ut typ)
+::
+++  slam                                                ::  slam a gate
+  |=  [gat=vase sam=vase]  ^-  vase
+  =+  :-  ^=  typ  ^-  type
+          [%cell p.gat p.sam]
+      ^=  gen  ^-  hoon
+      [%cnsg [%$ ~] [%$ 2] [%$ 3] ~]
+  =+  gun=(~(mint ut typ) %noun gen)
+  [p.gun (slum q.gat q.sam)]
+::
+::  +slab: states whether you can access an arm in a type.
+::
+::    .way: the access type ($vial): read, write, or read-and-write.
+::    The fourth case of $vial, %free, is not permitted because it would
+::    allow you to discover "private" information about a type,
+::    information which you could not make use of in (law-abiding) hoon anyway.
+::
+++  slab                                                ::  test if contains
+  |=  [way=?(%read %rite %both) cog=@tas typ=type]
+  ?=  [%& *]
+  (~(fond ut typ) way ~[cog])
+::
+++  slap
+  |=  [vax=vase gen=hoon]  ^-  vase                     ::  untyped vase .*
+  =+  gun=(~(mint ut p.vax) %noun gen)
+  [p.gun .*(q.vax q.gun)]
+::
+++  slog                                                ::  deify printf
+  =|  pri=@                                             ::  priority level
+  |=  a=tang  ^+  same                                  ::  .=  ~&(%a 1)
+  ?~(a same ~>(%slog.[pri i.a] $(a t.a)))               ::  ((slog ~[>%a<]) 1)
+::                                                      ::
+++  mean                                                ::  crash with trace
+  |=  a=tang
+  ^+  !!
+  ?~  a  !!
+  ~_(i.a $(a t.a))
+::
+++  road
+  |*  =(trap *)
+  ^+  $:trap
+  =/  res  (mule trap)
+  ?-  -.res
+    %&  p.res
+    %|  (mean p.res)
+  ==
+::
+++  slew                                                ::  get axis in vase
+  |=  [axe=@ vax=vase]
+  =/  typ  |.  (~(peek ut p.vax) %free axe)
+  |-  ^-  (unit vase)
+  ?:  =(1 axe)  `[$:typ q.vax]
+  ?@  q.vax     ~
+  $(axe (mas axe), q.vax ?-((cap axe) %2 -.q.vax, %3 +.q.vax))
+::
+++  slim                                                ::  identical to seer?
+  |=  old=vise  ^-  vase
+  old
+::
+++  slit                                                ::  type of slam
+  |=  [gat=type sam=type]
+  ?>  (~(nest ut (~(peek ut gat) %free 6)) & sam)
+  (~(play ut [%cell gat sam]) [%cnsg [%$ ~] [%$ 2] [%$ 3] ~])
+::
+++  slob                                                ::  superficial arm
+  |=  [cog=@tas typ=type]
+  ^-  ?
+  ?+  typ  |
+      [%hold *]  $(typ ~(repo ut typ))
+      [%hint *]  $(typ ~(repo ut typ))
+      [%core *]
+    |-  ^-  ?
+    ?~  q.r.q.typ  |
+    ?|  (~(has by q.q.n.q.r.q.typ) cog)
+        $(q.r.q.typ l.q.r.q.typ)
+        $(q.r.q.typ r.q.r.q.typ)
+    ==
+  ==
+::
+++  sloe                                                ::  get arms in core
+  |=  typ=type
+  ^-  (list term)
+  ?+    typ  ~
+      [%hold *]  $(typ ~(repo ut typ))
+      [%hint *]  $(typ ~(repo ut typ))
+      [%core *]
+    %-  zing
+    %+  turn  ~(tap by q.r.q.typ)
+      |=  [* b=tome]
+    %+  turn  ~(tap by q.b)
+      |=  [a=term *]
+    a
+  ==
+::
+++  slop                                                ::  cons two vases
+  |=  [hed=vase tal=vase]
+  ^-  vase
+  [[%cell p.hed p.tal] [q.hed q.tal]]
+::
+++  slot                                                ::  got axis in vase
+  |=  [axe=@ vax=vase]  ^-  vase
+  [(~(peek ut p.vax) %free axe) .*(q.vax [0 axe])]
+::
+++  slym                                                ::  slam w+o sample-type
+  |=  [gat=vase sam=*]  ^-  vase
+  (slap gat(+<.q sam) [%limb %$])
+::
+++  sped                                                ::  reconstruct type
+  |=  vax=vase
+  ^-  vase
+  :_  q.vax
+  ?@  q.vax  (~(fuse ut p.vax) [%atom %$ ~])
+  ?@  -.q.vax
+    ^=  typ
+    %-  ~(play ut p.vax)
+    [%wtgr [%wtts [%leaf %tas -.q.vax] [%& 2]~] [%$ 1]]
+  (~(fuse ut p.vax) [%cell %noun %noun])
+::  +swat: deferred +slap
+::
+++  swat
+  |=  [tap=(trap vase) gen=hoon]
+  ^-  (trap vase)
+  =/  gun  (~(mint ut p:$:tap) %noun gen)
+  |.  ~+
+  [p.gun .*(q:$:tap q.gun)]
+::
+::    5d: parser
++|  %parser
+::
+::  +vang: set +vast params
+::
+::    bug: debug mode
+::    doc: doccord parsing
+::    wer: where we are
+::
+++  vang
+  |=  [f=$@(? [bug=? doc=?]) wer=path]
+  %*(. vast bug ?@(f f bug.f), doc ?@(f & doc.f), wer wer)
+::
+++  vast                                                ::  main parsing core
+  =+  [bug=`?`| wer=*path doc=`?`&]
+  |%
+  ++  gash  %+  cook                                    ::  parse path
+              |=  a=(list tyke)  ^-  tyke
+              ?~(a ~ (weld i.a $(a t.a)))
+            (more fas limp)
+  ++  gasp  ;~  pose                                    ::  parse =path= etc.
+              %+  cook
+                |=([a=tyke b=tyke c=tyke] :(weld a b c))
+              ;~  plug
+                (cook |=(a=(list) (turn a |=(b=* ~))) (star tis))
+                (cook |=(a=hoon [[~ a] ~]) hasp)
+                (cook |=(a=(list) (turn a |=(b=* ~))) (star tis))
+              ==
+              (cook |=(a=(list) (turn a |=(b=* ~))) (plus tis))
+            ==
+  ++  glam  ~+((glue ace))
+  ++  hasp  ;~  pose                                    ::  path element
+              (ifix [sel ser] wide)
+              (stag %cncl (ifix [pal par] (most ace wide)))
+              (stag %sand (stag %tas (cold %$ buc)))
+              (stag %sand (stag %t qut))
+              %+  cook
+                |=(a=coin [%sand ?:(?=([~ %tas *] a) %tas %ta) ~(rent co a)])
+              nuck:so
+            ==
+  ++  limp  %+  cook
+              |=  [a=(list) b=tyke]
+              ?~  a  b
+              $(a t.a, b [`[%sand %tas %$] b])
+            ;~(plug (star fas) gasp)
+  ++  mota  %+  cook
+              |=([a=tape b=tape] (rap 3 (weld a b)))
+            ;~(plug (star low) (star hig))
+  ++  docs
+    |%
+    ::  +apex: prefix comment. may contain batch comments.
+    ::
+    ::    when a prefix doccord is parsed, it is possible that there is no +gap
+    ::    afterward to be consumed, so we add an additional newline and
+    ::    decrement the line number in the `hair` of the parser
+    ::
+    ::    the reason for this is that the whitespace parsing under +vast seems
+    ::    to factor more cleanly this way, at least compared to the variations
+    ::    tried without the extra newline. this doesn't mean there isn't a
+    ::    better factorization without it, though.
+    ++  apex
+      ?.  doc  (easy *whit)
+      %+  knee  *whit  |.  ~+
+      ;~  plug
+        |=  tub=nail
+        =/  vex
+          %.  tub
+          %-  star
+          %+  cook  |*([[a=* b=*] c=*] [a b c])
+          ;~(pfix (punt leap) into ;~(pose larg smol))
+        ?~  q.vex  vex
+        :-  p=p.vex
+        %-  some
+        ?~  p.u.q.vex
+          [p=~ q=q.u.q.vex]
+        :-  p=(malt p.u.q.vex)
+        q=`nail`[[(dec p.p.q.u.q.vex) q.p.q.u.q.vex] ['\0a' q.q.u.q.vex]]
+      ==
+    ::
+    ::  +apse: postfix comment.
+    ::
+    ::    a one line comment at the end of a line (typically starting at column
+    ::    57) that attaches to the expression starting at the beginning of the
+    ::    current line. does not use a $link.
+    ++  apse
+      ?.  doc  (easy *whiz)
+      %+  knee  *whiz  |.  ~+
+      ;~  pose
+        ;~(less ;~(plug into step en-link col ace) ;~(pfix into step line))
+      ::
+        (easy *whiz)
+      ==
+    ::
+    ++  leap                                            ::  whitespace w/o docs
+      %+  cold  ~
+      ;~  plug
+        ;~  pose
+          (just '\0a')
+          ;~(plug gah ;~(pose gah skip))
+          skip
+        ==
+        (star ;~(pose skip gah))
+      ==
+    ::
+    ::  +smol: 2 aces then summary, 4 aces then paragraphs.
+    ++  smol
+      ;~  pfix
+        step
+        ;~  plug
+          ;~  plug
+            (plus en-link)
+            ;~  pose
+              (ifix [;~(plug col ace) (just '\0a')] (cook crip (plus prn)))
+              (ifix [(star ace) (just '\0a')] (easy *cord))
+            ==
+          ==
+          (rant ;~(pfix step step text))
+        ==
+      ==
+    ::
+    ::  +larg: 4 aces then summary, 2 aces then paragraphs.
+    ++  larg
+      ;~  pfix
+        step  step
+        ;~  plug
+          ;~  sfix
+            ;~  plug
+              ;~  pose
+                ;~(sfix (plus en-link) col ace)
+                ;~(less ace (easy *cuff))
+              ==
+              ;~(less ace (cook crip (plus prn)))
+            ==
+            (just '\0a')
+          ==
+          (rant ;~(pfix step teyt))
+        ==
+      ==
+    ::
+    ++  rant
+      |*  sec=rule
+      %-  star
+      ;~  pfix
+        (ifix [into (just '\0a')] (star ace))
+        (plus (ifix [into (just '\0a')] sec))
+      ==
+    ::
+    ++  skip                                            ::  non-doccord comment
+      ;~  plug
+        col  col
+        ;~(less ;~(pose larg smol) ;~(plug (star prn) (just '\0a')))
+      ==
+    ::
+    ++  null  (cold ~ (star ace))
+    ++  text  (pick line code)
+    ++  teyt  (pick line ;~(pfix step code))
+    ++  line  ;~(less ace (cook crip (star prn)))
+    ++  code  ;~(pfix step ;~(less ace (cook crip (star prn))))
+    ++  step  ;~(plug ace ace)
+    ::
+    ++  into
+      ;~(plug (star ace) col col)
+    ::
+    ++  en-link
+      |=  a=nail  %.  a
+      %+  knee  *link  |.  ~+
+      %-  stew
+      ^.  stet  ^.  limo
+      :~  :-  '|'  ;~(pfix bar (stag %chat sym))
+          :-  '.'  ;~(pfix dot (stag %frag sym))
+          :-  '+'  ;~(pfix lus (stag %funk sym))
+          :-  '$'  ;~(pfix buc (stag %plan sym))
+          :-  '%'  ;~(pfix cen (stag %cone bisk:so))
+      ==
+    --
+  ::
+  ++  clad                                              ::  hoon doccords
+    |*  fel=rule
+    %+  cook
+      |=  [a=whit b=hoon c=whiz]
+      =?  b  !=(c *whiz)
+        [%note help/`[c]~ b]
+      =+  docs=~(tap by bat.a)
+      |-
+      ?~  docs  b
+      $(docs t.docs, b [%note help/i.docs b])
+    (seam fel)
+  ++  coat                                              ::  spec doccords
+    |*  fel=rule
+    %+  cook
+      |=  [a=whit b=spec c=whiz]
+      =?  b  !=(c *whiz)
+        [%gist help/`[c]~ b]
+      =+  docs=~(tap by bat.a)
+      |-
+      ?~  docs  b
+      $(docs t.docs, b [%gist help/i.docs b])
+    (seam fel)
+  ++  scye                                              ::  with prefix doccords
+    |*  fel=rule
+    ;~(pose ;~(plug apex:docs ;~(pfix gap fel)) ;~(plug (easy *whit) fel))
+  ++  seam                                              ::  with doccords
+    |*  fel=rule
+    (scye ;~(plug fel apse:docs))
+  ::
+  ++  plex                                              ::  reparse static path
+    |=  gen=hoon  ^-  (unit path)
+    ?:  ?=([%dbug *] gen)                               ::  unwrap %dbug
+      $(gen q.gen)
+    ?.  ?=([%clsg *] gen)  ~                            ::  require :~ hoon
+    %+  reel  p.gen                                     ::  build using elements
+    |=  [a=hoon b=_`(unit path)`[~ u=/]]                ::  starting from just /
+    ?~  b  ~
+    ?.  ?=([%sand ?(%ta %tas) @] a)  ~                  ::  /foo constants
+    `[q.a u.b]
+  ::
+  ++  phax
+    |=  ruw=(list (list woof))
+    =+  [yun=*(list hoon) cah=*(list @)]
+    =+  wod=|=([a=tape b=(list hoon)] ^+(b ?~(a b [[%mcfs %knit (flop a)] b])))
+    |-  ^+  yun
+    ?~  ruw
+      (flop (wod cah yun))
+    ?~  i.ruw  $(ruw t.ruw)
+    ?@  i.i.ruw
+      $(i.ruw t.i.ruw, cah [i.i.ruw cah])
+    $(i.ruw t.i.ruw, cah ~, yun [p.i.i.ruw (wod cah yun)])
+  ::
+  ++  posh
+    |=  [pre=(unit tyke) pof=(unit [p=@ud q=tyke])]
+    ^-  (unit (list hoon))
+    =-  ?^(- - ~&(%posh-fail -))
+    =+  wom=(poof wer)
+    %+  biff
+      ?~  pre  `u=wom
+      %+  bind  (poon wom u.pre)
+      |=  moz=(list hoon)
+      ?~(pof moz (weld moz (slag (lent u.pre) wom)))
+    |=  yez=(list hoon)
+    ?~  pof  `yez
+    =+  zey=(flop yez)
+    =+  [moz=(scag p.u.pof zey) gul=(slag p.u.pof zey)]
+    =+  zom=(poon (flop moz) q.u.pof)
+    ?~(zom ~ `(weld (flop gul) u.zom))
+  ::
+  ++  poof                                              ::  path -> (list hoon)
+    |=(pax=path ^-((list hoon) (turn pax |=(a=@ta [%sand %ta a]))))
+  ::
+  ::  tyke is =foo== as ~[~ `foo ~ ~]
+  ::  interpolate '=' path components
+  ++  poon                                              ::  try to replace '='s
+    |=  [pag=(list hoon) goo=tyke]                      ::    default to pag
+    ^-  (unit (list hoon))                              ::    for null goo's
+    ?~  goo  `~                                         ::  keep empty goo
+    %+  both                                            ::  otherwise head comes
+      ?^(i.goo i.goo ?~(pag ~ `u=i.pag))                ::    from goo or pag
+    $(goo t.goo, pag ?~(pag ~ t.pag))                   ::  recurse on tails
+  ::
+  ++  poor
+    %+  sear  posh
+    ;~  plug
+      (stag ~ gash)
+      ;~(pose (stag ~ ;~(pfix cen porc)) (easy ~))
+    ==
+  ::
+  ++  porc
+    ;~  plug
+      (cook |=(a=(list) (lent a)) (star cen))
+      ;~(pfix fas gash)
+    ==
+  ::
+  ++  rump
+    %+  sear
+      |=  [a=wing b=(unit hoon)]  ^-  (unit hoon)
+      ?~(b [~ %wing a] ?.(?=([@ ~] a) ~ [~ [%rock %tas i.a] u.b]))
+    ;~(plug rope ;~(pose (stag ~ wede) (easy ~)))
+  ::
+  ++  rood
+    ;~  pfix  fas
+      (stag %clsg poor)
+    ==
+  ::
+  ++  reed
+    ;~  pfix  fas
+      (stag %clsg (more fas stem))
+    ==
+  ::
+  ++  stem
+    %+  knee  *hoon  |.  ~+
+    %+  cook
+      |=  iota=$%([%hoon =hoon] iota)
+      ?@  iota  [%rock %tas iota]
+      ?:  ?=(%hoon -.iota)  hoon.iota
+      [%clhp [%rock %tas -.iota] [%sand iota]]
+    |^  %-  stew
+      ^.  stet  ^.  limo
+      :~  :-  'a'^'z'  ;~  pose
+                         (spit (stag %cncl (ifix [pal par] (most ace wide))))
+                         (spit (ifix [sel ser] wide))
+                         (slot sym)
+                       ==
+          :-  '$'      (cold %$ buc)
+          :-  '0'^'9'  (slot bisk:so)
+          :-  '-'      (slot tash:so)
+          :-  '.'      ;~(pfix dot zust:so)
+          :-  '~'      (slot ;~(pfix sig ;~(pose crub:so (easy [%n ~]))))
+          :-  '\''     (stag %t qut)
+          :-  '['      (slip (ifix [sel ser] wide))
+          :-  '('      (slip (stag %cncl (ifix [pal par] (most ace wide))))
+      ==
+    ::
+    ++  slip  |*(r=rule (stag %hoon r))
+    ++  slot  |*(r=rule (sear (soft iota) r))
+    ++  spit
+      |*  r=rule
+      %+  stag  %hoon
+      %+  cook
+        |*([a=term b=*] `hoon`[%clhp [%rock %tas a] b])
+      ;~((glue lus) sym r)
+    --
+  ::
+  ++  rupl
+    %+  cook
+      |=  [a=? b=(list hoon) c=?]
+      ?:  a
+        ?:  c
+          [%clsg [%clsg b] ~]
+        [%clsg b]
+      ?:  c
+        [%clsg [%cltr b] ~]
+      [%cltr b]
+    ;~  plug
+      ;~  pose
+        (cold | (just '['))
+        (cold & (jest '~['))
+      ==
+    ::
+      ;~  pose
+        (ifix [ace gap] (most gap tall))
+        (most ace wide)
+      ==
+    ::
+      ;~  pose
+        (cold & (jest ']~'))
+        (cold | (just ']'))
+      ==
+    ==
+  ::
+  ::
+  ++  sail                                              ::  xml template
+    |=  in-tall-form=?  =|  lin=?
+    |%
+    ::
+    ++  apex                                            ::  product hoon
+      %+  cook
+        |=  tum=(each manx:hoot marl:hoot)  ^-  hoon
+        ?-  -.tum
+          %&  [%xray p.tum]
+          %|  [%mcts p.tum]
+        ==
+      top-level
+    ::
+    ++  top-level                                       ::  entry-point
+      ;~(pfix mic ?:(in-tall-form tall-top wide-top))
+    ::
+    ++  inline-embed                                    ::  brace interpolation
+      %+  cook  |=(a=tuna:hoot a)
+      ;~  pose
+        ;~(pfix mic bracketed-elem(in-tall-form |))
+        ;~(plug tuna-mode sump)
+        (stag %tape sump)
+      ==
+    ::
+    ++  script-or-style                                 ::  script or style
+      %+  cook  |=(a=marx:hoot a)
+      ;~  plug
+        ;~(pose (jest %script) (jest %style))
+        wide-attrs
+      ==
+    ::
+    ++  tuna-mode                                       ::  xml node(s) kind
+      ;~  pose
+        (cold %tape hep)
+        (cold %manx lus)
+        (cold %marl tar)
+        (cold %call cen)
+      ==
+    ::
+    ++  wide-top                                        ::  wide outer top
+      %+  knee  *(each manx:hoot marl:hoot)  |.  ~+
+      ;~  pose
+        (stag %| wide-quote)
+        (stag %| wide-paren-elems)
+        (stag %& ;~(plug tag-head wide-tail))
+      ==
+    ::
+    ++  wide-inner-top                                  ::  wide inner top
+      %+  knee  *(each tuna:hoot marl:hoot)  |.  ~+
+      ;~  pose
+        wide-top
+        (stag %& ;~(plug tuna-mode wide))
+      ==
+    ::
+    ++  wide-attrs                                      ::  wide attributes
+      %+  cook  |=(a=(unit mart:hoot) (fall a ~))
+      %-  punt
+      %+  ifix  [pal par]
+      %+  more  (jest ', ')
+      ;~((glue ace) a-mane hopefully-quote)
+    ::
+    ++  wide-tail                                       ::  wide elements
+      %+  cook  |=(a=marl:hoot a)
+      ;~(pose ;~(pfix col wrapped-elems) (cold ~ mic) (easy ~))
+    ::
+    ++  wide-elems                                      ::  wide elements
+      %+  cook  |=(a=marl:hoot a)
+      %+  cook  join-tops
+      (star ;~(pfix ace wide-inner-top))
+    ::
+    ++  wide-paren-elems                                ::  wide flow
+      %+  cook  |=(a=marl:hoot a)
+      %+  cook  join-tops
+      (ifix [pal par] (more ace wide-inner-top))
+    ::
+    ::+|
+    ::
+    ++  drop-top
+      |=  a=(each tuna:hoot marl:hoot)  ^-  marl:hoot
+      ?-  -.a
+        %&  [p.a]~
+        %|  p.a
+      ==
+    ::
+    ++  join-tops
+      |=  a=(list (each tuna:hoot marl:hoot))  ^-  marl:hoot
+      (zing (turn a drop-top))
+    ::
+    ::+|
+    ::
+    ++  wide-quote                                      ::  wide quote
+      %+  cook  |=(a=marl:hoot a)
+      ;~  pose
+        ;~  less  (jest '"""')
+          (ifix [doq doq] (cook collapse-chars quote-innards))
+        ==
+      ::
+        %-  inde
+        %+  ifix  [(jest '"""\0a') (jest '\0a"""')]
+        (cook collapse-chars quote-innards(lin |))
+      ==
+    ::
+    ++  quote-innards                                   ::  wide+tall flow
+      %+  cook  |=(a=(list $@(@ tuna:hoot)) a)
+      %-  star
+      ;~  pose
+        ;~(pfix bas ;~(pose (mask "-+*%;\{") bas doq bix:ab))
+        inline-embed
+        ;~(less bas kel ?:(in-tall-form fail doq) prn)
+        ?:(lin fail ;~(less (jest '\0a"""') (just '\0a')))
+      ==
+    ::
+    ++  bracketed-elem                                  ::  bracketed element
+      %+  ifix  [kel ker]
+      ;~(plug tag-head wide-elems)
+    ::
+    ++  wrapped-elems                                   ::  wrapped tuna
+      %+  cook  |=(a=marl:hoot a)
+      ;~  pose
+        wide-paren-elems
+        (cook |=(@t `marl`[;/((trip +<))]~) qut)
+        (cook drop-top wide-top)
+      ==
+    ::
+    ++  a-mane                                          ::  mane as hoon
+      %+  cook
+        |=  [a=@tas b=(unit @tas)]
+        ?~(b a [a u.b])
+      ;~  plug
+        mixed-case-symbol
+        ;~  pose
+          %+  stag  ~
+            ;~(pfix cab mixed-case-symbol)
+          (easy ~)
+        ==
+      ==
+    ::
+    ++  en-class
+      |=  a=(list [%class p=term])
+      ^-  (unit [%class tape])
+      ?~  a  ~
+      %-  some
+      :-  %class
+      |-
+      %+  welp  (trip p.i.a)
+      ?~  t.a  ~
+      [' ' $(a t.a)]
+    ::
+    ++  tag-head                                        ::  tag head
+      %+  cook
+        |=  [a=mane:hoot b=mart:hoot c=mart:hoot]
+        ^-  marx:hoot
+        [a (weld b c)]
+      ;~  plug
+        a-mane
+      ::
+        %+  cook
+          |=  a=(list (unit [term (list beer:hoot)]))
+          ^-  (list [term (list beer:hoot)])
+          :: discard nulls
+          (murn a same)
+        ;~  plug
+          (punt ;~(plug (cold %id hax) (cook trip sym)))
+          (cook en-class (star ;~(plug (cold %class dot) sym)))
+          (punt ;~(plug ;~(pose (cold %href fas) (cold %src pat)) soil))
+          (easy ~)
+        ==
+      ::
+        wide-attrs
+      ==
+    ::
+    ++  tall-top                                        ::  tall top
+      %+  knee  *(each manx:hoot marl:hoot)  |.  ~+
+      ;~  pose
+        (stag %| ;~(pfix (plus ace) (cook collapse-chars quote-innards)))
+        (stag %& ;~(plug script-or-style script-style-tail))
+        (stag %& tall-elem)
+        (stag %| wide-quote)
+        (stag %| ;~(pfix tis tall-tail))
+        (stag %& ;~(pfix gar gap (stag [%div ~] cram)))
+        (stag %| ;~(plug ;~((glue gap) tuna-mode tall) (easy ~)))
+        (easy %| [;/("\0a")]~)
+      ==
+    ::
+    ++  tall-attrs                                      ::  tall attributes
+      %-  star
+      ;~  pfix  ;~(plug gap tis)
+        ;~((glue gap) a-mane hopefully-quote)
+      ==
+    ::
+    ++  tall-elem                                       ::  tall preface
+      %+  cook
+        |=  [a=[p=mane:hoot q=mart:hoot] b=mart:hoot c=marl:hoot]
+        ^-  manx:hoot
+        [[p.a (weld q.a b)] c]
+      ;~(plug tag-head tall-attrs tall-tail)
+    ::
+    ::REVIEW is there a better way to do this?
+    ++  hopefully-quote                                 :: prefer "quote" form
+      %+  cook  |=(a=(list beer:hoot) a)
+      %+  cook  |=(a=hoon ?:(?=(%knit -.a) p.a [~ a]~))
+      wide
+    ::
+    ++  script-style-tail                               ::  unescaped tall tail
+      %+  cook  |=(a=marl:hoot a)
+      %+  ifix  [gap ;~(plug gap duz)]
+      %+  most  gap
+      ;~  pfix  mic
+        %+  cook  |=(a=tape ;/(a))
+        ;~  pose
+          ;~(pfix ace (star prn))
+          (easy "\0a")
+        ==
+      ==
+    ::
+    ++  tall-tail                                       ::  tall tail
+      ?>  in-tall-form
+      %+  cook  |=(a=marl:hoot a)
+      ;~  pose
+        (cold ~ mic)
+        ;~(pfix col wrapped-elems(in-tall-form |))
+        ;~(pfix col ace (cook collapse-chars(in-tall-form |) quote-innards))
+        (ifix [gap ;~(plug gap duz)] tall-kids)
+      ==
+    ::
+    ++  tall-kids                                       ::  child elements
+      %+  cook  join-tops
+      ::  look for sail first, or markdown if not
+      (most gap ;~(pose top-level (stag %| cram)))
+    ::
+    ++  collapse-chars                                  ::  group consec chars
+      |=  reb=(list $@(@ tuna:hoot))
+      ^-  marl:hoot
+      =|  [sim=(list @) tuz=marl:hoot]
+      |-  ^-  marl:hoot
+      ?~  reb
+        =.  sim
+          ?.  in-tall-form   sim
+          [10 |-(?~(sim sim ?:(=(32 i.sim) $(sim t.sim) sim)))]
+        ?~(sim tuz [;/((flop sim)) tuz])
+      ?@  i.reb
+        $(reb t.reb, sim [i.reb sim])
+      ?~  sim  [i.reb $(reb t.reb, sim ~)]
+      [;/((flop sim)) i.reb $(reb t.reb, sim ~)]
+    --
+  ++  cram                                              ::  parse unmark
+    =>  |%
+        ++  item  (pair mite marl:hoot)                 ::  xml node generator
+        ++  colm  @ud                                   ::  column
+        ++  tarp  marl:hoot                             ::  node or generator
+        ++  mite                                        ::  context
+          $?  %down                                     ::  outer embed
+              %lunt                                     ::  unordered list
+              %lime                                     ::  list item
+              %lord                                     ::  ordered list
+              %poem                                     ::  verse
+              %bloc                                     ::  blockquote
+              %head                                     ::  heading
+          ==                                            ::
+        ++  trig                                        ::  line style
+          $:  col=@ud                                   ::  start column
+              sty=trig-style                            ::  style
+          ==                                            ::
+        ++  trig-style                                  ::  type of parsed line
+          $%  $:  %end                                  ::  terminator
+          $?  %done                                     ::  end of input
+                  %stet                                 ::    == end of markdown
+                  %dent                                 ::    outdent
+              ==  ==                                    ::
+              $:  %one                                  ::  leaf node
+              $?  %rule                                 ::    --- horz rule
+                  %fens                                 ::    ``` code fence
+                  %expr                                 ::    ;sail expression
+              ==  ==                                    ::
+              [%new p=trig-new]                         ::  open container
+              [%old %text]                              ::  anything else
+          ==                                            ::
+        ++  trig-new                                    ::  start a
+          $?  %lite                                     ::    + line item
+              %lint                                     ::    - line item
+              %head                                     ::  # heading
+              %bloc                                     ::  > block-quote
+              %poem                                     ::    [ ]{8} poem
+          ==                                            ::
+        ++  graf                                        ::  paragraph element
+          $%  [%bold p=(list graf)]                     ::  *bold*
+              [%talc p=(list graf)]                     ::  _italics_
+              [%quod p=(list graf)]                     ::  "double quote"
+              [%code p=tape]                            ::  code literal
+              [%text p=tape]                            ::  text symbol
+              [%link p=(list graf) q=tape]              ::  URL
+              [%mage p=tape q=tape]                     ::  image
+              [%expr p=tuna:hoot]                       ::  interpolated hoon
+          ==
+        --
+    =<  (non-empty:parse |=(nail `(like tarp)`~($ main +<)))
+    |%
+    ++  main
+      ::
+      ::    state of the parsing loop.
+      ::
+      ::  we maintain a construction stack for elements and a line
+      ::  stack for lines in the current block.  a blank line
+      ::  causes the current block to be parsed and thrown in the
+      ::  current element.  when the indent column retreats, the
+      ::  element stack rolls up.
+      ::
+      ::  .verbose: debug printing enabled
+      ::  .err: error position
+      ::  .ind: outer and inner indent level
+      ::  .hac: stack of items under construction
+      ::  .cur: current item under construction
+      ::  .par: current "paragraph" being read in
+      ::  .[loc txt]: parsing state
+      ::
+      =/  verbose  &
+      =|  err=(unit hair)
+      =|  ind=[out=@ud inr=@ud]
+      =|  hac=(list item)
+      =/  cur=item  [%down ~]
+      =|  par=(unit (pair hair wall))
+      |_  [loc=hair txt=tape]
+      ::
+      ++  $                                           ::  resolve
+        ^-  (like tarp)
+        =>  line
+        ::
+        ::  if error position is set, produce error
+        ?.  =(~ err)
+          ~&  err+err
+          [+.err ~]
+        ::
+        ::  all data was consumed
+        =-  [loc `[- [loc txt]]]
+        =>  close-par
+        |-  ^-  tarp
+        ::
+        ::  fold all the way to top
+        ?~  hac  cur-to-tarp
+        $(..^$ close-item)
+      ::
+      ::+|
+      ::
+      ++  cur-indent
+        ?-  p.cur
+          %down  2
+          %head  0
+          %lunt  0
+          %lime  2
+          %lord  0
+          %poem  8
+          %bloc  2
+        ==
+      ::
+      ++  back                                          ::  column retreat
+        |=  luc=@ud
+        ^+  +>
+        ?:  (gte luc inr.ind)  +>
+        ::
+        ::  nex: next backward step that terminates this context
+        =/  nex=@ud  cur-indent  ::  REVIEW code and poem blocks are
+                                 ::  handled elsewhere
+        ?:  (gth nex (sub inr.ind luc))
+          ::
+          ::  indenting pattern violation
+          ~?  verbose  indent-pattern-violation+[p.cur nex inr.ind luc]
+          ..^$(inr.ind luc, err `[p.loc luc])
+        =.  ..^$  close-item
+        $(inr.ind (sub inr.ind nex))
+      ::
+      ++  cur-to-tarp                                   ::  item to tarp
+        ^-  tarp
+        ?:  ?=(?(%down %head %expr) p.cur)
+          (flop q.cur)
+        =-  [[- ~] (flop q.cur)]~
+        ?-  p.cur
+          %lunt  %ul
+          %lord  %ol
+          %lime  %li
+          %poem  %div ::REVIEW actual container element?
+          %bloc  %blockquote
+        ==
+      ::
+      ++  close-item  ^+  .                             ::  complete and pop
+        ?~  hac  .
+        %=  .
+          hac  t.hac
+          cur  [p.i.hac (weld cur-to-tarp q.i.hac)]
+        ==
+      ::
+      ++  read-line                                     ::  capture raw line
+        =|  lin=tape
+        |-  ^+  [[lin *(unit _err)] +<.^$]  :: parsed tape and halt/error
+        ::
+        ::  no unterminated lines
+        ?~  txt
+          ~?  verbose  %unterminated-line
+          [[~ ``loc] +<.^$]
+        ?.  =(`@`10 i.txt)
+          ?:  (gth inr.ind q.loc)
+            ?.  =(' ' i.txt)
+              ~?  verbose  expected-indent+[inr.ind loc txt]
+              [[~ ``loc] +<.^$]
+            $(txt t.txt, q.loc +(q.loc))
+          ::
+          ::  save byte and repeat
+          $(txt t.txt, q.loc +(q.loc), lin [i.txt lin])
+        =.  lin
+        ::
+        ::  trim trailing spaces
+        |-  ^-  tape
+          ?:  ?=([%' ' *] lin)
+            $(lin t.lin)
+          (flop lin)
+          ::
+        =/  eat-newline=nail  [[+(p.loc) 1] t.txt]
+        =/  saw  look(+<.$ eat-newline)
+        ::
+        ?:  ?=([~ @ %end ?(%stet %dent)] saw)           ::  stop on == or dedent
+          [[lin `~] +<.^$]
+        [[lin ~] eat-newline]
+      ::
+      ++  look                                          ::  inspect line
+        ^-  (unit trig)
+        %+  bind  (wonk (look:parse loc txt))
+        |=  a=trig  ^+  a
+        ::
+        ::  treat a non-terminator as a terminator
+        ::  if it's outdented
+        ?:  =(%end -.sty.a)  a
+        ?:  (lth col.a out.ind)
+          a(sty [%end %dent])
+        a
+      ::
+      ++  close-par                                     ::  make block
+        ^+  .
+        ::
+        ::  empty block, no action
+        ?~  par  .
+        ::
+        ::  if block is verse
+        ?:  ?=(%poem p.cur)
+          ::
+          ::  add break between stanzas
+          =.  q.cur  ?~(q.cur q.cur [[[%br ~] ~] q.cur])
+          =-  close-item(par ~, q.cur (weld - q.cur), inr.ind (sub inr.ind 8))
+          %+  turn  q.u.par
+          |=  tape  ^-  manx
+          ::
+          ::  each line is a paragraph
+          :-  [%p ~]
+          :_  ~
+          ;/("{+<}\0a")
+        ::
+        ::  yex: block recomposed, with newlines
+        =/  yex=tape
+          %-  zing
+          %+  turn  (flop q.u.par)
+          |=  a=tape
+          (runt [(dec inr.ind) ' '] "{a}\0a")
+        ::
+        ::  vex: parse of paragraph
+        =/  vex=(like tarp)
+          ::
+          ::  either a one-line header or a paragraph
+          %.  [p.u.par yex]
+          ?:  ?=(%head p.cur)
+            (full head:parse)
+          (full para:parse)
+        ::
+        ::  if error, propagate correctly
+        ?~  q.vex
+          ~?  verbose  [%close-par p.cur yex]
+          ..$(err `p.vex)
+        ::
+        ::  finish tag if it's a header
+        =<  ?:(?=(%head p.cur) close-item ..$)
+        ::
+        ::  save good result, clear buffer
+        ..$(par ~, q.cur (weld p.u.q.vex q.cur))
+      ::
+      ++  line  ^+  .                                   ::  body line loop
+        ::
+        ::  abort after first error
+        ?:  !=(~ err)  .
+        ::
+        ::  saw: profile of this line
+        =/  saw  look
+        ~?  [debug=|]  [%look ind=ind saw=saw txt=txt]
+        ::
+        ::  if line is blank
+        ?~  saw
+          ::
+          ::  break section
+          =^  a=[tape fin=(unit _err)]  +<.$  read-line
+          ?^  fin.a
+            ..$(err u.fin.a)
+          =>(close-par line)
+        ::
+        ::  line is not blank
+        =>  .(saw u.saw)
+        ::
+        ::  if end of input, complete
+        ?:  ?=(%end -.sty.saw)
+          ..$(q.loc col.saw)
+        ::
+        =.  ind  ?~(out.ind [col.saw col.saw] ind)      ::  init indents
+        ::
+        ?:  ?|  ?=(~ par)                          :: if after a paragraph or
+                ?&  ?=(?(%down %lime %bloc) p.cur)  :: unspaced new container
+                    |(!=(%old -.sty.saw) (gth col.saw inr.ind))
+            ==  ==
+          =>  .(..$ close-par)
+            ::
+          ::  if column has retreated, adjust stack
+          =.  ..$  (back col.saw)
+            ::
+          =^  col-ok  sty.saw
+            ?+  (sub col.saw inr.ind)  [| sty.saw]      :: columns advanced
+              %0  [& sty.saw]
+              %8  [& %new %poem]
+            ==
+          ?.  col-ok
+            ~?  verbose  [%columns-advanced col.saw inr.ind]
+            ..$(err `[p.loc col.saw])
+        ::
+          =.  inr.ind  col.saw
+      ::
+          ::  unless adding a matching item, close lists
+          =.  ..$
+            ?:  ?|  &(?=(%lunt p.cur) !?=(%lint +.sty.saw))
+                    &(?=(%lord p.cur) !?=(%lite +.sty.saw))
+                ==
+              close-item
+            ..$
+        ::
+          =<  line(par `[loc ~])  ^+  ..$               ::  continue with para
+          ?-    -.sty.saw
+              %one  (read-one +.sty.saw)                ::  parse leaves
+              %new  (open-item p.sty.saw)               ::  open containers
+              %old  ..$                                 ::  just text
+          ==
+        ::
+        ::
+        ::- - - foo
+        ::  detect bad block structure
+        ?.  ::  first line of container is legal
+            ?~  q.u.par  &
+            ?-  p.cur
+        ::
+            ::  can't(/directly) contain text
+              ?(%lord %lunt)  ~|(bad-leaf-container+p.cur !!)
+        ::
+            ::  only one line in a header
+              %head  |
+          ::
+            ::  indented literals need to end with a blank line
+              %poem  (gte col.saw inr.ind)
+        ::
+            ::  text tarps must continue aligned
+              ?(%down %lunt %lime %lord %bloc)  =(col.saw inr.ind)
+          ==
+          ~?  verbose  bad-block-structure+[p.cur inr.ind col.saw]
+          ..$(err `[p.loc col.saw])
+        ::
+        ::  accept line and maybe continue
+        =^  a=[lin=tape fin=(unit _err)]  +<.$  read-line
+        =.  par  par(q.u [lin.a q.u.par])
+        ?^  fin.a  ..$(err u.fin.a)
+        line
+      ++  parse-block                                   ::  execute parser
+        |=  fel=$-(nail (like tarp))  ^+  +>
+        =/  vex=(like tarp)  (fel loc txt)
+        ?~  q.vex
+          ~?  verbose  [%parse-block txt]
+          +>.$(err `p.vex)
+        =+  [res loc txt]=u.q.vex
+        %_  +>.$
+          loc  loc
+          txt  txt
+          q.cur  (weld (flop `tarp`res) q.cur)          ::  prepend to the stack
+        ==
+      ::
+      ++  read-one                                      ::  read %one item
+        |=  sty=?(%expr %rule %fens)  ^+  +>
+        ?-  sty
+          %expr  (parse-block expr:parse)
+          %rule  (parse-block hrul:parse)
+          %fens  (parse-block (fens:parse inr.ind))
+        ==
+      ::
+      ++  open-item                                     ::  enter list/quote
+        |=  saw=trig-new
+        =<  +>.$:apex
+        |%
+        ++  apex  ^+  .                                 ::  open container
+          ?-  saw
+            %poem  (push %poem)                         ::  verse literal
+            %head  (push %head)                         ::  heading
+            %bloc  (entr %bloc)                         ::  blockquote line
+            %lint  (lent %lunt)                         ::  unordered list
+            %lite  (lent %lord)                         ::  ordered list
+          ==
+        ::
+        ++  push                                        ::  push context
+          |=(mite +>(hac [cur hac], cur [+< ~]))
+        ::
+        ++  entr                                        ::  enter container
+          |=  typ=mite
+          ^+  +>
+          ::
+          ::  indent by 2
+          =.  inr.ind  (add 2 inr.ind)
+          ::
+          ::  "parse" marker
+          =.  txt  (slag (sub inr.ind q.loc) txt)
+          =.  q.loc  inr.ind
+          ::
+          (push typ)
+        ::
+        ++  lent                                        ::  list entry
+          |=  ord=?(%lord %lunt)
+          ^+  +>
+          =>  ?:(=(ord p.cur) +>.$ (push ord))          ::  push list if new
+          (entr %lime)
+        --
+      --
+    ::
+    ++  parse                                           ::  individual parsers
+      |%
+      ++  look                                          ::  classify line
+        %+  cook  |=(a=(unit trig) a)
+        ;~  pfix  (star ace)
+          %+  here                                      ::  report indent
+            |=([a=pint b=?(~ trig-style)] ?~(b ~ `[q.p.a b]))
+          ;~  pose
+            (cold ~ (just `@`10))                       ::  blank line
+          ::
+            (full (easy [%end %done]))                  ::  end of input
+            (cold [%end %stet] duz)                     ::  == end of markdown
+          ::
+            (cold [%one %rule] ;~(plug hep hep hep))    ::  --- horizontal ruler
+            (cold [%one %fens] ;~(plug tic tic tic))    ::  ``` code fence
+            (cold [%one %expr] mic)                     ::  ;sail expression
+          ::
+            (cold [%new %head] ;~(plug (star hax) ace)) ::  # heading
+            (cold [%new %lint] ;~(plug hep ace))        ::  - line item
+            (cold [%new %lite] ;~(plug lus ace))        ::  + line item
+            (cold [%new %bloc] ;~(plug gar ace))        ::  > block-quote
+          ::
+            (easy [%old %text])                         ::  anything else
+          ==
+        ==
+      ::
+      ::
+      ++  calf                                          ::  cash but for tic tic
+        |*  tem=rule
+        %-  star
+        ;~  pose
+          ;~(pfix bas tem)
+          ;~(less tem prn)
+        ==
+      ++  cash                                          ::  escaped fence
+        |*  tem=rule
+        %-  echo
+        %-  star
+        ;~  pose
+          whit
+          ;~(plug bas tem)
+          ;~(less tem prn)
+        ==
+      ::
+      ++  cool                                          ::  reparse
+        |*  $:  ::  fex: primary parser
+                ::  sab: secondary parser
+                ::
+                fex=rule
+                sab=rule
+            ==
+        |=  [loc=hair txt=tape]
+        ^+  *sab
+        ::
+        ::  vex: fenced span
+        =/  vex=(like tape)  (fex loc txt)
+        ?~  q.vex  vex
+        ::
+        ::  hav: reparse full fenced text
+        =/  hav  ((full sab) [loc p.u.q.vex])
+        ::
+        ::  reparsed error position is always at start
+        ?~  q.hav  [loc ~]
+        ::
+        ::  the complete type with the main product
+        :-  p.vex
+        `[p.u.q.hav q.u.q.vex]
+      ::
+      ::REVIEW surely there is a less hacky "first or after space" solution
+      ++  easy-sol                                      ::  parse start of line
+        |*  a=*
+        |=  b=nail
+        ?:  =(1 q.p.b)  ((easy a) b)
+        (fail b)
+      ::
+      ++  echo                                          ::  hoon literal
+        |*  sab=rule
+        |=  [loc=hair txt=tape]
+        ^-  (like tape)
+        ::
+        ::  vex: result of parsing wide hoon
+        =/  vex  (sab loc txt)
+        ::
+        ::  use result of expression parser
+        ?~  q.vex  vex
+        =-  [p.vex `[- q.u.q.vex]]
+        ::
+        ::  but replace payload with bytes consumed
+        |-  ^-  tape
+        ?:  =(q.q.u.q.vex txt)  ~
+        ?~  txt  ~
+        [i.txt $(txt +.txt)]
+      ::
+      ++  non-empty
+        |*  a=rule
+        |=  tub=nail  ^+  (a)
+        =/  vex  (a tub)
+        ~!  vex
+        ?~  q.vex  vex
+        ?.  =(tub q.u.q.vex)  vex
+        (fail tub)
+      ::
+      ::
+      ++  word                                          ::  tarp parser
+        %+  knee  *(list graf)  |.  ~+
+        %+  cook
+          |=  a=$%(graf [%list (list graf)])
+          ^-  (list graf)
+          ?:(?=(%list -.a) +.a [a ~])
+        ;~  pose
+        ::
+        ::  ordinary word
+        ::
+          %+  stag  %text
+          ;~(plug ;~(pose low hig) (star ;~(pose nud low hig hep)))
+        ::
+        ::  naked \escape
+        ::
+          (stag %text ;~(pfix bas (cook trip ;~(less ace prn))))
+        ::
+        ::  trailing \ to add <br>
+        ::
+          (stag %expr (cold [[%br ~] ~] ;~(plug bas (just '\0a'))))
+        ::
+        ::  *bold literal*
+        ::
+          (stag %bold (ifix [tar tar] (cool (cash tar) werk)))
+        ::
+        ::  _italic literal_
+        ::
+          (stag %talc (ifix [cab cab] (cool (cash cab) werk)))
+        ::
+        ::  "quoted text"
+        ::
+          (stag %quod (ifix [doq doq] (cool (cash doq) werk)))
+        ::
+        ::  `classic markdown quote`
+        ::
+          (stag %code (ifix [tic tic] (calf tic)))
+        ::
+        ::  ++arm, +$arm, +*arm, ++arm:core, ...
+        ::
+          %+  stag  %code
+          ;~  plug
+            lus  ;~(pose lus buc tar)
+            low  (star ;~(pose nud low hep col))
+          ==
+        ::
+        ::  [arbitrary *content*](url)
+        ::
+          %+  stag  %link
+          ;~  (glue (punt whit))
+            (ifix [sel ser] (cool (cash ser) werk))
+            (ifix [pal par] (cash par))
+          ==
+        ::
+        ::  ![alt text](url)
+        ::
+          %+  stag  %mage
+          ;~  pfix  zap
+            ;~  (glue (punt whit))
+              (ifix [sel ser] (cash ser))
+              (ifix [pal par] (cash par))
+            ==
+          ==
+        ::
+        ::  #hoon
+        ::
+          %+  stag  %list
+          ;~  plug
+            (stag %text ;~(pose (cold " " whit) (easy-sol ~)))
+            (stag %code ;~(pfix hax (echo wide)))
+            ;~(simu whit (easy ~))
+          ==
+        ::
+        ::  direct hoon constant
+        ::
+          %+  stag  %list
+          ;~  plug
+            (stag %text ;~(pose (cold " " whit) (easy-sol ~)))
+          ::
+            %+  stag  %code
+            %-  echo
+            ;~  pose
+              ::REVIEW just copy in 0x... parsers directly?
+              ;~(simu ;~(plug (just '0') alp) bisk:so)
+            ::
+              tash:so
+              ;~(pfix dot perd:so)
+              ;~(pfix sig ;~(pose twid:so (easy [%$ %n 0])))
+              ;~(pfix cen ;~(pose sym buc pam bar qut nuck:so))
+            ==
+          ::
+            ;~(simu whit (easy ~))
+          ==
+        ::
+        ::  whitespace
+        ::
+          (stag %text (cold " " whit))
+        ::
+        ::  {interpolated} sail
+        ::
+          (stag %expr inline-embed:(sail |))
+        ::
+        ::  just a byte
+        ::
+          (stag %text (cook trip ;~(less ace prn)))
+        ==
+      ::
+      ++  werk  (cook zing (star word))                 ::  indefinite tarp
+      ::
+      ++  down                                          ::  parse inline tarp
+        %+  knee  *tarp  |.  ~+
+        =-  (cook - werk)
+        ::
+        ::  collect raw tarp into xml tags
+        |=  gaf=(list graf)
+        ^-  tarp
+        =<  main
+        |%
+        ++  main
+          ^-  tarp
+          ?~  gaf  ~
+          ?.  ?=(%text -.i.gaf)
+            (weld (item i.gaf) $(gaf t.gaf))
+          ::
+          ::  fip: accumulate text blocks
+          =/  fip=(list tape)  [p.i.gaf]~
+          |-  ^-  tarp
+          ?~  t.gaf  [;/((zing (flop fip))) ~]
+          ?.  ?=(%text -.i.t.gaf)
+            [;/((zing (flop fip))) ^$(gaf t.gaf)]
+          $(gaf t.gaf, fip :_(fip p.i.t.gaf))
+        ::
+        ++  item
+          |=  nex=graf
+          ^-  tarp  ::CHECK can be tuna:hoot?
+          ?-  -.nex
+            %text  !!  :: handled separately
+            %expr  [p.nex]~
+            %bold  [[%b ~] ^$(gaf p.nex)]~
+            %talc  [[%i ~] ^$(gaf p.nex)]~
+            %code  [[%code ~] ;/(p.nex) ~]~
+            %quod  ::
+                   ::  smart quotes
+                   %=    ^$
+                       gaf
+                     :-  [%text (tufa ~-~201c. ~)]
+                     %+  weld  p.nex
+                     `(list graf)`[%text (tufa ~-~201d. ~)]~
+                   ==
+            %link  [[%a [%href q.nex] ~] ^$(gaf p.nex)]~
+            %mage  [[%img [%src q.nex] ?~(p.nex ~ [%alt p.nex]~)] ~]~
+          ==
+        --
+      ::
+      ++  hrul                                          ::  empty besides fence
+        %+  cold  [[%hr ~] ~]~
+        ;~(plug (star ace) hep hep hep (star hep) (just '\0a'))
+      ::
+      ++  tics
+        ;~(plug tic tic tic (just '\0a'))
+      ::
+      ++  fens
+        |=  col=@u  ~+
+        =/  ind  (stun [(dec col) (dec col)] ace)
+        =/  ind-tics  ;~(plug ind tics)
+        %+  cook  |=(txt=tape `tarp`[[%pre ~] ;/(txt) ~]~)
+        ::
+        ::  leading outdent is ok since container may
+        ::  have already been parsed and consumed
+        %+  ifix  [;~(plug (star ace) tics) ind-tics]
+        %^  stir  ""  |=([a=tape b=tape] "{a}\0a{b}")
+        ;~  pose
+          %+  ifix  [ind (just '\0a')]
+          ;~(less tics (star prn))
+        ::
+          (cold "" ;~(plug (star ace) (just '\0a')))
+        ==
+      ::
+      ++  para                                          ::  paragraph
+        %+  cook
+          |=(a=tarp ?~(a ~ [[%p ~] a]~))
+        ;~(pfix (punt whit) down)
+      ::
+      ++  expr                                          ::  expression
+        =>  (sail &)                                    ::  tall-form
+        %+  ifix  [(star ace) ;~(simu gap (easy))]      ::  look-ahead for gap
+        (cook drop-top top-level)                       ::  list of tags
+        ::
+      ::
+      ++  whit                                          ::  whitespace
+        (cold ' ' (plus ;~(pose (just ' ') (just '\0a'))))
+      ::
+      ++  head                                          ::  parse heading
+        %+  cook
+          |=  [haxes=tape kids=tarp]  ^-  tarp
+          =/  tag  (crip 'h' <(lent haxes)>)            ::  e.g. ### -> %h3
+          =/  id  (contents-to-id kids)
+          [[tag [%id id]~] kids]~
+        ::
+        ;~(pfix (star ace) ;~((glue whit) (stun [1 6] hax) down))
+      ::
+      ++  contents-to-id                                ::  # text into elem id
+        |=  a=(list tuna:hoot)  ^-  tape
+        =;  raw=tape
+          %+  turn  raw
+          |=  @tD
+          ^-  @tD
+          ?:  ?|  &((gte +< 'a') (lte +< 'z'))
+                  &((gte +< '0') (lte +< '9'))
+              ==
+            +<
+          ?:  &((gte +< 'A') (lte +< 'Z'))
+            (add 32 +<)
+          '-'
+        ::
+        ::  collect all text in header tarp
+        |-  ^-  tape
+        ?~  a  ~
+        %+  weld
+          ^-  tape
+          ?-    i.a
+              [[%$ [%$ *] ~] ~]                       ::  text node contents
+            (murn v.i.a.g.i.a |=(a=beer:hoot ?^(a ~ (some a))))
+              [^ *]  $(a c.i.a)                         ::  concatenate children
+              [@ *]  ~                                  ::  ignore interpolation
+          ==
+        $(a t.a)
+      --
+    --
+  ::
+  ++  scad
+    %+  knee  *spec  |.  ~+
+    %-  stew
+    ^.  stet  ^.  limo
+    :~
+      :-  '_'
+        ;~(pfix cab (stag %bccb wide))
+      :-  ','
+        ;~(pfix com (stag %bcmc wide))
+      :-  '$'
+        (stag %like (most col rope))
+      :-  '%'
+        ;~  pose
+          ;~  pfix  cen
+            ;~  pose
+              (stag %leaf (stag %tas (cold %$ buc)))
+              (stag %leaf (stag %f (cold & pam)))
+              (stag %leaf (stag %f (cold | bar)))
+              (stag %leaf (stag %t qut))
+              (stag %leaf (sear |=(a=coin ?:(?=(%$ -.a) (some +.a) ~)) nuck:so))
+            ==
+          ==
+        ==
+      :-  '('
+        %+  cook  |=(spec +<)
+        %+  stag  %make
+        %+  ifix  [pal par]
+        ;~  plug
+          wide
+          ;~(pose ;~(pfix ace (most ace wyde)) (easy ~))
+        ==
+      :-  '['
+        (stag %bccl (ifix [sel ser] (most ace wyde)))
+      :-  '*'
+        (cold [%base %noun] tar)
+      :-  '/'
+        ;~(pfix fas (stag %loop ;~(pose (cold %$ buc) sym)))
+      :-  '@'
+        ;~(pfix pat (stag %base (stag %atom mota)))
+      :-  '?'
+        ;~  pose
+          %+  stag  %bcwt
+          ;~(pfix wut (ifix [pal par] (most ace wyde)))
+        ::
+          (cold [%base %flag] wut)
+        ==
+      :-  '~'
+        (cold [%base %null] sig)
+      :-  '!'
+        (cold [%base %void] ;~(plug zap zap))
+      :-  '^'
+        ;~  pose
+          (stag %like (most col rope))
+          (cold [%base %cell] ket)
+        ==
+      :-  '='
+        ;~  pfix  tis
+          %+  sear
+            |=  [=(unit term) =spec]
+            %+  bind
+              ~(autoname ax spec)
+            |=  =term
+            =*  name  ?~(unit term (cat 3 u.unit (cat 3 '-' term)))
+            [%bcts name spec]
+          ;~  pose
+            ;~(plug (stag ~ ;~(sfix sym tis)) wyde)
+            (stag ~ wyde)
+          ==
+        ==
+      :-  ['a' 'z']
+        ;~  pose
+          (stag %bcts ;~(plug sym ;~(pfix tis wyde)))
+          (stag %like (most col rope))
+        ==
+    ==
+  ::
+  ++  scat
+    %+  knee  *hoon  |.  ~+
+    %-  stew
+    ^.  stet  ^.  limo
+    :~
+      :-  ','
+        ;~  pose
+          (stag %ktcl ;~(pfix com wyde))
+          (stag %wing rope)
+        ==
+      :-  '!'
+        ;~  pose
+          (stag %wtzp ;~(pfix zap wide))
+          (stag %zpzp (cold ~ ;~(plug zap zap)))
+        ==
+      :-  '_'
+        ;~(pfix cab (stag %ktcl (stag %bccb wide)))
+      :-  '$'
+        ;~  pose
+          ;~  pfix  buc
+            ;~  pose
+              ::  XX: these are all obsolete in hoon 142
+              ::
+              (stag %leaf (stag %tas (cold %$ buc)))
+              (stag %leaf (stag %t qut))
+              (stag %leaf (sear |=(a=coin ?:(?=(%$ -.a) (some +.a) ~)) nuck:so))
+            ==
+          ==
+          rump
+        ==
+      :-  '%'
+        ;~  pfix  cen
+          ;~  pose
+            (stag %clsg (sear |~([a=@ud b=tyke] (posh ~ ~ a b)) porc))
+            (stag %rock (stag %tas (cold %$ buc)))
+            (stag %rock (stag %f (cold & pam)))
+            (stag %rock (stag %f (cold | bar)))
+            (stag %rock (stag %t qut))
+            (cook (jock &) nuck:so)
+            (stag %clsg (sear |=(a=(list) (posh ~ ~ (lent a) ~)) (star cen)))
+          ==
+        ==
+      :-  '&'
+        ;~  pose
+          (cook |=(a=wing [%cnts a ~]) rope)
+          (stag %wtpm ;~(pfix pam (ifix [pal par] (most ace wide))))
+          ;~(plug (stag %rock (stag %f (cold & pam))) wede)
+          (stag %sand (stag %f (cold & pam)))
+        ==
+      :-  '\''
+        (stag %sand (stag %t qut))
+      :-  '('
+        (stag %cncl (ifix [pal par] (most ace wide)))
+      :-  '*'
+        ;~  pose
+          (stag %kttr ;~(pfix tar wyde))
+          (cold [%base %noun] tar)
+        ==
+      :-  '@'
+        ;~(pfix pat (stag %base (stag %atom mota)))
+      :-  '+'
+        ;~  pose
+          (stag %dtls ;~(pfix lus (ifix [pal par] wide)))
+        ::
+          %+  cook
+            |=  a=(list (list woof))
+            :-  %mcfs
+            [%knit |-(^-((list woof) ?~(a ~ (weld i.a $(a t.a)))))]
+          (most dog ;~(pfix lus soil))
+        ::
+          (cook |=(a=wing [%cnts a ~]) rope)
+        ==
+      :-  '-'
+        ;~  pose
+          (stag %sand tash:so)
+        ::
+          %+  cook
+            |=  a=(list (list woof))
+            [%clsg (phax a)]
+          (most dog ;~(pfix hep soil))
+        ::
+          (cook |=(a=wing [%cnts a ~]) rope)
+        ==
+      :-  '.'
+        ;~  pose
+          (cook (jock |) ;~(pfix dot perd:so))
+          (cook |=(a=wing [%cnts a ~]) rope)
+        ==
+      :-  ['0' '9']
+        %+  cook
+          |=  [a=dime b=(unit hoon)]
+          ?~(b [%sand a] [[%rock a] u.b])
+        ;~(plug bisk:so (punt wede))
+      :-  ':'
+        ;~  pfix  col
+          ;~  pose
+            (stag %mccl (ifix [pal par] (most ace wide)))
+            ;~(pfix fas (stag %mcfs wide))
+          ==
+        ==
+      :-  '='
+        ;~  pfix  tis
+          ;~  pose
+            (stag %dtts (ifix [pal par] ;~(glam wide wide)))
+          ::
+            %+  sear
+              ::  mainly used for +skin formation
+              ::
+              |=  =spec
+              ^-  (unit hoon)
+              %+  bind  ~(autoname ax spec)
+              |=(=term `hoon`[%ktts term %kttr spec])
+            wyde
+          ==
+        ==
+      :-  '?'
+        ;~  pose
+          %+  stag  %ktcl
+          (stag %bcwt ;~(pfix wut (ifix [pal par] (most ace wyde))))
+        ::
+          (cold [%base %flag] wut)
+        ==
+      :-  '['
+        rupl
+      :-  '^'
+        ;~  pose
+          (stag %wing rope)
+          (cold [%base %cell] ket)
+        ==
+      :-  '`'
+        ;~  pfix  tic
+          ;~  pose
+            %+  cook
+              |=([a=@ta b=hoon] [%ktls [%sand a 0] [%ktls [%sand %$ 0] b]])
+            ;~(pfix pat ;~(plug mota ;~(pfix tic wide)))
+            ;~  pfix  tar
+              (stag %kthp (stag [%base %noun] ;~(pfix tic wide)))
+            ==
+            (stag %kthp ;~(plug wyde ;~(pfix tic wide)))
+            (stag %ktls ;~(pfix lus ;~(plug wide ;~(pfix tic wide))))
+            (cook |=(a=hoon [[%rock %n ~] a]) wide)
+          ==
+        ==
+      :-  '"'
+        %+  cook
+          |=  a=(list (list woof))
+          [%knit |-(^-((list woof) ?~(a ~ (weld i.a $(a t.a)))))]
+        (most dog soil)
+      :-  ['a' 'z']
+        rump
+      :-  '|'
+        ;~  pose
+          (cook |=(a=wing [%cnts a ~]) rope)
+          (stag %wtbr ;~(pfix bar (ifix [pal par] (most ace wide))))
+          ;~(plug (stag %rock (stag %f (cold | bar))) wede)
+          (stag %sand (stag %f (cold | bar)))
+        ==
+      :-  '~'
+        ;~  pose
+          rupl
+        ::
+          ;~  pfix  sig
+            ;~  pose
+              (stag %clsg (ifix [sel ser] (most ace wide)))
+            ::
+              %+  stag  %cnsg
+              %+  ifix
+                [pal par]
+              ;~(glam rope wide (most ace wide))
+            ::
+              (cook (jock |) twid:so)
+              (stag [%bust %null] wede)
+              (easy [%bust %null])
+            ==
+          ==
+        ==
+      :-  '/'
+        rood
+      :-  '<'
+        (ifix [gal gar] (stag %tell (most ace wide)))
+      :-  '>'
+        (ifix [gar gal] (stag %yell (most ace wide)))
+      :-  '#'
+        ;~(pfix hax reed)
+    ==
+  ++  soil
+    ;~  pose
+      ;~  less  (jest '"""')
+        %+  ifix  [doq doq]
+        %-  star
+        ;~  pose
+          ;~(pfix bas ;~(pose bas doq kel bix:ab))
+          ;~(less doq bas kel prn)
+          (stag ~ sump)
+        ==
+      ==
+    ::
+      %-  iny  %+  ifix
+        [(jest '"""\0a') (jest '\0a"""')]
+      %-  star
+      ;~  pose
+        ;~(pfix bas ;~(pose bas kel bix:ab))
+        ;~(less bas kel prn)
+        ;~(less (jest '\0a"""') (just `@`10))
+        (stag ~ sump)
+      ==
+    ==
+  ++  sump  (ifix [kel ker] (stag %cltr (most ace wide)))
+  ++  norm                                              ::  rune regular form
+    |=  tol=?
+    |%
+    ++  structure
+      %-  stew
+      ^.  stet  ^.  limo
+      :~  :-  '$'
+            ;~  pfix  buc
+              %-  stew
+              ^.  stet  ^.  limo
+              :~  [':' (rune col %bccl exqs)]
+                  ['%' (rune cen %bccn exqs)]
+                  ['<' (rune gal %bcgl exqb)]
+                  ['>' (rune gar %bcgr exqb)]
+                  ['^' (rune ket %bckt exqb)]
+                  ['~' (rune sig %bcsg exqd)]
+                  ['|' (rune bar %bcbr exqc)]
+                  ['&' (rune pam %bcpm exqc)]
+                  ['@' (rune pat %bcpt exqb)]
+                  ['_' (rune cab %bccb expa)]
+                  ['-' (rune hep %bchp exqb)]
+                  ['=' (rune tis %bcts exqg)]
+                  ['?' (rune wut %bcwt exqs)]
+                  [';' (rune mic %bcmc expa)]
+                  ['+' (rune lus %bcls exqg)]
+              ==
+            ==
+        :-  '%'
+          ;~  pfix  cen
+            %-  stew
+            ^.  stet  ^.  limo
+            :~  :-  '^'
+                %+  cook
+                  |=  [%cnkt a=hoon b=spec c=spec d=spec]
+                  [%make a b c d ~]
+                (rune ket %cnkt exqy)
+            ::
+                :-  '+'
+                %+  cook
+                  |=  [%cnls a=hoon b=spec c=spec]
+                  [%make a b c ~]
+                (rune lus %cnls exqx)
+            ::
+                :-  '-'
+                %+  cook
+                  |=  [%cnhp a=hoon b=spec]
+                  [%make a b ~]
+                (rune hep %cnhp exqd)
+            ::
+                :-  '.'
+                %+  cook
+                  |=  [%cndt a=spec b=hoon]
+                  [%make b a ~]
+                (rune dot %cndt exqc)
+            ::
+                :-  ':'
+                %+  cook
+                  |=  [%cncl a=hoon b=(list spec)]
+                  [%make a b]
+                (rune col %cncl exqz)
+            ==
+          ==
+        :-  '#'
+          ;~  pfix  hax  fas
+            %+  stag  %bccl
+            %+  cook
+              |=  [[i=spec t=(list spec)] e=spec]
+              [i (snoc t e)]
+            ;~  plug
+              %+  most  ;~(less ;~(plug fas tar) fas)
+              %-  stew
+              ^.  stet  ^.  limo
+              :~  :-  ['a' 'z']
+                  ;~  pose
+                    ::  /name=@aura
+                    ::
+                    %+  cook
+                      |=  [=term =aura]
+                      ^-  spec
+                      :+  %bccl
+                        [%leaf %tas aura]
+                      :_  ~
+                      :+  %bcts  term
+                      ?+  aura  [%base %atom aura]
+                        %f  [%base %flag]
+                        %n  [%base %null]
+                      ==
+                    ;~(plug sym ;~(pfix tis pat mota))
+                  ::
+                    ::  /constant
+                    ::
+                    (stag %leaf (stag %tas ;~(pose sym (cold %$ buc))))
+                  ==
+                ::
+                  ::  /@aura
+                  ::
+                  :-  '@'
+                  %+  cook
+                    |=  =aura
+                    ^-  spec
+                    :+  %bccl
+                      [%leaf %tas aura]
+                    [%base %atom aura]~
+                  ;~(pfix pat mota)
+                ::
+                  ::  /?
+                  ::
+                  :-  '?'
+                  (cold [%bccl [%leaf %tas %f] [%base %flag] ~] wut)
+                ::
+                  ::  /~
+                  ::
+                  :-  '~'
+                  (cold [%bccl [%leaf %tas %n] [%base %null] ~] sig)
+              ==
+            ::
+              ::  open-ended or fixed-length
+              ::
+              ;~  pose
+                (cold [%base %noun] ;~(plug fas tar))
+                (easy %base %null)
+              ==
+            ==
+          ==
+      ==
+    ++  expression
+      %-  stew
+      ^.  stet  ^.  limo
+      :~  :-  '|'
+            ;~  pfix  bar
+              %-  stew
+              ^.  stet  ^.  limo
+              :~  ['_' (rune cab %brcb exqr)]
+                  ['%' (runo cen %brcn ~ expe)]
+                  ['@' (runo pat %brpt ~ expe)]
+                  [':' (rune col %brcl expb)]
+                  ['.' (rune dot %brdt expa)]
+                  ['-' (rune hep %brhp expa)]
+                  ['^' (rune ket %brkt expr)]
+                  ['~' (rune sig %brsg exqc)]
+                  ['*' (rune tar %brtr exqc)]
+                  ['=' (rune tis %brts exqc)]
+                  ['?' (rune wut %brwt expa)]
+                  ['$' (rune buc %brbc exqe)]
+              ==
+            ==
+          :-  '$'
+            ;~  pfix  buc
+              %-  stew
+              ^.  stet  ^.  limo
+              :~  ['@' (stag %ktcl (rune pat %bcpt exqb))]
+                  ['_' (stag %ktcl (rune cab %bccb expa))]
+                  [':' (stag %ktcl (rune col %bccl exqs))]
+                  ['%' (stag %ktcl (rune cen %bccn exqs))]
+                  ['<' (stag %ktcl (rune gal %bcgl exqb))]
+                  ['>' (stag %ktcl (rune gar %bcgr exqb))]
+                  ['|' (stag %ktcl (rune bar %bcbr exqc))]
+                  ['&' (stag %ktcl (rune pam %bcpm exqc))]
+                  ['^' (stag %ktcl (rune ket %bckt exqb))]
+                  ['~' (stag %ktcl (rune sig %bcsg exqd))]
+                  ['-' (stag %ktcl (rune hep %bchp exqb))]
+                  ['=' (stag %ktcl (rune tis %bcts exqg))]
+                  ['?' (stag %ktcl (rune wut %bcwt exqs))]
+                  ['+' (stag %ktcl (rune lus %bcls exqg))]
+                  ['.' (rune dot %kttr exqa)]
+                  [',' (rune com %ktcl exqa)]
+              ==
+            ==
+          :-  '%'
+            ;~  pfix  cen
+              %-  stew
+              ^.  stet  ^.  limo
+              :~  ['_' (rune cab %cncb exph)]
+                  ['.' (rune dot %cndt expb)]
+                  ['^' (rune ket %cnkt expd)]
+                  ['+' (rune lus %cnls expc)]
+                  ['-' (rune hep %cnhp expb)]
+                  [':' (rune col %cncl expi)]
+                  ['~' (rune sig %cnsg expn)]
+                  ['*' (rune tar %cntr expm)]
+                  ['=' (rune tis %cnts exph)]
+              ==
+            ==
+          :-  ':'
+            ;~  pfix  col
+              %-  stew
+              ^.  stet  ^.  limo
+              :~  ['_' (rune cab %clcb expb)]
+                  ['^' (rune ket %clkt expd)]
+                  ['+' (rune lus %clls expc)]
+                  ['-' (rune hep %clhp expb)]
+                  ['~' (rune sig %clsg exps)]
+                  ['*' (rune tar %cltr exps)]
+              ==
+            ==
+          :-  '.'
+            ;~  pfix  dot
+              %-  stew
+              ^.  stet  ^.  limo
+              :~  ['+' (rune lus %dtls expa)]
+                  ['*' (rune tar %dttr expb)]
+                  ['=' (rune tis %dtts expb)]
+                  ['?' (rune wut %dtwt expa)]
+                  ['^' (rune ket %dtkt exqn)]
+              ==
+            ==
+          :-  '^'
+            ;~  pfix  ket
+              %-  stew
+              ^.  stet  ^.  limo
+              :~  ['|' (rune bar %ktbr expa)]
+                  ['.' (rune dot %ktdt expb)]
+                  ['-' (rune hep %kthp exqc)]
+                  ['+' (rune lus %ktls expb)]
+                  ['&' (rune pam %ktpm expa)]
+                  ['~' (rune sig %ktsg expa)]
+                  ['=' (rune tis %ktts expj)]
+                  ['?' (rune wut %ktwt expa)]
+                  ['*' (rune tar %kttr exqa)]
+                  [':' (rune col %ktcl exqa)]
+              ==
+            ==
+          :-  '~'
+            ;~  pfix  sig
+              %-  stew
+              ^.  stet  ^.  limo
+              :~  ['|' (rune bar %sgbr expb)]
+                  ['$' (rune buc %sgbc expf)]
+                  ['_' (rune cab %sgcb expb)]
+                  ['%' (rune cen %sgcn hind)]
+                  ['/' (rune fas %sgfs hine)]
+                  ['<' (rune gal %sggl hinb)]
+                  ['>' (rune gar %sggr hinb)]
+                  ['+' (rune lus %sgls hinc)]
+                  ['&' (rune pam %sgpm hinf)]
+                  ['?' (rune wut %sgwt hing)]
+                  ['=' (rune tis %sgts expb)]
+                  ['!' (rune zap %sgzp expb)]
+              ==
+            ==
+          :-  ';'
+            ;~  pfix  mic
+              %-  stew
+              ^.  stet  ^.  limo
+              :~  [':' (rune col %mccl expi)]
+                  ['/' (rune fas %mcfs expa)]
+                  ['<' (rune gal %mcgl expz)]
+                  ['~' (rune sig %mcsg expi)]
+                  [';' (rune mic %mcmc exqc)]
+              ==
+            ==
+          :-  '='
+            ;~  pfix  tis
+              %-  stew
+              ^.  stet  ^.  limo
+              :~  ['|' (rune bar %tsbr exqc)]
+                  ['.' (rune dot %tsdt expq)]
+                  ['?' (rune wut %tswt expw)]
+                  ['^' (rune ket %tskt expt)]
+                  [':' (rune col %tscl expp)]
+                  ['/' (rune fas %tsfs expo)]
+                  [';' (rune mic %tsmc expo)]
+                  ['<' (rune gal %tsgl expb)]
+                  ['>' (rune gar %tsgr expb)]
+                  ['-' (rune hep %tshp expb)]
+                  ['*' (rune tar %tstr expg)]
+                  [',' (rune com %tscm expb)]
+                  ['+' (rune lus %tsls expb)]
+                  ['~' (rune sig %tssg expi)]
+              ==
+            ==
+          :-  '?'
+            ;~  pfix  wut
+              %-  stew
+              ^.  stet  ^.  limo
+              :~  ['|' (rune bar %wtbr exps)]
+                  [':' (rune col %wtcl expc)]
+                  ['.' (rune dot %wtdt expc)]
+                  ['<' (rune gal %wtgl expb)]
+                  ['>' (rune gar %wtgr expb)]
+                  ['-' ;~(pfix hep (toad txhp))]
+                  ['^' ;~(pfix ket (toad tkkt))]
+                  ['=' ;~(pfix tis (toad txts))]
+                  ['#' ;~(pfix hax (toad txhx))]
+                  ['+' ;~(pfix lus (toad txls))]
+                  ['&' (rune pam %wtpm exps)]
+                  ['@' ;~(pfix pat (toad tkvt))]
+                  ['~' ;~(pfix sig (toad tksg))]
+                  ['!' (rune zap %wtzp expa)]
+              ==
+            ==
+          :-  '!'
+            ;~  pfix  zap
+              %-  stew
+              ^.  stet  ^.  limo
+              :~  [':' ;~(pfix col (toad expy))]
+                  ['.' ;~(pfix dot (toad |.(loaf(bug |))))]
+                  [',' (rune com %zpcm expb)]
+                  [';' (rune mic %zpmc expb)]
+                  ['>' (rune gar %zpgr expa)]
+                  ['<' (rune gal %zpgl exqc)]
+                  ['@' (rune pat %zppt expx)]
+                  ['=' (rune tis %zpts expa)]
+                  ['?' (rune wut %zpwt hinh)]
+              ==
+            ==
+      ==
+    ::
+    ++  boog  !:
+      %+  knee  [p=*whit q=*term r=*help s=*hoon]
+      |.(~+((scye ;~(pose bola boba))))
+    ++  bola                                           ::  ++  arms
+      %+  knee  [q=*term r=*help s=*hoon]  |.  ~+
+      %+  cook
+        |=  [q=term r=whiz s=hoon]
+        ?:  =(r *whiz)
+          [q *help s]
+        [q [[%funk q]~ [r]~] s]
+      ;~  pfix  (jest '++')
+        ;~  plug
+          ;~(pfix gap ;~(pose (cold %$ buc) sym))
+          apse:docs
+          ;~(pfix jump loaf)
+        ==
+      ==
+    ::TODO consider special casing $%
+    ++  boba                                           ::  +$  arms
+      %+  knee  [q=*term r=*help s=*hoon]  |.  ~+
+      %+  cook
+        |=  [q=term r=whiz s=spec]
+        ?:  =(r *whiz)
+          [q *help [%ktcl %name q s]]
+        [q [[%plan q]~ [r]~] [%ktcl %name q s]]
+      ;~  pfix  (jest '+$')
+        ;~  plug
+          ;~(pfix gap sym)
+          apse:docs
+          ;~(pfix jump loan)
+        ==
+      ==
+   ::
+   ::  parses a or [a b c] or a  b  c  ==
+   ++  lynx
+      =/  wid  (ifix [sel ser] (most ace sym))
+      =/  tal
+        ;~  sfix
+          (most gap sym)
+          ;~(plug gap duz)
+        ==
+      =/  one
+        %-  cook  :_  sym
+        |=  a=term
+        `(list term)`~[a]
+      %-  cook
+      :_  ;~(pose (runq wid tal) one)
+      ::  lestify
+      |=  a=(list term)
+      ?~(a !! a)
+    ::
+    ++  whap  !:                                        ::  chapter
+      %+  cook
+        |=  a=(list (qual whit term help hoon))
+        ::  separate $helps into their own list to be passed to +glow
+        =/  [duds=(list help) nude=(list (pair term hoon))]
+          %+  roll  a
+          |=  $:  $=  bog
+                  (qual whit term help hoon)
+                ::
+                  $=  gob
+                  [duds=(list help) nude=(list (pair term hoon))]
+              ==
+          =/  [unt=(list help) tag=(list help)]
+            %+  skid  ~(tap by bat.p.bog)  |=(=help =(~ cuff.help))
+          :-  ?:  =(*help r.bog)
+                (weld tag duds.gob)
+              [r.bog (weld tag duds.gob)]
+          |-
+          ?~  unt  [[q.bog s.bog] nude.gob]
+          =.  s.bog  [%note help/i.unt s.bog]
+          $(unt t.unt)
+        ::
+        %+  glow  duds
+        |-  ^-  (map term hoon)
+        ?~  nude  ~
+        =+  $(nude t.nude)
+        %+  ~(put by -)
+          p.i.nude
+        ?:  (~(has by -) p.i.nude)
+          [%eror (weld "duplicate arm: +" (trip p.i.nude))]
+        q.i.nude
+      ::
+      (most mush boog)
+    ::
+    ::  +glow: moves batch comments to the correct arm
+    ++  glow
+      |=  [duds=(list help) nude=(map term hoon)]
+      ^-  (map term hoon)
+      |-
+      ?~  duds  nude
+      ::  if there is no link, its not part of a batch comment
+      ?~  cuff.i.duds
+        ::  this shouldn't happen yet until we look for cuffs of length >1
+        ::  but we need to prove that cuff is nonempty anyways
+        $(duds t.duds)
+      ::
+      ::TODO: look past the first link. this probably requires
+      ::a major rethink on how batch comments work
+      =/  nom=(unit term)
+        ?+    i.cuff.i.duds  ~
+        ::  we only support ++ and +$ batch comments right now
+        ::
+            ?([%funk *] [%plan *])
+          `p.i.cuff.i.duds
+        ==
+      %=  $
+        duds  t.duds
+        nude  ?~  nom  nude
+              ?.  (~(has by nude) u.nom)
+                ::  ~>  %slog.[0 leaf+"glow: unmatched link"]
+                nude
+              (~(jab by nude) u.nom |=(a=hoon [%note help+i.duds a]))
+      ==
+    ::
+    ++  whip                                            ::  chapter declare
+      %+  cook
+        |=  [[a=whit b=term c=whiz] d=(map term hoon)]
+        ^-  [whit (pair term (map term hoon))]
+        ?.  =(*whit a)
+          [a b d]
+        ?:  =(*whiz c)
+          [*whit b d]
+        [%*(. *whit bat (malt [[%chat b]~ [c]~]~)) b d]
+      ;~(plug (seam ;~(pfix (jest '+|') gap cen sym)) whap)
+    ::
+    ++  wasp                                            ::  $brcb aliases
+      ;~  pose
+        %+  ifix
+          [;~(plug lus tar muck) muck]
+        (most muck ;~(gunk sym loll))
+      ::
+        (easy ~)
+      ==
+    ::
+    ++  wisp  !:                                        ::  core tail
+      ?.  tol  fail
+      %+  cook
+        |=  a=(list [wit=whit wap=(pair term (map term hoon))])
+        ^-  (map term tome)
+        =<  p
+        |-  ^-  (pair (map term tome) (map term hoon))
+        ?~  a  [~ ~]
+        =/  mor  $(a t.a)
+        =.  q.wap.i.a
+          %-  ~(urn by q.wap.i.a)
+          |=  b=(pair term hoon)  ^+  +.b
+          ::  tests for duplicate arms between two chapters
+          ?.  (~(has by q.mor) p.b)  +.b
+          [%eror (weld "duplicate arm: +" (trip p.b))]
+        :_  (~(uni by q.mor) q.wap.i.a)
+        %+  ~(put by p.mor)
+          p.wap.i.a
+        :-  %-  ~(get by bat.wit.i.a)
+            ?:  (~(has by bat.wit.i.a) [%chat p.wap.i.a]~)
+              [%chat p.wap.i.a]~
+            ~
+        ?.  (~(has by p.mor) p.wap.i.a)
+          q.wap.i.a
+        [[%$ [%eror (weld "duplicate chapter: |" (trip p.wap.i.a))]] ~ ~]
+      ::
+      ::TODO: allow cores with unnamed chapter as well as named chapters?
+      ;~  pose
+        dun
+        ;~  sfix
+          ;~  pose
+            (most mush whip)
+            ;~(plug (stag *whit (stag %$ whap)) (easy ~))
+          ==
+          gap
+          dun
+        ==
+      ==
+    ::
+    ::TODO: check parser performance
+    ++  toad                                            ::  untrap parser expr
+      |*  har=_expa
+      =+  dur=(ifix [pal par] $:har(tol |))
+      ?.  tol
+        dur
+      ;~(pose ;~(pfix jump $:har(tol &)) ;~(pfix gap $:har(tol &)) dur)
+    ::
+    ++  rune                                            ::  build rune
+      |*  [dif=rule tuq=* har=_expa]
+      ;~(pfix dif (stag tuq (toad har)))
+    ::
+    ++  runo                                            ::  rune plus
+      |*  [dif=rule hil=* tuq=* har=_expa]
+      ;~(pfix dif (stag hil (stag tuq (toad har))))
+    ::
+    ++  runq                                            ::  wide or tall if tol
+      |*  [wid=rule tal=rule]                           ::  else wide
+      ?.  tol
+        wid
+      ;~(pose wid tal)
+    ::
+    ++  butt  |*  zor=rule                              ::  closing == if tall
+              ?:(tol ;~(sfix zor ;~(plug gap duz)) zor)
+    ++  ulva  |*  zor=rule                              ::  closing -- and tall
+              ?.(tol fail ;~(sfix zor ;~(plug gap dun)))
+    ++  glop  ~+((glue mash))                           ::  separated by space
+    ++  gunk  ~+((glue muck))                           ::  separated list
+    ++  goop  ~+((glue mush))                           ::  separator list & docs
+    ++  hank  (most mush loaf)                          ::  gapped hoons
+    ++  hunk  (most mush loan)                          ::  gapped specs
+    ++  jump  ;~(pose leap:docs gap)                    ::  gap before docs
+    ++  loaf  ?:(tol tall wide)                         ::  hoon
+    ++  loll  ?:(tol tall(doc |) wide(doc |))           ::  hoon without docs
+    ++  loan  ?:(tol till wyde)                         ::  spec
+    ++  lore  (sear |=(=hoon ~(flay ap hoon)) loaf)     ::  skin
+    ++  lomp  ;~(plug sym (punt ;~(pfix tis wyde)))     ::  typeable name
+    ++  mash  ?:(tol gap ;~(plug com ace))              ::  list separator
+    ++  muss  ?:(tol jump ;~(plug com ace))             ::  list w/ doccords
+    ++  muck  ?:(tol gap ace)                           ::  general separator
+    ++  mush  ?:(tol jump ace)                          ::  separator w/ docs
+    ++  teak  %+  knee  *tiki  |.  ~+                   ::  wing or hoon
+              =+  ^=  gub
+                  |=  [a=term b=$%([%& p=wing] [%| p=hoon])]
+                  ^-  tiki
+                  ?-(-.b %& [%& [~ a] p.b], %| [%| [~ a] p.b])
+              =+  ^=  wyp
+                  ;~  pose
+                     %+  cook  gub
+                     ;~  plug
+                       sym
+                       ;~(pfix tis ;~(pose (stag %& rope) (stag %| wide)))
+                     ==
+                  ::
+                     (stag %& (stag ~ rope))
+                     (stag %| (stag ~ wide))
+                  ==
+              ?.  tol  wyp
+              ;~  pose
+                wyp
+              ::
+                ;~  pfix
+                  ;~(plug ket tis gap)
+                  %+  cook  gub
+                  ;~  plug
+                    sym
+                    ;~(pfix gap ;~(pose (stag %& rope) (stag %| tall)))
+                  ==
+                ==
+              ::
+                (stag %| (stag ~ tall))
+              ==
+    ++  rack  (most muss ;~(goop loaf loaf))            ::  list [hoon hoon]
+    ++  ruck  (most muss ;~(goop loan loaf))            ::  list [spec hoon]
+    ++  rick  (most mash ;~(goop rope loaf))            ::  list [wing hoon]
+    ::  hoon contents
+    ::
+    ++  expa  |.(loaf)                                  ::  one hoon
+    ++  expb  |.(;~(goop loaf loaf))                    ::  two hoons
+    ++  expc  |.(;~(goop loaf loaf loaf))               ::  three hoons
+    ++  expd  |.(;~(goop loaf loaf loaf loaf))          ::  four hoons
+    ++  expe  |.(wisp)                                  ::  core tail
+    ++  expf  |.(;~(goop ;~(pfix cen sym) loaf))        ::  %term and hoon
+    ++  expg  |.(;~(gunk lomp loll loaf))               ::  term/spec, two hoons
+    ++  exph  |.((butt ;~(gunk rope rick)))             ::  wing, [wing hoon]s
+    ++  expi  |.((butt ;~(goop loaf hank)))             ::  one or more hoons
+    ++  expj  |.(;~(goop lore loaf))                    ::  skin and hoon
+   :: ++  expk  |.(;~(gunk loaf ;~(plug loaf (easy ~))))::  list of two hoons
+   :: ++  expl  |.(;~(gunk sym loaf loaf))              ::  term, two hoons
+    ++  expm  |.((butt ;~(gunk rope loaf rick)))        ::  several [spec hoon]s
+    ++  expn  |.  ;~  gunk  rope  loaf                  ::  wing, hoon,
+                    ;~(plug loaf (easy ~))              ::  list of one hoon
+                  ==                                    ::
+    ++  expo  |.(;~(goop wise loaf loaf))               ::  =;
+    ++  expp  |.(;~(goop (butt rick) loaf))             ::  [wing hoon]s, hoon
+    ++  expq  |.(;~(goop rope loaf loaf))               ::  wing and two hoons
+    ++  expr  |.(;~(goop loaf wisp))                    ::  hoon and core tail
+    ++  exps  |.((butt hank))                           ::  closed gapped hoons
+    ++  expt  |.(;~(gunk wise rope loaf loaf))          ::  =^
+    ++  expu  |.(;~(gunk rope loaf (butt hank)))        ::  wing, hoon, hoons
+   :: ++  expv  |.((butt rick))                         ::  just changes
+    ++  expw  |.(;~(goop rope loaf loaf loaf))          ::  wing and three hoons
+    ++  expx  |.(;~(goop ropa loaf loaf))               ::  wings and two hoons
+    ++  expy  |.(loaf(bug &))                           ::  hoon with tracing
+    ++  expz  |.(;~(goop loan loaf loaf loaf))          ::  spec and three hoons
+    ::  spec contents
+    ::
+    ++  exqa  |.(loan)                                  ::  one spec
+    ++  exqb  |.(;~(goop loan loan))                    ::  two specs
+    ++  exqc  |.(;~(goop loan loaf))                    ::  spec then hoon
+    ++  exqd  |.(;~(goop loaf loan))                    ::  hoon then spec
+    ++  exqe  |.(;~(goop lynx loan))                    ::  list of names then spec
+    ++  exqs  |.((butt hunk))                           ::  closed gapped specs
+    ++  exqg  |.(;~(goop sym loan))                     ::  term and spec
+    ::++  exqk  |.(;~(goop loaf ;~(plug loan (easy ~))))::  hoon with one spec
+    ++  exqn  |.(;~(gunk loan (stag %cltr (butt hank))))::  autoconsed hoons
+    ++  exqr  |.(;~(gunk loan ;~(plug wasp wisp)))      ::  spec/aliases?/tail
+    ::++  exqw  |.(;~(goop loaf loan))                  ::  hoon and spec
+    ++  exqx  |.(;~(goop loaf loan loan))               ::  hoon, two specs
+    ++  exqy  |.(;~(goop loaf loan loan loan))          ::  hoon, three specs
+    ++  exqz  |.(;~(goop loaf (butt hunk)))             ::  hoon, n specs
+    ::
+    ::    tiki expansion for %wt runes
+    ::
+    ++  txhp  |.  %+  cook  |=  [a=tiki b=(list (pair spec hoon))]
+                            (~(wthp ah a) b)
+                  (butt ;~(gunk teak ruck))
+    ++  tkkt  |.  %+  cook  |=  [a=tiki b=hoon c=hoon]
+                            (~(wtkt ah a) b c)
+                  ;~(gunk teak loaf loaf)
+    ++  txls  |.  %+  cook  |=  [a=tiki b=hoon c=(list (pair spec hoon))]
+                            (~(wtls ah a) b c)
+                  (butt ;~(gunk teak loaf ruck))
+    ++  tkvt  |.  %+  cook  |=  [a=tiki b=hoon c=hoon]
+                            (~(wtpt ah a) b c)
+                  ;~(gunk teak loaf loaf)
+    ++  tksg  |.  %+  cook  |=  [a=tiki b=hoon c=hoon]
+                            (~(wtsg ah a) b c)
+                  ;~(gunk teak loaf loaf)
+    ++  txts  |.  %+  cook  |=  [a=spec b=tiki]
+                            (~(wtts ah b) a)
+                  ;~(gunk loan teak)
+    ++  txhx  |.  %+  cook  |=  [a=skin b=tiki]
+                            (~(wthx ah b) a)
+                  ;~(gunk lore teak)
+    ::
+    ::  hint syntax
+    ::
+    ++  hinb  |.(;~(goop bont loaf))                    ::  hint and hoon
+    ++  hinc  |.                                        ::  optional =en, hoon
+              ;~(pose ;~(goop bony loaf) (stag ~ loaf)) ::
+    ++  hind  |.(;~(gunk bonk loaf ;~(goop bonz loaf))) ::  jet hoon "bon"s hoon
+    ++  hine  |.(;~(goop bonk loaf))                    ::  jet-hint and hoon
+    ++  hinf  |.                                        ::  0-3 >s, two hoons
+      ;~  pose
+        ;~(goop (cook lent (stun [1 3] gar)) loaf loaf)
+        (stag 0 ;~(goop loaf loaf))
+      ==
+    ++  hing  |.                                        ::  0-3 >s, three hoons
+      ;~  pose
+        ;~(goop (cook lent (stun [1 3] gar)) loaf loaf loaf)
+        (stag 0 ;~(goop loaf loaf loaf))
+      ==
+    ++  bonk                                            ::  jet signature
+      ;~  pfix  cen
+        ;~  pose
+          ;~(plug sym ;~(pfix col ;~(plug sym ;~(pfix dot ;~(pfix dot dem)))))
+          ;~(plug sym ;~(pfix col ;~(plug sym ;~(pfix dot dem))))
+          ;~(plug sym ;~(pfix dot dem))
+          sym
+        ==
+      ==
+    ++  hinh  |.                                        ::  1/2 numbers, hoon
+        ;~  goop
+          ;~  pose
+            dem
+            (ifix [sel ser] ;~(plug dem ;~(pfix ace dem)))
+          ==
+          loaf
+        ==
+    ++  bont  ;~  (bend)                                ::  term, optional hoon
+                ;~(pfix cen sym)
+                ;~(pfix dot ;~(pose wide ;~(pfix muck loaf)))
+              ==
+    ++  bony  (cook |=(a=(list) (lent a)) (plus tis))   ::  base 1 =en count
+    ++  bonz                                            ::  term-labelled hoons
+      ;~  pose
+        (cold ~ sig)
+        %+  ifix
+          ?:(tol [;~(plug duz gap) ;~(plug gap duz)] [pal par])
+        (more mash ;~(gunk ;~(pfix cen sym) loaf))
+      ==
+    --
+  ::
+  ++  lang                                              ::  lung sample
+    $:  ros=hoon
+        $=  vil
+        $%  [%tis p=hoon]
+            [%col p=hoon]
+            [%ket p=hoon]
+            [%lit p=(list (pair wing hoon))]
+        ==
+    ==
+  ::
+  ++  lung
+    ~+
+    %-  bend
+    |:  $:lang
+    ^-  (unit hoon)
+    ?-    -.vil
+      %col  ?:(=([%base %flag] ros) ~ [~ %tsgl ros p.vil])
+      %lit  (bind ~(reek ap ros) |=(hyp=wing [%cnts hyp p.vil]))
+      %ket  [~ ros p.vil]
+      %tis  =+  rud=~(flay ap ros)
+            ?~(rud ~ `[%ktts u.rud p.vil])
+    ==
+  ::
+  ++  long
+    %+  knee  *hoon  |.  ~+
+    ;~  lung
+      scat
+      ;~  pose
+        ;~(plug (cold %tis tis) wide)
+        ;~(plug (cold %col col) wide)
+        ;~(plug (cold %ket ket) wide)
+        ;~  plug
+          (easy %lit)
+          (ifix [pal par] lobo)
+        ==
+      ==
+    ==
+  ::
+  ++  lobo  (most ;~(plug com ace) ;~(glam rope wide))
+  ++  loon  (most ;~(plug com ace) ;~(glam wide wide))
+  ++  lute                                              ::  tall [] noun
+    ~+
+    %+  cook  |=(hoon +<)
+    %+  stag  %cltr
+    %+  ifix
+      [;~(plug sel gap) ;~(plug gap ser)]
+    (most gap tall)
+  ::
+  ++  ropa  (most col rope)
+  ++  rope                                              ::  wing form
+    %+  knee  *wing
+    |.  ~+
+    %+  (slug |=([a=limb b=wing] [a b]))
+      dot
+    ;~  pose
+      (cold [%| 0 ~] com)
+      %+  cook
+        |=([a=(list) b=term] ?~(a b [%| (lent a) `b]))
+      ;~(plug (star ket) ;~(pose sym (cold %$ buc)))
+    ::
+      %+  cook
+        |=(a=axis [%& a])
+      ;~  pose
+        ;~(pfix lus dim:ag)
+        ;~(pfix pam (cook |=(a=@ ?:(=(0 a) 0 (mul 2 +($(a (dec a)))))) dim:ag))
+        ;~(pfix bar (cook |=(a=@ ?:(=(0 a) 1 +((mul 2 $(a (dec a)))))) dim:ag))
+        ven
+        (cold 1 dot)
+      ==
+    ==
+  ::
+  ++  wise
+    ;~  pose
+      ;~  pfix  tis
+        %+  sear
+          |=  =spec
+          ^-  (unit skin)
+          %+  bind  ~(autoname ax spec)
+          |=  =term
+          [%name term %spec spec %base %noun]
+        wyde
+      ==
+    ::
+      %+  cook
+        |=  [=term =(unit spec)]
+        ^-  skin
+        ?~  unit
+          term
+        [%name term %spec u.unit %base %noun]
+      ;~  plug  sym
+        (punt ;~(pfix ;~(pose fas tis) wyde))
+      ==
+    ::
+      %+  cook
+        |=  =spec
+        ^-  skin
+        [%spec spec %base %noun]
+      wyde
+    ==
+  ::
+  ++  tall                                              ::  full tall form
+    %+  knee  *hoon
+    |.(~+((wart (clad ;~(pose expression:(norm &) long lute apex:(sail &))))))
+  ++  till                                              ::  mold tall form
+    %+  knee  *spec
+    |.(~+((wert (coat ;~(pose structure:(norm &) scad)))))
+  ++  wede                                              ::  wide bulb
+    ::  XX: lus deprecated
+    ::
+    ;~(pfix ;~(pose lus fas) wide)
+  ++  wide                                              ::  full wide form
+    %+  knee  *hoon
+    |.(~+((wart ;~(pose expression:(norm |) long apex:(sail |)))))
+  ++  wyde                                              ::  mold wide form
+    %+  knee  *spec
+    |.(~+((wert ;~(pose structure:(norm |) scad))))
+  ++  wart
+    |*  zor=rule
+    %+  here
+      |=  [a=pint b=hoon]
+      ?:(bug [%dbug [wer a] b] b)
+    zor
+  ++  wert
+    |*  zor=rule
+    %+  here
+      |=  [a=pint b=spec]
+      ?:(bug [%dbug [wer a] b] b)
+    zor
+  --
+::
+++  vest
+  ~/  %vest
+  |=  tub=nail
+  ^-  (like hoon)
+  %.  tub
+  %-  full
+  (ifix [gay gay] tall:vast)
+::
+++  vice
+  |=  txt=@ta
+  ^-  hoon
+  (rash txt wide:vast)
+::
+++  make                                                ::  compile cord to nock
+  |=  txt=@
+  q:(~(mint ut %noun) %noun (ream txt))
+::
+++  rain                                                ::  parse with % path
+  |=  [bon=path txt=@]
+  ^-  hoon
+  =+  vaz=vast
+  ~|  bon
+  (scan (trip txt) (full (ifix [gay gay] tall:vaz(wer bon))))
+::
+++  ream                                                ::  parse cord to hoon
+  |=  txt=@
+  ^-  hoon
+  (rash txt vest)
+::
+++  reck                                                ::  parse hoon file
+  |=  bon=path
+  (rain bon .^(@t %cx (weld bon `path`[%hoon ~])))
+::
+++  ride                                                ::  end-to-end compiler
+  |=  [typ=type txt=@]
+  ^-  (pair type nock)
+  ~>  %slog.[0 leaf/"ride: parsing"]
+  =/  gen  (ream txt)
+  ~>  %slog.[0 leaf/"ride: compiling"]
+  ~<  %slog.[0 leaf/"ride: compiled"]
+  (~(mint ut typ) %noun gen)
+::
+::    5e: molds and mold builders
++|  %molds-and-mold-builders
+::
++$  mane  $@(@tas [@tas @tas])                          ::  XML name+space
++$  manx  $~([[%$ ~] ~] [g=marx c=marl])                ::  dynamic XML node
++$  marl  (list manx)                                   ::  XML node list
++$  mars  [t=[n=%$ a=[i=[n=%$ v=tape] t=~]] c=~]        ::  XML cdata
++$  mart  (list [n=mane v=tape])                        ::  XML attributes
++$  marx  $~([%$ ~] [n=mane a=mart])                    ::  dynamic XML tag
++$  mite  (list @ta)                                    ::  mime type
++$  pass  @                                             ::  public key
++$  ring  @                                             ::  private key
++$  ship  @p                                            ::  network identity
++$  shop  (each ship (list @ta))                        ::  urbit/dns identity
++$  spur  path                                          ::  ship desk case spur
++$  time  @da                                           ::  galactic time
+::
+::    5f: profiling support (XX move)
++|  %profiling-support
+::
+++  pi-heck
+    |=  [nam=@tas day=doss]
+    ^-  doss
+    =+  lam=(~(get by hit.day) nam)
+    day(hit (~(put by hit.day) nam ?~(lam 1 +(u.lam))))
+::
+++  pi-noon                                             ::  sample trace
+  |=  [mot=term paz=(list path) day=doss]
+  =|  lax=(unit path)
+  |-  ^-  doss
+  ?~  paz  day(mon (pi-mope mot mon.day))
+  %=    $
+      paz  t.paz
+      lax  `i.paz
+      cut.day
+    %+  ~(put by cut.day)  i.paz
+    ^-  hump
+    =+  nax=`(unit path)`?~(t.paz ~ `i.t.paz)
+    =+  hup=`hump`=+(hup=(~(get by cut.day) i.paz) ?^(hup u.hup [*moan ~ ~]))
+    :+  (pi-mope mot mon.hup)
+      ?~  lax  out.hup
+      =+  hag=(~(get by out.hup) u.lax)
+      (~(put by out.hup) u.lax ?~(hag 1 +(u.hag)))
+    ?~  nax  inn.hup
+    =+  hag=(~(get by inn.hup) u.nax)
+    (~(put by inn.hup) u.nax ?~(hag 1 +(u.hag)))
+  ==
+++  pi-mope                                             ::  add sample
+  |=  [mot=term mon=moan]
+  ?+  mot  mon
+    %fun  mon(fun +(fun.mon))
+    %noc  mon(noc +(noc.mon))
+    %glu  mon(glu +(glu.mon))
+    %mal  mon(mal +(mal.mon))
+    %far  mon(far +(far.mon))
+    %coy  mon(coy +(coy.mon))
+    %euq  mon(euq +(euq.mon))
+  ==
+++  pi-moth                                             ::  count sample
+  |=  mon=moan  ^-  @ud
+  :(add fun.mon noc.mon glu.mon mal.mon far.mon coy.mon euq.mon)
+::
+++  pi-mumm                                             ::  print sample
+  |=  mon=moan  ^-  tape
+  =+  tot=(pi-moth mon)
+  ;:  welp
+    ^-  tape
+    ?:  =(0 noc.mon)  ~
+    (welp (scow %ud (div (mul 100 noc.mon) tot)) "n ")
+  ::
+    ^-  tape
+    ?:  =(0 fun.mon)  ~
+    (welp (scow %ud (div (mul 100 fun.mon) tot)) "c ")
+  ::
+    ^-  tape
+    ?:  =(0 glu.mon)  ~
+    (welp (scow %ud (div (mul 100 glu.mon) tot)) "g ")
+  ::
+    ^-  tape
+    ?:  =(0 mal.mon)  ~
+    (welp (scow %ud (div (mul 100 mal.mon) tot)) "m ")
+  ::
+    ^-  tape
+    ?:  =(0 far.mon)  ~
+    (welp (scow %ud (div (mul 100 far.mon) tot)) "f ")
+  ::
+    ^-  tape
+    ?:  =(0 coy.mon)  ~
+    (welp (scow %ud (div (mul 100 coy.mon) tot)) "y ")
+  ::
+    ^-  tape
+    ?:  =(0 euq.mon)  ~
+    (welp (scow %ud (div (mul 100 euq.mon) tot)) "e ")
+  ==
+::
+++  pi-tell                                             ::  produce dump
+  |=  day=doss
+  ^-  (list tape)
+  ?:  =(day *doss)  ~
+  =+  tot=(pi-moth mon.day)
+  ;:  welp
+    [(welp "events: " (pi-mumm mon.day)) ~]
+  ::
+    %+  turn
+      %+  sort  ~(tap by hit.day)
+      |=  [a=[* @] b=[* @]]
+      (lth +.a +.b)
+    |=  [nam=term num=@ud]
+    :(welp (trip nam) ": " (scow %ud num))
+    ["" ~]
+  ::
+    %-  zing
+    ^-  (list (list tape))
+    %+  turn
+      %+  sort  ~(tap by cut.day)
+      |=  [one=(pair path hump) two=(pair path hump)]
+      (gth (pi-moth mon.q.one) (pi-moth mon.q.two))
+    |=  [pax=path hup=hump]
+    =+  ott=(pi-moth mon.hup)
+    ;:  welp
+      [(welp "label: " (spud pax)) ~]
+      [(welp "price: " (scow %ud (div (mul 100 ott) tot))) ~]
+      [(welp "shape: " (pi-mumm mon.hup)) ~]
+    ::
+      ?:  =(~ out.hup)  ~
+      :-  "into:"
+      %+  turn
+        %+  sort  ~(tap by out.hup)
+        |=([[* a=@ud] [* b=@ud]] (gth a b))
+      |=  [pax=path num=@ud]
+      ^-  tape
+      :(welp "  " (spud pax) ": " (scow %ud num))
+    ::
+      ?:  =(~ inn.hup)  ~
+      :-  "from:"
+      %+  turn
+        %+  sort  ~(tap by inn.hup)
+        |=([[* a=@ud] [* b=@ud]] (gth a b))
+      |=  [pax=path num=@ud]
+      ^-  tape
+      :(welp "  " (spud pax) ": " (scow %ud num))
+    ::
+      ["" ~]
+      ~
+    ==
+  ==
+--

+ 16 - 0
crates/nockapp/apps/http-app/Cargo.toml

@@ -0,0 +1,16 @@
+[package]
+name = "http-app"
+version.workspace = true
+edition.workspace = true
+
+[dependencies]
+crown  = { path = "../../crown" }
+sword = { workspace = true }
+sword_macros = { workspace = true }
+clap = { workspace = true, features = ["derive", "cargo", "color", "env"]}
+tokio = { workspace = true, features = ["signal"] }
+tracing = { workspace = true }
+
+[[bin]]
+name = "http-app"
+path = "main.rs"

BIN
crates/nockapp/apps/http-app/bootstrap/http.jam


+ 98 - 0
crates/nockapp/apps/http-app/bootstrap/kernel.hoon

@@ -0,0 +1,98 @@
+/+  *wrapper
+=>
+|%
++$  server-state  %stateless
+++  moat  (keep server-state)
++$  header  [k=@t v=@t]
++$  octs  [p=@ q=@]
++$  method
+  $?  %'GET'
+      %'HEAD'
+      %'POST'
+      %'PUT'
+      %'DELETE'
+      %'CONNECT'
+      %'OPTIONS'
+      %'TRACE'
+      %'PATCH'
+  ==
+::
++$  cause
+  $:  %req
+      id=@
+      uri=@t
+      =method
+      headers=(list header)
+      body=(unit octs)
+  ==
+::
++$  effect
+  $:  %res
+      id=@
+      status=@ud
+      headers=(list header)
+      body=(unit octs)
+  ==
+::
+++  to-octs
+  |=  bod=@
+  ^-  (unit octs)
+  =/  len  (met 3 bod)
+  ?:  =(len 0)  ~
+  `[len bod]
+--
+::
+~&  %serving
+%-  (moat |)
+^-  fort:moat
+|_  k=server-state
+::
+::  +load: upgrade from previous state
+::
+++  load
+  |=  arg=server-state
+  arg
+::
+::  +peek: external inspect
+::
+++  peek
+  |=  =path
+  ^-  (unit (unit *))
+  !!
+::
+::  +poke: external apply
+::
+++  poke
+  |=  [=wire eny=@ our=@ux now=@da dat=*]
+  ^-  [(list effect) server-state]
+  =/  sof-cau=(unit cause)  ((soft cause) dat)
+  ?~  sof-cau
+    ~&  "cause incorrectly formatted!"
+    ~&  dat
+    !!
+  =/  [id=@ uri=@t =method headers=(list header) body=(unit octs)]  +.u.sof-cau
+  ~&  [id+id uri+uri method+method headers+headers]
+  :_  k
+  :_  ~
+  ^-  effect
+  =-  ~&  effect+-
+      -
+  ?+    method  [%res ~ %400 ~ ~]
+      %'GET'
+    :*  %res  id=id  %200
+      ['content-type' 'text/html']~
+    %-  to-octs
+    '''
+    <!doctype html>
+    <html>
+      <body>
+        <h1>Hello NockApp!</h1>
+      </body>
+    </html>
+    '''
+   ==
+  ::
+      %'POST'
+    !!
+  ==
+--

+ 26 - 0
crates/nockapp/apps/http-app/main.rs

@@ -0,0 +1,26 @@
+use clap::{command, ColorChoice, Parser};
+use crown::kernel::boot;
+use crown::kernel::boot::Cli as BootCli;
+use tracing::debug;
+
+static KERNEL_JAM: &[u8] =
+    include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/bootstrap/http.jam"));
+#[derive(Parser, Debug)]
+#[command(about = "Tests various poke types for the kernel", author = "zorp", version, color = ColorChoice::Auto)]
+struct TestCli {
+    #[command(flatten)]
+    boot: BootCli,
+}
+
+#[tokio::main]
+async fn main() -> Result<(), Box<dyn std::error::Error>> {
+    let cli = TestCli::parse();
+    debug!("KERNEL_JAM len: {:?}", KERNEL_JAM.to_vec().len());
+    boot::init_default_tracing(&cli.boot.clone());
+    let mut http_app = boot::setup(KERNEL_JAM, Some(cli.boot.clone()), &[], "choo")?;
+    http_app.add_io_driver(crown::http_driver()).await;
+
+    http_app.run().await.expect("Failed to run app");
+
+    Ok(())
+}

+ 16 - 0
crates/nockapp/apps/test-app/Cargo.toml

@@ -0,0 +1,16 @@
+[package]
+name = "test-app"
+version.workspace = true
+edition.workspace = true
+
+[dependencies]
+crown = { path = "../../crown" }
+sword = { workspace = true }
+sword_macros = { workspace = true }
+clap = { workspace = true, features = ["derive", "cargo", "color", "env"]}
+tokio = { workspace = true, features = ["signal"] }
+tracing = { workspace = true }
+
+[[bin]]
+name = "test"
+path = "main.rs"

+ 71 - 0
crates/nockapp/apps/test-app/bootstrap/kernel.hoon

@@ -0,0 +1,71 @@
+/+  *wrapper
+=>
+|%
++$  versioned-state
+  $%  test-state
+  ==
++$  test-state  [%0 val=@]
+++  moat  (keep test-state)
++$  cause  ?(%inc %inc-exit)
+::
++$  effect
+  $%  [%state test-state]
+      [%exit id=@]
+  ==
+--
+::
+%-  (moat |)
+^-  fort:moat
+|_  k=test-state
+::
+::  +load: upgrade from previous state
+::
+++  load
+  |=  old=test-state
+  ~&  "test-app: load"
+  ?~  ((soft versioned-state) old)
+    ~&   "test-app: +load old state does not nest under versioned-state"
+    !!
+  old
+::
+::  +peek: external inspect
+::
+++  peek
+  |=  arg=*
+  ^-  (unit (unit *))
+  =/  pax  ((soft path) arg)
+  ?~  pax  ~|(invalid-peek+pax !!)
+  ~&  >  "peeked at {<u.pax>}"
+  ?+  u.pax  ~|(invalid-peek+pax !!)
+  ::
+      [%state ~]
+    ``k
+  ==
+::
+::  +poke: external apply
+::
+++  poke
+  |=  [=wire eny=@ our=@ux now=@da dat=*]
+  ^-  [(list effect) test-state]
+  ~&  >>  "poke: {<wire>}"
+  =/  sof-cau=(unit cause)  ((soft cause) dat)
+  ?~  sof-cau
+    ~&  "cause incorrectly formatted!"
+    ~&  dat
+    !!
+  ?+    `@tas`u.sof-cau  !!
+      %inc
+    =.  val.k  +(val.k)
+    :_  k
+    =-  ~&  effect+-
+      -
+    ~[[%state k]]
+  ::
+     %inc-exit
+    =.  val.k  +(val.k)
+    :_  k
+    =-  ~&  effect+-
+      -
+    ~[[%exit 0] [%state k]]
+  ==
+--

BIN
crates/nockapp/apps/test-app/bootstrap/test-ker.jam


+ 47 - 0
crates/nockapp/apps/test-app/main.rs

@@ -0,0 +1,47 @@
+use clap::{command, ColorChoice, Parser};
+use crown::kernel::boot;
+use crown::kernel::boot::Cli as BootCli;
+use crown::noun::slab::NounSlab;
+use sword::noun::D;
+use sword_macros::tas;
+use tracing::debug;
+
+static KERNEL_JAM: &[u8] = include_bytes!(concat!(
+    env!("CARGO_MANIFEST_DIR"),
+    "/bootstrap/test-ker.jam"
+));
+#[derive(Parser, Debug)]
+#[command(about = "Tests various poke types for the kernel", author = "zorp", version, color = ColorChoice::Auto)]
+struct TestCli {
+    #[command(flatten)]
+    boot: BootCli,
+    #[arg(long, help = "Exit after poke")]
+    exit: bool,
+}
+
+#[tokio::main]
+async fn main() -> Result<(), Box<dyn std::error::Error>> {
+    let cli = TestCli::parse();
+    debug!("KERNEL_JAM len: {:?}", KERNEL_JAM.to_vec().len());
+    boot::init_default_tracing(&cli.boot.clone());
+    let mut test_app = boot::setup(KERNEL_JAM, Some(cli.boot.clone()), &[], "test")?;
+    let poke = if cli.exit {
+        D(tas!(b"inc-exit"))
+    } else {
+        D(tas!(b"inc"))
+    };
+    let mut slab = NounSlab::new();
+    slab.set_root(poke);
+    test_app
+        .add_io_driver(crown::one_punch_driver(
+            slab,
+            crown::nockapp::driver::Operation::Poke,
+        ))
+        .await;
+
+    test_app.add_io_driver(crown::exit_driver()).await;
+
+    test_app.run().await.expect("Failed to run app");
+
+    Ok(())
+}

+ 55 - 0
crates/nockapp/crown/Cargo.toml

@@ -0,0 +1,55 @@
+[package]
+name = "crown"
+version.workspace = true
+edition.workspace = true
+
+[features]
+default = ["slog-tracing"]
+slog-tracing = []
+trait-alias = []
+bazel_build = []
+
+[dependencies]
+anyhow = { workspace = true }
+axum = { workspace = true }
+bitvec = { workspace = true }
+blake3 = { workspace = true }
+clap = { workspace = true, features = ["derive", "cargo", "color", "env"] }
+dirs = { workspace = true }
+ibig = { workspace = true }
+sword = { workspace = true }
+sword_macros = { workspace = true }
+assert_no_alloc = { workspace = true }
+async-trait = { workspace = true }
+bincode = { workspace = true, features = ["serde"] }
+byteorder = { workspace = true }
+bytes = { workspace = true, features = ["serde"] }
+either = { workspace = true }
+futures = { workspace = true }
+getrandom = { workspace = true }
+gnort = { workspace = true }
+intmap = { workspace = true }
+rand = { workspace = true }
+serde = { workspace = true }
+tempfile = { workspace = true }
+termimad = { workspace = true }
+thiserror = { workspace = true }
+tracing = { workspace = true }
+tracing-test = { workspace = true }
+tracing-subscriber = { workspace = true }
+tokio = { workspace = true, features = ["time", "sync", "signal"] }
+tokio-util = { workspace = true, features = ["rt"] }
+yaque = { workspace = true }
+chrono = { workspace = true }
+
+opentelemetry.workspace = true
+opentelemetry-otlp.workspace = true
+opentelemetry_sdk.workspace = true
+tonic.workspace = true
+tracing-opentelemetry.workspace = true
+
+[dev-dependencies]
+
+[lib]
+name = "crown"
+path = "src/lib.rs"

+ 58 - 0
crates/nockapp/crown/src/drivers/exit.rs

@@ -0,0 +1,58 @@
+use crate::nockapp::driver::{make_driver, IODriverFn};
+use crate::NounExt;
+use tracing::{error, info};
+
+/// Creates an IO driver function for handling exit signals.
+///
+/// This function creates a driver that listens for exit signals and terminates
+/// the process with the provided exit code when received.
+///
+/// # Returns
+///
+/// An `IODriverFn` that can be used with the NockApp to handle exit signals.
+pub fn exit() -> IODriverFn {
+    make_driver(|handle| async move {
+        info!("exit_driver: waiting for effect");
+        loop {
+            tokio::select! {
+                eff = handle.next_effect() => {
+                    match eff {
+                        Ok(eff) => {
+                            unsafe {
+                                let noun = eff.root();
+                                if let Ok(cell) = noun.as_cell() {
+                                    if cell.head().eq_bytes(b"exit") && cell.tail().is_atom() {
+                                        // Exit with the code provided in the tail
+                                        if let Ok(exit_code) = cell.tail().as_atom().and_then(|atom| atom.as_u64()) {
+                                            handle.exit.send(exit_code as usize).await.unwrap_or_else(|err| {
+                                                panic!(
+                                                    "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                                                    file!(),
+                                                    line!(),
+                                                    option_env!("GIT_SHA")
+                                                )
+                                            });
+                                        } else {
+                                            // Default to error code 1 if we can't get a valid exit code
+                                            handle.exit.send(1).await.unwrap_or_else(|err| {
+                                                panic!(
+                                                    "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                                                    file!(),
+                                                    line!(),
+                                                    option_env!("GIT_SHA")
+                                                )
+                                            });
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                        Err(e) => {
+                            error!("Error receiving effect: {:?}", e);
+                        }
+                    }
+                }
+            }
+        }
+    })
+}

+ 177 - 0
crates/nockapp/crown/src/drivers/file.rs

@@ -0,0 +1,177 @@
+use crate::nockapp::driver::{make_driver, IODriverFn};
+use crate::nockapp::wire::{Wire, WireRepr};
+use crate::noun::slab::NounSlab;
+use crate::noun::FromAtom;
+use crate::AtomExt;
+use sword::noun::{IndirectAtom, Noun, D, NO, T, YES};
+use sword_macros::tas;
+use tracing::{error, info};
+
+pub enum FileWire {
+    Read,
+    Write,
+}
+
+impl Wire for FileWire {
+    const VERSION: u64 = 1;
+    const SOURCE: &'static str = "file";
+
+    fn to_wire(&self) -> crate::nockapp::wire::WireRepr {
+        let tags = match self {
+            FileWire::Read => vec!["read".into()],
+            FileWire::Write => vec!["write".into()],
+        };
+        WireRepr::new(FileWire::SOURCE, FileWire::VERSION, tags)
+    }
+}
+
+/// File IO Driver
+///
+/// ## Effects
+/// `[%file %read path=@t]`
+/// results in poke
+/// `[%file %read ~]` on read error
+/// or
+/// `[%file %read ~ contents=@]` on read success
+///
+///  `[%file %write path=@t contents=@]`
+///  results in file written to disk and poke
+///  `[%file %write path=@t contents=@ success=?]`
+pub fn file() -> IODriverFn {
+    make_driver(|handle| async move {
+        loop {
+            let effect_res = handle.next_effect().await;
+            let slab = match effect_res {
+                Ok(slab) => slab,
+                Err(e) => {
+                    error!("Error receiving effect: {:?}", e);
+                    continue;
+                }
+            };
+
+            let Ok(effect_cell) = unsafe { slab.root() }.as_cell() else {
+                continue;
+            };
+
+            if !unsafe { effect_cell.head().raw_equals(&D(tas!(b"file"))) } {
+                continue;
+            }
+
+            let Ok(file_cell) = effect_cell.tail().as_cell() else {
+                continue;
+            };
+
+            let (operation, path_atom) = match file_cell.head().as_direct() {
+                Ok(tag) if tag.data() == tas!(b"read") => ("read", file_cell.tail().as_atom().ok()),
+                Ok(tag) if tag.data() == tas!(b"write") => {
+                    let Ok(write_cell) = file_cell.tail().as_cell() else {
+                        continue;
+                    };
+                    ("write", write_cell.head().as_atom().ok())
+                }
+                _ => continue,
+            };
+
+            match (operation, path_atom) {
+                ("read", Some(path_atom)) => {
+                    let path = String::from_utf8(Vec::from(path_atom.as_ne_bytes()))?;
+                    match tokio::fs::read(&path).await {
+                        Ok(contents) => {
+                            let mut poke_slab = NounSlab::new();
+                            let contents_atom = unsafe {
+                                IndirectAtom::new_raw_bytes_ref(&mut poke_slab, &contents)
+                                    .normalize_as_atom()
+                            };
+                            let contents_noun = Noun::from_atom(contents_atom);
+                            let poke_noun = T(
+                                &mut poke_slab,
+                                &[D(tas!(b"file")), D(tas!(b"read")), D(0), contents_noun],
+                            );
+                            poke_slab.set_root(poke_noun);
+                            let wire = FileWire::Read.to_wire();
+                            handle.poke(wire, poke_slab).await?;
+                        }
+                        Err(_) => {
+                            let mut poke_slab = NounSlab::new();
+                            let poke_noun =
+                                T(&mut poke_slab, &[D(tas!(b"file")), D(tas!(b"read")), D(0)]);
+                            poke_slab.set_root(poke_noun);
+                            let wire = FileWire::Read.to_wire();
+                            handle.poke(wire, poke_slab).await?;
+                        }
+                    }
+                }
+                ("write", Some(path_atom)) => {
+                    let Ok(write_cell) = file_cell.tail().as_cell() else {
+                        continue;
+                    };
+                    let Ok(contents_atom) = write_cell.tail().as_atom() else {
+                        continue;
+                    };
+                    let path = path_atom.into_string()?;
+                    let contents = contents_atom.as_ne_bytes();
+                    info!("file driver: writing {} bytes to: {}", contents.len(), path);
+
+                    // Create parent directories if they don't exist
+                    if let Some(parent) = std::path::Path::new(&path).parent() {
+                        if let Err(e) = tokio::fs::create_dir_all(parent).await {
+                            error!("file driver: error creating directories: {}", e);
+                            let mut poke_slab = NounSlab::new();
+                            let poke_noun = T(
+                                &mut poke_slab,
+                                &[
+                                    D(tas!(b"file")),
+                                    D(tas!(b"write")),
+                                    path_atom.as_noun(),
+                                    contents_atom.as_noun(),
+                                    NO,
+                                ],
+                            );
+                            poke_slab.set_root(poke_noun);
+                            let wire = FileWire::Write.to_wire();
+                            handle.poke(wire, poke_slab).await?;
+                            continue;
+                        }
+                    }
+
+                    match tokio::fs::write(&path, contents).await {
+                        Ok(_) => {
+                            let mut poke_slab = NounSlab::new();
+                            let poke_noun = T(
+                                &mut poke_slab,
+                                &[
+                                    D(tas!(b"file")),
+                                    D(tas!(b"write")),
+                                    path_atom.as_noun(),
+                                    contents_atom.as_noun(),
+                                    YES,
+                                ],
+                            );
+                            poke_slab.set_root(poke_noun);
+                            let wire = FileWire::Write.to_wire();
+                            handle.poke(wire, poke_slab).await?;
+                        }
+                        Err(e) => {
+                            error!("file driver: error writing to path: {}", e);
+                            let mut poke_slab = NounSlab::new();
+                            let poke_noun = T(
+                                &mut poke_slab,
+                                &[
+                                    D(tas!(b"file")),
+                                    D(tas!(b"write")),
+                                    path_atom.as_noun(),
+                                    contents_atom.as_noun(),
+                                    NO,
+                                ],
+                            );
+                            poke_slab.set_root(poke_noun);
+                            let wire = FileWire::Write.to_wire();
+                            handle.poke(wire, poke_slab).await?;
+                        }
+                    }
+                }
+                _ => continue,
+            }
+        }
+    })
+}

+ 365 - 0
crates/nockapp/crown/src/drivers/http.rs

@@ -0,0 +1,365 @@
+use crate::nockapp::driver::{make_driver, IODriverFn, PokeResult};
+use crate::nockapp::wire::{Wire, WireRepr};
+use crate::nockapp::NockAppError;
+use crate::noun::slab::NounSlab;
+use crate::{AtomExt, Bytes};
+use std::collections::HashMap;
+use std::sync::atomic::{AtomicU64, Ordering};
+
+use axum::body::Body;
+use axum::extract::State;
+use axum::http::{HeaderMap, Method, StatusCode, Uri};
+use axum::response::Response;
+use axum::routing::any;
+use sword::noun::{Atom, D, T};
+use sword_macros::tas;
+use tokio::select;
+use tokio::sync::{oneshot, RwLock};
+use tracing::debug;
+
+type Responder = oneshot::Sender<Result<Response, StatusCode>>;
+#[derive(Debug)]
+struct RequestMessage {
+    id: u64,
+    uri: Uri,
+    method: Method,
+    headers: HeaderMap,
+    body: Option<axum::body::Bytes>,
+    resp: Responder,
+}
+
+struct ResponseBuilder {
+    status_code: StatusCode,
+    headers: Vec<(String, String)>,
+    body: Option<axum::body::Bytes>,
+}
+
+pub enum HttpWire {
+    Request,
+}
+
+impl Wire for HttpWire {
+    const VERSION: u64 = 1;
+    const SOURCE: &'static str = "http";
+
+    fn to_wire(&self) -> WireRepr {
+        let tags = match self {
+            HttpWire::Request => vec!["req".into()],
+        };
+        WireRepr::new(HttpWire::SOURCE, HttpWire::VERSION, tags)
+    }
+}
+
+static COUNTER: AtomicU64 = AtomicU64::new(0);
+// wraps on overflow
+fn get_id() -> u64 {
+    COUNTER.fetch_add(1, Ordering::Relaxed)
+}
+
+/// HTTP IO driver
+pub fn http() -> IODriverFn {
+    make_driver(move |handle| async move {
+        let (tx, mut rx) = tokio::sync::mpsc::channel::<RequestMessage>(10);
+        let app = any(sword_handler).with_state(tx);
+
+        let listener = tokio::net::TcpListener::bind("0.0.0.0:8080")
+            .await
+            .unwrap_or_else(|err| {
+                panic!(
+                    "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            });
+        debug!(
+            "listening on {}",
+            listener.local_addr().unwrap_or_else(|err| {
+                panic!(
+                    "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            })
+        );
+        tokio::spawn(async move {
+            axum::serve(listener, app.into_make_service())
+                .await
+                .unwrap_or_else(|err| {
+                    panic!(
+                        "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                        file!(),
+                        line!(),
+                        option_env!("GIT_SHA")
+                    )
+                });
+        });
+
+        let channel_map = RwLock::new(HashMap::<u64, Responder>::new());
+
+        loop {
+            // Start receiving messages
+            select! {
+                msg = rx.recv() => {
+                    let msg = msg.unwrap_or_else(|| panic!("Panicked at {}:{} (git sha: {:?})", file!(), line!(), option_env!("GIT_SHA")));
+                    channel_map.write().await.insert(msg.id, msg.resp);
+                    let mut slab = NounSlab::new();
+                    let id =  Atom::from_value(&mut slab, msg.id).unwrap_or_else(|err| {
+                        panic!(
+                            "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                            file!(),
+                            line!(),
+                            option_env!("GIT_SHA")
+                        )
+                    });
+                    let uri =
+                        Atom::from_value(&mut slab, msg.uri.to_string()).unwrap_or_else(|err| {
+                            panic!(
+                                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                                file!(),
+                                line!(),
+                                option_env!("GIT_SHA")
+                            )
+                        });
+
+                    let method =
+                        Atom::from_value(&mut slab, msg.method.to_string()).unwrap_or_else(|err| {
+                            panic!(
+                                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                                file!(),
+                                line!(),
+                                option_env!("GIT_SHA")
+                            )
+                        });
+
+                    let mut headers = D(0);
+                    for (k, v) in msg.headers {
+                        let key = k.unwrap_or_else(|| panic!("Panicked at {}:{} (git sha: {:?})", file!(), line!(), option_env!("GIT_SHA"))).as_str().to_string();
+                        let val = v.to_str().unwrap_or_else(|err| {
+                            panic!(
+                                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                                file!(),
+                                line!(),
+                                option_env!("GIT_SHA")
+                            )
+                        });
+                        let k_atom = Atom::from_value(&mut slab, key).unwrap_or_else(|err| {
+                            panic!(
+                                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                                file!(),
+                                line!(),
+                                option_env!("GIT_SHA")
+                            )
+                        });
+                        let v_atom = Atom::from_value(&mut slab, val).unwrap_or_else(|err| {
+                            panic!(
+                                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                                file!(),
+                                line!(),
+                                option_env!("GIT_SHA")
+                            )
+                        });
+                        let header_cell = T(&mut slab, &[k_atom.as_noun(), v_atom.as_noun()]);
+                        headers = T(&mut slab, &[header_cell, headers]);
+                    }
+
+                    let body: crate::Noun = {
+                        if let Some(bod) = msg.body {
+                            let ato = Atom::from_bytes(&mut slab, &bod).as_noun();
+                            T(
+                                &mut slab,
+                                &[D(0), D(bod.len().try_into().unwrap_or_else(|err| {
+                                    panic!(
+                                        "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                                        file!(),
+                                        line!(),
+                                        option_env!("GIT_SHA")
+                                    )
+                                })), ato],
+                            )
+                        } else {
+                            D(0)
+                        }
+                    };
+
+                    let poke = {
+                        T(
+                            &mut slab,
+                            &[D(tas!(b"req")), id.as_noun(), uri.as_noun(), method.as_noun(), headers, body],
+                        )
+                    };
+                    debug!("poking: {:?}", poke);
+                    slab.set_root(poke);
+
+                    let wire = HttpWire::Request.to_wire();
+                    let poke_result = handle.poke(wire, slab).await?;
+                    debug!("poke result: {:?}", poke_result);
+
+                    if let PokeResult::Nack = poke_result {
+                        return Err(NockAppError::PokeFailed);
+                    }
+                }
+                effect = handle.next_effect() => {
+                    debug!("effect: {:?}", effect);
+                    let slab = effect.unwrap_or_else(|err| {
+                        panic!(
+                            "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                            file!(),
+                            line!(),
+                            option_env!("GIT_SHA")
+                        )
+                    });
+                    let effect = unsafe { slab.root() };
+                    let res_list = effect.as_cell()?;
+                    let mut res = res_list.tail().as_cell()?;
+                    let id = res.head().as_atom()?.as_u64().unwrap_or_else(|err| {
+                        panic!(
+                            "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                            file!(),
+                            line!(),
+                            option_env!("GIT_SHA")
+                        )
+                    });
+                    res = res.tail().as_cell()?;
+                    let status_code = res
+                        .head()
+                        .as_atom()?
+                        .direct()
+                        .expect("not a valid status code!")
+                        .data();
+                    let mut header_list = res.tail().as_cell()?.head();
+                    let mut header_vec: Vec<(String, String)> = Vec::new();
+                    loop {
+                        if header_list.is_atom() {
+                            break;
+                        } else {
+                            let header = header_list.as_cell()?.head().as_cell()?;
+                            let key_vec = header.head().as_atom()?;
+                            let val_vec = header.tail().as_atom()?;
+
+                            if let Ok(key) = key_vec.to_bytes_until_nul() {
+                                if let Ok(val) = val_vec.to_bytes_until_nul() {
+                                    header_vec.push((
+                                        String::from_utf8(key)?,
+                                        String::from_utf8(val)?,
+                                    ));
+                                    header_list = header_list.as_cell()?.tail();
+                                } else {
+                                    break;
+                                }
+                            } else {
+                                break;
+                            }
+                        }
+                    }
+
+                    let maybe_body = res.tail().as_cell()?.tail();
+
+                    let body: Option<Bytes> = {
+                        if maybe_body.is_cell() {
+                            let body_octs = maybe_body.as_cell()?.tail().as_cell()?;
+                            let body_len = body_octs
+                                .head()
+                                .as_atom()?
+                                .direct()
+                                .expect("body len")
+                                .data();
+                            let mut body_vec: Vec<u8> = b"0".repeat(body_len.try_into().unwrap_or_else(|err| {
+                                panic!(
+                                    "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                                    file!(),
+                                    line!(),
+                                    option_env!("GIT_SHA")
+                                )
+                            }));
+                            let body_atom = body_octs.tail().as_atom()?;
+                            body_vec.copy_from_slice(&body_atom.to_bytes_until_nul().unwrap_or_else(|err| {
+                                panic!(
+                                    "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                                    file!(),
+                                    line!(),
+                                    option_env!("GIT_SHA")
+                                )
+                            }));
+                            Some(Bytes::from(body_vec))
+                        } else {
+                            None
+                        }
+                    };
+
+                    let resp = if let Ok(status) = StatusCode::from_u16(status_code as u16) {
+                        let res_builder = ResponseBuilder {
+                            status_code: status,
+                            headers: header_vec,
+                            body,
+                        };
+
+                        let mut res = Response::builder().status(res_builder.status_code);
+
+                        for (k, v) in res_builder.headers {
+                            res = res.header(k, v);
+                        }
+
+                        let bod = res_builder.body.ok_or("invalid response").unwrap_or_else(|err| {
+                            panic!(
+                                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                                file!(),
+                                line!(),
+                                option_env!("GIT_SHA")
+                            )
+                        });
+                        Ok(res.body(Body::from(bod)).unwrap_or_else(|err| {
+                            panic!(
+                                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                                file!(),
+                                line!(),
+                                option_env!("GIT_SHA")
+                            )
+                        }))
+                    } else {
+                        debug!("statuscode internal server error");
+                        Err(StatusCode::INTERNAL_SERVER_ERROR)
+                    };
+
+                    let resp_tx = channel_map.write().await.remove(&id).unwrap_or_else(|| panic!("Panicked at {}:{} (git sha: {:?})", file!(), line!(), option_env!("GIT_SHA")));
+                    let _ = resp_tx.send(resp);
+                }
+            }
+        }
+    })
+}
+
+async fn sword_handler(
+    method: Method,
+    headers: HeaderMap,
+    uri: Uri,
+    State(sender): State<tokio::sync::mpsc::Sender<RequestMessage>>,
+    body: axum::body::Bytes,
+) -> Result<Response, StatusCode> {
+    let (resp_tx, resp_rx) = oneshot::channel::<Result<Response, StatusCode>>();
+    let opt_body: Option<axum::body::Bytes> = {
+        if body.is_empty() {
+            None
+        } else {
+            Some(body)
+        }
+    };
+    let msg = RequestMessage {
+        id: get_id(),
+        uri,
+        method,
+        headers,
+        body: opt_body,
+        resp: resp_tx,
+    };
+
+    let _ = sender.send(msg).await;
+
+    // Await the response
+    if let Ok(result) = resp_rx.await {
+        result
+    } else {
+        Err(StatusCode::INTERNAL_SERVER_ERROR)
+    }
+}

+ 39 - 0
crates/nockapp/crown/src/drivers/markdown.rs

@@ -0,0 +1,39 @@
+use crate::nockapp::driver::{make_driver, IODriverFn};
+use crate::AtomExt;
+use sword::noun::D;
+use sword_macros::tas;
+
+use termimad::MadSkin;
+use tracing::error;
+
+pub fn markdown() -> IODriverFn {
+    make_driver(|handle| async move {
+        let skin = MadSkin::default_dark();
+
+        loop {
+            match handle.next_effect().await {
+                Ok(effect) => {
+                    let Ok(effect_cell) = unsafe { effect.root() }.as_cell() else {
+                        continue;
+                    };
+                    if unsafe { effect_cell.head().raw_equals(&D(tas!(b"markdown"))) } {
+                        let markdown_text = effect_cell.tail();
+
+                        let text = if let Ok(atom) = markdown_text.as_atom() {
+                            String::from_utf8_lossy(&atom.to_bytes_until_nul()?).to_string()
+                        } else {
+                            error!("Failed to convert markdown text to string");
+                            continue;
+                        };
+
+                        println!("{}", skin.term_text(&text));
+                    }
+                }
+                Err(e) => {
+                    error!("Error in markdown driver: {:?}", e);
+                    continue;
+                }
+            }
+        }
+    })
+}

+ 15 - 0
crates/nockapp/crown/src/drivers/mod.rs

@@ -0,0 +1,15 @@
+pub mod exit;
+pub mod file;
+pub mod http;
+pub mod markdown;
+pub mod npc;
+pub mod one_punch;
+pub mod timer;
+
+pub use exit::exit as exit_driver;
+pub use file::file as file_driver;
+pub use http::http as http_driver;
+pub use markdown::markdown as markdown_driver;
+pub use npc::{npc_client as npc_client_driver, npc_listener as npc_listener_driver};
+pub use one_punch::one_punch_man as one_punch_driver;
+pub use timer::make_timer_driver as timer_driver;

+ 668 - 0
crates/nockapp/crown/src/drivers/npc.rs

@@ -0,0 +1,668 @@
+use crate::nockapp::driver::{make_driver, IODriverFn, PokeResult, TaskJoinSet};
+use crate::nockapp::wire::{Wire, WireRepr};
+use crate::nockapp::NockAppError;
+use crate::noun::slab::NounSlab;
+use crate::Bytes;
+use bytes::buf::BufMut;
+use std::sync::Arc;
+
+use sword::noun::{D, T};
+use sword_macros::tas;
+use tokio::io::{split, AsyncReadExt, AsyncWriteExt, ReadHalf, WriteHalf};
+use tokio::net::{UnixListener, UnixStream};
+use tokio::select;
+use tokio::sync::Mutex;
+use tokio::task::JoinSet;
+use tokio::time::{sleep, Duration};
+use tracing::{debug, error};
+
+pub enum NpcWire {
+    Poke(u64),
+    Pack(u64),
+    Nack(u64),
+    Bind(u64),
+}
+
+impl Wire for NpcWire {
+    const VERSION: u64 = 1;
+    const SOURCE: &'static str = "npc";
+
+    fn to_wire(&self) -> WireRepr {
+        let tags = match self {
+            NpcWire::Poke(pid) => vec!["poke".into(), pid.into()],
+            NpcWire::Pack(pid) => vec!["pack".into(), pid.into()],
+            NpcWire::Nack(pid) => vec!["nack".into(), pid.into()],
+            NpcWire::Bind(pid) => vec!["bind".into(), pid.into()],
+        };
+        WireRepr::new(Self::SOURCE, Self::VERSION, tags)
+    }
+}
+
+/// NPC Listener IO driver
+pub fn npc_listener(listener: UnixListener) -> IODriverFn {
+    make_driver(move |mut handle| async move {
+        let mut client_join_set = TaskJoinSet::new();
+        loop {
+            select! {
+                stream_res = listener.accept() => {
+                    debug!("Accepted new connection");
+                    match stream_res {
+                        Ok((stream, _)) => {
+                            let (my_handle, their_handle) = handle.dup();
+                            handle = my_handle;
+                            let _ = client_join_set.spawn(npc_client(stream)(their_handle));
+                        },
+                        Err(e) => {
+                            error!("Error accepting connection: {:?}", e);
+                        }
+                    }
+                },
+                Some(result) = client_join_set.join_next() => {
+                    match result {
+                        Ok(Ok(())) => debug!("npc: client task completed successfully"),
+                        Ok(Err(e)) => error!("npc: client task error: {:?}", e),
+                        Err(e) => error!("npc: client task join error: {:?}", e),
+                    }
+                },
+                // TODO: don't do this, revive robin hood
+                _ = sleep(Duration::from_millis(100)) => {
+                    // avoid tight-looping
+                }
+            }
+        }
+    })
+}
+
+/// NPC Client IO driver
+pub fn npc_client(stream: UnixStream) -> IODriverFn {
+    make_driver(move |handle| async move {
+        let (stream_read, mut stream_write) = split(stream);
+        let stream_read_arc = Arc::new(Mutex::new(stream_read));
+        let mut read_message_join_set = JoinSet::new();
+        read_message_join_set.spawn(read_message(stream_read_arc.clone()));
+
+        'driver: loop {
+            select! {
+                message = read_message_join_set.join_next() => {
+                    match message {
+                        Some(Ok(Ok(Some(mut slab)))) => {
+                            debug!("npc_client: read message");
+                            let Ok(message_cell) = unsafe { slab.root() }.as_cell() else {
+                                continue;
+                            };
+
+                            let (pid, directive_cell) = match (message_cell.head().as_direct(), message_cell.tail().as_cell()) {
+                                (Ok(direct), Ok(cell)) => (direct.data(), cell),
+                                _ => continue,
+                            };
+
+                            let Ok(directive_tag) = directive_cell.head().as_direct() else {
+                                continue;
+                            };
+                            let directive_tag = directive_tag.data();
+
+                            match directive_tag {
+                                tas!(b"poke") => {
+                                    debug!("npc_client: poke");
+                                    let mut poke_slab = NounSlab::new();
+                                    let poke = directive_cell.tail();
+                                    poke_slab.copy_into(poke);
+                                    let wire = NpcWire::Poke(pid).to_wire();
+                                    let result = handle.poke(wire, poke_slab).await?;
+                                    let (tag, noun) = match result {
+                                        PokeResult::Ack => (tas!(b"pack"), D(0)),
+                                        PokeResult::Nack => (tas!(b"nack"), D(0)),
+                                    };
+
+                                    let mut response_slab = NounSlab::new();
+                                    let response_noun = T(&mut response_slab, &[D(pid), D(tag), noun]);
+                                    response_slab.set_root(response_noun);
+                                    if !write_message(&mut stream_write, response_slab).await? {
+                                        break 'driver;
+                                    }
+                                },
+                                tas!(b"peek") => {
+                                    debug!("npc_client: peek");
+                                    let path = directive_cell.tail();
+                                    slab.set_root(path);
+                                    let peek_res = handle.peek(slab).await?;
+                                    match peek_res {
+                                        Some(mut bind_slab) => {
+                                            bind_slab.modify(|root| {
+                                                vec![D(pid), D(tas!(b"bind")), root]
+                                            });
+                                            if !write_message(&mut stream_write, bind_slab).await? {
+                                                break 'driver;
+                                            }
+                                        },
+                                        None => {
+                                            error!("npc: peek failed!");
+                                        }
+                                    }
+                                },
+                                tas!(b"pack") | tas!(b"nack") | tas!(b"bind") => {
+                                    debug!("npc_client: pack, nack, or bind");
+                                    let tag = match directive_tag {
+                                        tas!(b"pack") => tas!(b"npc-pack"),
+                                        tas!(b"nack") => tas!(b"npc-nack"),
+                                        tas!(b"bind") => tas!(b"npc-bind"),
+                                        _ => unreachable!(),
+                                    };
+                                    let wire = match directive_tag {
+                                        tas!(b"pack") => NpcWire::Pack(pid),
+                                        tas!(b"nack") => NpcWire::Nack(pid),
+                                        tas!(b"bind") => NpcWire::Bind(pid),
+                                        _ => unreachable!(),
+                                    };
+                                    let poke = if tag == tas!(b"npc-bind") {
+                                        T(&mut slab, &[D(tag), D(pid), directive_cell.tail()])
+                                    } else {
+                                        T(&mut slab, &[D(tag), D(pid)])
+                                    };
+                                    slab.set_root(poke);
+
+                                    handle.poke(wire.to_wire(), slab).await?;
+                                },
+                                _ => {
+                                    debug!("npc_client: unexpected message: {:?}", directive_tag);
+                                },
+                            }
+                        },
+                        Some(Ok(Ok(None))) => {
+                            break 'driver;
+                        },
+                        Some(Err(e)) => {
+                            error!("{e:?}");
+                        },
+                        Some(Ok(Err(e))) => {
+                            error!("{e:?}");
+                        },
+                        None => {
+                            read_message_join_set.spawn(read_message(stream_read_arc.clone()));
+                        }
+                    }
+                },
+                effect_res = handle.next_effect() => {
+                    let mut slab = effect_res?; // Closed error should error driver
+                    let Ok(effect_cell) = unsafe { slab.root() }.as_cell() else {
+                        continue;
+                    };
+                    // TODO: distinguish connections
+                    if unsafe { effect_cell.head().raw_equals(&D(tas!(b"npc"))) } {
+                        slab.set_root(effect_cell.tail());
+                        if !write_message(&mut stream_write, slab).await? {
+                            break 'driver;
+                        }
+                    }
+                }
+            }
+        }
+        Ok(())
+    })
+}
+
+async fn read_message(
+    stream_arc: Arc<Mutex<ReadHalf<UnixStream>>>,
+) -> Result<Option<NounSlab>, NockAppError> {
+    let mut stream = stream_arc.lock_owned().await;
+    let mut size_bytes = [0u8; 8];
+    debug!("Attempting to read message size...");
+    match stream.read_exact(&mut size_bytes).await {
+        Ok(0) => {
+            debug!("Connection closed");
+            return Ok(None);
+        }
+        Ok(size) => {
+            debug!("Read size: {:?}", size);
+        }
+        Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
+            debug!("Connection closed unexpectedly");
+            return Ok(None);
+        }
+        Err(e) => {
+            debug!("Error reading size: {:?}", e);
+            return Err(NockAppError::IoError(e));
+        }
+    }
+    let size = usize::from_le_bytes(size_bytes);
+    debug!("Message size: {} bytes", size);
+    let mut buf = Vec::with_capacity(size).limit(size);
+    while buf.remaining_mut() > 0 {
+        debug!(
+            "Reading message content, {} bytes remaining",
+            buf.remaining_mut()
+        );
+        match stream.read_buf(&mut buf).await {
+            Ok(0) => {
+                debug!("Connection closed while reading message content");
+                return Ok(None);
+            }
+            Ok(_) => {}
+            Err(e) => return Err(NockAppError::IoError(e)),
+        }
+    }
+    debug!("Successfully read entire message");
+    let mut slab = NounSlab::new();
+    let noun = slab.cue_into(Bytes::from(buf.into_inner()))?;
+    slab.set_root(noun);
+    Ok(Some(slab))
+}
+
+async fn write_message(
+    stream: &mut WriteHalf<UnixStream>,
+    msg_slab: NounSlab,
+) -> Result<bool, NockAppError> {
+    let msg_bytes = msg_slab.jam();
+    let msg_len = msg_bytes.len();
+    debug!("Attempting to write message of {} bytes", msg_len);
+    let mut msg_len_bytes = &msg_len.to_le_bytes()[..];
+    let mut msg_buf = &msg_bytes[..];
+    while !msg_len_bytes.is_empty() {
+        debug!(
+            "Writing message length, {} bytes remaining",
+            msg_len_bytes.len()
+        );
+        let bytes = stream
+            .write_buf(&mut msg_len_bytes)
+            .await
+            .map_err(NockAppError::IoError)?;
+        if bytes == 0 {
+            debug!("Wrote 0 bytes for message length, returning false");
+            return Ok(false);
+        }
+    }
+    while !msg_buf.is_empty() {
+        debug!("Writing message content, {} bytes remaining", msg_buf.len());
+        let bytes = stream
+            .write_buf(&mut msg_buf)
+            .await
+            .map_err(NockAppError::IoError)?;
+        if bytes == 0 {
+            debug!("Wrote 0 bytes for message content, returning false");
+            return Ok(false);
+        }
+    }
+    debug!("Successfully wrote entire message");
+    Ok(true)
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::nockapp::driver::{IOAction, NockAppHandle};
+
+    use super::*;
+    use std::io::{Read, Write};
+    use std::os::unix::net::UnixStream as StdUnixStream;
+    use std::time::Duration;
+    use tempfile::tempdir;
+    use tokio::net::UnixStream;
+    use tokio::sync::{broadcast, mpsc};
+    use tokio::time::timeout;
+    use tracing_test::traced_test;
+
+    async fn setup_socket_pair() -> (UnixStream, StdUnixStream) {
+        let dir = tempdir().unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+        let socket_path = dir.path().join("test.sock");
+        let listener = UnixListener::bind(&socket_path).unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+
+        let client = StdUnixStream::connect(&socket_path).unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+        let (server, _) = listener.accept().await.unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+
+        (server, client)
+    }
+
+    #[tokio::test]
+    #[cfg_attr(miri, ignore)]
+    async fn test_write_message_format() {
+        let (server, mut client) = setup_socket_pair().await;
+        let (_, mut writer) = split(server);
+
+        let mut test_slab = NounSlab::new();
+        let test_noun = T(&mut test_slab, &[D(123), D(456)]);
+        test_slab.set_root(test_noun);
+
+        write_message(&mut writer, test_slab)
+            .await
+            .unwrap_or_else(|err| {
+                panic!(
+                    "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            });
+
+        let mut size_buf = [0u8; 8];
+        client.read_exact(&mut size_buf).unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+        let size = usize::from_le_bytes(size_buf);
+
+        let mut msg_buf = vec![0u8; size];
+        client.read_exact(&mut msg_buf).unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+
+        let mut received_slab = NounSlab::new();
+        let received_noun = received_slab
+            .cue_into(Bytes::from(msg_buf))
+            .unwrap_or_else(|err| {
+                panic!(
+                    "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            });
+        received_slab.set_root(received_noun);
+
+        let root = unsafe { received_slab.root() };
+        let cell = root.as_cell().unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+
+        assert_eq!(
+            cell.head()
+                .as_direct()
+                .unwrap_or_else(|err| {
+                    panic!(
+                        "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                        file!(),
+                        line!(),
+                        option_env!("GIT_SHA")
+                    )
+                })
+                .data(),
+            123
+        );
+        assert_eq!(
+            cell.tail()
+                .as_direct()
+                .unwrap_or_else(|err| {
+                    panic!(
+                        "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                        file!(),
+                        line!(),
+                        option_env!("GIT_SHA")
+                    )
+                })
+                .data(),
+            456
+        );
+    }
+
+    #[tokio::test]
+    #[cfg_attr(miri, ignore)]
+    async fn test_write_message_empty() {
+        let (server, mut client) = setup_socket_pair().await;
+        let (_, mut writer) = split(server);
+
+        let mut test_slab = NounSlab::new();
+        let test_noun = T(&mut test_slab, &[D(0), D(0)]);
+        test_slab.set_root(test_noun);
+
+        assert!(write_message(&mut writer, test_slab)
+            .await
+            .unwrap_or_else(|err| {
+                panic!(
+                    "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            }));
+
+        let mut size_buf = [0u8; 8];
+        client.read_exact(&mut size_buf).unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+        assert!(usize::from_le_bytes(size_buf) > 0);
+    }
+
+    #[tokio::test]
+    #[cfg_attr(miri, ignore)]
+    async fn test_read_message_eof() {
+        let (server, client) = setup_socket_pair().await;
+        drop(client);
+
+        let stream_arc = Arc::new(Mutex::new(split(server).0));
+        let result = read_message(stream_arc).await;
+        assert!(result
+            .unwrap_or_else(|err| {
+                panic!(
+                    "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            })
+            .is_none());
+    }
+
+    #[tokio::test]
+    #[traced_test]
+    #[cfg_attr(miri, ignore)]
+    async fn test_npc_driver() {
+        // Setup
+        let dir = tempdir().unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+        let socket_path = dir.path().join("test.sock");
+        let listener = UnixListener::bind(&socket_path).unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+
+        // Create channels for driver communication
+        let (tx_io, mut rx_io) = mpsc::channel(32);
+        let (tx_effect, rx_effect) = broadcast::channel(32);
+        let (tx_exit, _) = mpsc::channel(1);
+
+        let handle = NockAppHandle {
+            io_sender: tx_io,
+            effect_sender: tx_effect.clone(),
+            effect_receiver: Mutex::new(rx_effect),
+            exit: tx_exit,
+        };
+
+        // Spawn the listener driver
+        let _driver_task = tokio::spawn(npc_listener(listener)(handle));
+
+        // Connect client
+        let mut client = StdUnixStream::connect(&socket_path).unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+
+        // Create test noun slab
+        let mut test_slab = NounSlab::new();
+        let msg_noun = T(&mut test_slab, &[D(tas!(b"poke")), D(123), D(456)]);
+        let test_noun = T(&mut test_slab, &[D(1), msg_noun]);
+        test_slab.set_root(test_noun);
+
+        // Jam the noun to bytes
+        let msg_bytes = test_slab.jam();
+        let msg_len = msg_bytes.len();
+
+        // Write length prefix and jammed noun
+        client
+            .write_all(&(msg_len as u64).to_le_bytes())
+            .unwrap_or_else(|err| {
+                panic!(
+                    "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            });
+        client.write_all(&msg_bytes).unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+
+        debug!("client: wrote {} bytes", msg_len);
+
+        // Verify driver received poke
+        if let Some(IOAction::Poke {
+            wire: _wire,
+            poke: noun_slab,
+            ack_channel: _,
+        }) = timeout(Duration::from_secs(1), rx_io.recv())
+            .await
+            .unwrap_or_else(|err| {
+                panic!(
+                    "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            })
+        {
+            debug!("test_npc_driver: poke data: {:?}", unsafe {
+                noun_slab.root()
+            });
+
+            // Verify noun content
+            let noun = unsafe { noun_slab.root() };
+            let noun_cell = noun.as_cell().unwrap_or_else(|err| {
+                panic!(
+                    "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            });
+            assert_eq!(
+                noun_cell
+                    .head()
+                    .as_direct()
+                    .unwrap_or_else(|err| {
+                        panic!(
+                            "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                            file!(),
+                            line!(),
+                            option_env!("GIT_SHA")
+                        )
+                    })
+                    .data(),
+                123
+            );
+            assert_eq!(
+                noun_cell
+                    .tail()
+                    .as_direct()
+                    .unwrap_or_else(|err| {
+                        panic!(
+                            "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                            file!(),
+                            line!(),
+                            option_env!("GIT_SHA")
+                        )
+                    })
+                    .data(),
+                456
+            );
+
+        // TODO: make this work
+        /* ack_channel.send(PokeResult::Ack).unwrap_or_else(|| panic!("Panicked at {}:{} (git sha: {:?})", file!(), line!(), option_env!("GIT_SHA")));
+
+        // Send effect through broadcast channel
+        let mut ack_slab = NounSlab::new();
+        let ack = T(&mut ack_slab.clone(), &[
+            D(tas!(b"npc")),
+            T(&mut ack_slab.clone(), &[D(123), D(tas!(b"pack")), D(0)])
+        ]);
+        ack_slab.set_root(ack);
+        tx_effect.send(ack_slab).unwrap_or_else(|| panic!("Panicked at {}:{} (git sha: {:?})", file!(), line!(), option_env!("GIT_SHA")));
+
+        // Verify client receives ack
+        let mut size_buf = [0u8; 8];
+        client.read_exact(&mut size_buf).unwrap_or_else(|| panic!("Panicked at {}:{} (git sha: {:?})", file!(), line!(), option_env!("GIT_SHA")));
+        let size = usize::from_le_bytes(size_buf);
+
+        let mut msg_buf = vec![0u8; size];
+        client.read_exact(&mut msg_buf).unwrap_or_else(|| panic!("Panicked at {}:{} (git sha: {:?})", file!(), line!(), option_env!("GIT_SHA")));
+
+        let mut received_slab = NounSlab::new();
+        let received_noun = received_slab.cue_into(Bytes::from(msg_buf)).unwrap_or_else(|| panic!("Panicked at {}:{} (git sha: {:?})", file!(), line!(), option_env!("GIT_SHA")));
+        received_slab.set_root(received_noun);
+
+        let root = unsafe { received_slab.root() };
+        let cell = root.as_cell().unwrap_or_else(|| panic!("Panicked at {}:{} (git sha: {:?})", file!(), line!(), option_env!("GIT_SHA")));
+        assert_eq!(cell.head().as_direct().unwrap_or_else(|| panic!("Panicked at {}:{} (git sha: {:?})", file!(), line!(), option_env!("GIT_SHA"))).data(), 123);
+        let rest = cell.tail().as_cell().unwrap_or_else(|| panic!("Panicked at {}:{} (git sha: {:?})", file!(), line!(), option_env!("GIT_SHA")));
+        assert_eq!(rest.head().as_direct().unwrap_or_else(|| panic!("Panicked at {}:{} (git sha: {:?})", file!(), line!(), option_env!("GIT_SHA"))).data(), tas!(b"pack"));
+        assert_eq!(rest.tail().as_direct().unwrap_or_else(|| panic!("Panicked at {}:{} (git sha: {:?})", file!(), line!(), option_env!("GIT_SHA"))).data(), 0); */
+        } else {
+            panic!("Did not receive poke message");
+        }
+
+        // Cleanup
+        drop(client);
+    }
+}

+ 157 - 0
crates/nockapp/crown/src/drivers/one_punch.rs

@@ -0,0 +1,157 @@
+use crate::nockapp::driver::*;
+use crate::nockapp::wire::Wire;
+use crate::nockapp::NockAppError;
+use crate::noun::slab::NounSlab;
+use either::Either::{self, Left, Right};
+use sword::noun::D;
+use sword_macros::tas;
+use tracing::{debug, error, info};
+
+pub enum OnePunchWire {
+    Poke,
+}
+
+impl Wire for OnePunchWire {
+    const VERSION: u64 = 1;
+    const SOURCE: &'static str = "one-punch";
+}
+
+pub fn one_punch_man(data: NounSlab, op: Operation) -> IODriverFn {
+    make_driver(|handle| async move {
+        let wire = OnePunchWire::Poke.to_wire();
+        let result = match op {
+            Operation::Poke => Left(handle.poke(wire, data).await?),
+            Operation::Peek => {
+                debug!("poke_once_driver: peeking with {:?}", data);
+                Right(handle.peek(data).await?)
+            }
+        };
+
+        tokio::select! {
+            res = handle_result(result, &op) => res,
+            eff = handle.next_effect() => {
+                handle_effect(eff, &handle).await
+            },
+            _ = tokio::time::sleep(tokio::time::Duration::from_secs(600)) => {
+                //TODO what is a good timeout for tests?
+                info!("poke_once_driver: no effect received after 10 minutes");
+                Err(NockAppError::Timeout)
+            }
+        }
+    })
+}
+/// Handles the result of a poke or peek operation.
+///
+/// Poke:
+/// - Ack: The poke operation was successful.
+/// - Nack: The poke operation failed.
+///
+/// Peek:
+/// - Some(NounSlab): The peek operation was successful and returned a NounSlab.
+/// - None: The peek operation failed or returned no result.
+///
+/// # Arguments
+///
+/// * `result` - The result of the operation.
+/// * `op` - The operation type (Poke or Peek).
+///
+/// # Returns
+///
+/// A Result indicating success or failure of the operation.
+async fn handle_result(
+    result: Either<PokeResult, Option<NounSlab>>,
+    op: &Operation,
+) -> Result<(), NockAppError> {
+    match op {
+        Operation::Poke => match result {
+            Left(PokeResult::Ack) => {
+                info!("Poke successful");
+                Ok(())
+            }
+            Left(PokeResult::Nack) => {
+                error!("Poke nacked");
+                Err(NockAppError::PokeFailed)
+            }
+            Right(_) => {
+                error!("Unexpected result for poke operation");
+                Err(NockAppError::UnexpectedResult)
+            }
+        },
+        Operation::Peek => match result {
+            Left(_) => {
+                error!("Unexpected result for peek operation");
+                Err(NockAppError::UnexpectedResult)
+            }
+            Right(Some(peek_result)) => {
+                info!("Peek result: {:?}", peek_result);
+                Ok(())
+            }
+            Right(_) => {
+                error!("Peek returned no result");
+                Err(NockAppError::PeekFailed)
+            }
+        },
+    }
+}
+
+/// Handles effects from the kernel.
+///
+/// # Arguments
+///
+/// * `eff` - The effect produced by the kernel.
+/// * `_handle` - The NockAppHandle (unused in this implementation).
+///
+/// # Returns
+///
+/// A Result indicating success or failure of handling the effect.
+async fn handle_effect(
+    eff: Result<NounSlab, NockAppError>,
+    _handle: &NockAppHandle,
+) -> Result<(), NockAppError> {
+    let eff = eff?;
+    debug!("poke_once_driver: effect received: {:?}", eff);
+
+    // Split out root bindings so they don't get dropped early
+    let root = unsafe { eff.root() };
+    debug!("poke_once_driver: root: {:?}", root);
+    let effect_cell = root.as_cell().unwrap_or_else(|err| {
+        panic!(
+            "Panicked with {err:?} at {}:{} (git sha: {:?})",
+            file!(),
+            line!(),
+            option_env!("GIT_SHA")
+        )
+    });
+    if unsafe { effect_cell.head().raw_equals(&D(tas!(b"npc"))) } {
+        let npc_effect = effect_cell.tail();
+        if let Ok(npc_effect_cell) = npc_effect.as_cell() {
+            match npc_effect_cell
+                .head()
+                .as_atom()
+                .unwrap_or_else(|err| {
+                    panic!(
+                        "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                        file!(),
+                        line!(),
+                        option_env!("GIT_SHA")
+                    )
+                })
+                .as_u64()
+                .expect("Failed to convert to u64")
+            {
+                tas!(b"gossip") => {
+                    // Ignore gossip data
+                    info!("Ignoring gossip data");
+                }
+                tas!(b"request") => {
+                    info!("Processing request effect");
+                    let request_data = npc_effect_cell.tail();
+                    info!("Request data: {:?}", request_data);
+                    // handle.poke(create_response(request_data)).await?;
+                }
+                _ => info!("Received unknown npc effect"),
+            }
+        }
+    }
+    Ok(())
+}

+ 29 - 0
crates/nockapp/crown/src/drivers/timer.rs

@@ -0,0 +1,29 @@
+use crate::nockapp::driver::*;
+use crate::nockapp::wire::Wire;
+use crate::noun::slab::NounSlab;
+use std::time::Duration;
+use tokio::time;
+
+pub enum TimerWire {
+    Tick,
+}
+
+impl Wire for TimerWire {
+    const VERSION: u64 = 1;
+    const SOURCE: &'static str = "timer";
+}
+
+pub fn make_timer_driver(interval_secs: u64, timer_slab: NounSlab) -> IODriverFn {
+    make_driver(move |handle| async move {
+        let mut timer_interval = time::interval(Duration::from_secs(interval_secs));
+
+        loop {
+            tokio::select! {
+                _ = timer_interval.tick() => {
+                    let wire = TimerWire::Tick.to_wire();
+                    handle.poke(wire, timer_slab.clone()).await?;
+                }
+            }
+        }
+    })
+}

+ 301 - 0
crates/nockapp/crown/src/kernel/boot.rs

@@ -0,0 +1,301 @@
+use crate::kernel::checkpoint::JamPaths;
+use crate::kernel::form::Kernel;
+use crate::{default_data_dir, NockApp};
+use chrono;
+use clap::{arg, command, ColorChoice, Parser};
+use std::fs;
+use std::path::PathBuf;
+use sword::jets::hot::HotEntry;
+use tracing::{debug, info, Level};
+use tracing_subscriber::fmt::format::Writer;
+use tracing_subscriber::fmt::{FmtContext, FormatEvent, FormatFields};
+use tracing_subscriber::layer::SubscriberExt;
+use tracing_subscriber::registry::LookupSpan;
+use tracing_subscriber::util::SubscriberInitExt;
+use tracing_subscriber::{fmt, EnvFilter};
+
+#[derive(Parser, Debug, Clone)]
+#[command(about = "boot a nockapp", author, version, color = ColorChoice::Auto)]
+pub struct Cli {
+    #[arg(
+        long,
+        help = "Start with a new data directory, removing any existing data",
+        default_value = "false"
+    )]
+    pub new: bool,
+
+    #[arg(long, help = "Make an Sword trace", default_value = "false")]
+    pub trace: bool,
+
+    #[arg(
+        long,
+        default_value = "1000",
+        help = "Set the save interval for checkpoints (in ms)"
+    )]
+    pub save_interval: u64,
+
+    #[arg(long, help = "Control colored output", value_enum, default_value_t = ColorChoice::Auto)]
+    pub color: ColorChoice,
+
+    #[arg(
+        long,
+        help = "Path to a jam file containing existing kernel state. Supports both JammedCheckpoint and ExportedState formats."
+    )]
+    pub state_jam: Option<String>,
+
+    #[arg(
+        long,
+        help = "Path to export the kernel state as a jam file in the ExportedState format."
+    )]
+    pub export_state_jam: Option<String>,
+}
+
+/// Result of setting up a NockApp
+pub enum SetupResult {
+    /// A fully initialized NockApp
+    App(NockApp),
+    /// State was exported successfully
+    ExportedState,
+}
+
+pub fn default_boot_cli(new: bool) -> Cli {
+    Cli {
+        save_interval: 1000,
+        new,
+        trace: false,
+        color: ColorChoice::Auto,
+        state_jam: None,
+        export_state_jam: None,
+    }
+}
+
+/// A minimal event formatter for development mode
+struct MinimalFormatter;
+
+impl<S, N> FormatEvent<S, N> for MinimalFormatter
+where
+    S: tracing::Subscriber + for<'a> LookupSpan<'a>,
+    N: for<'a> FormatFields<'a> + 'static,
+{
+    fn format_event(
+        &self,
+        ctx: &FmtContext<'_, S, N>,
+        mut writer: Writer<'_>,
+        event: &tracing::Event<'_>,
+    ) -> std::fmt::Result {
+        let level = *event.metadata().level();
+        let level_str = match level {
+            Level::TRACE => "\x1B[36mT\x1B[0m",
+            Level::DEBUG => "\x1B[34mD\x1B[0m",
+            Level::INFO => "\x1B[32mI\x1B[0m",
+            Level::WARN => "\x1B[33mW\x1B[0m",
+            Level::ERROR => "\x1B[31mE\x1B[0m",
+        };
+
+        // Get level color code for potential use with slogger
+        let level_color = match level {
+            Level::TRACE => "\x1B[36m", // Cyan
+            Level::DEBUG => "\x1B[34m", // Blue
+            Level::INFO => "\x1B[32m",  // Green
+            Level::WARN => "\x1B[33m",  // Yellow
+            Level::ERROR => "\x1B[31m", // Red
+        };
+
+        write!(writer, "{} ", level_str)?;
+
+        // simple, shorter timestamp (HH:mm:ss)
+        let now = chrono::Local::now();
+        let time_str = now.format("%H:%M:%S").to_string();
+        write!(writer, "\x1B[38;5;246m({time_str})\x1B[0m ")?;
+
+        let target = event.metadata().target();
+
+        // Special handling for slogger
+        if target == "slogger" {
+            // For slogger, omit the target prefix and color the message with the log level color
+            // this mimics the behavior of slogging in urbit
+            write!(writer, "{}", level_color)?;
+            ctx.field_format().format_fields(writer.by_ref(), event)?;
+            write!(writer, "\x1B[0m")?;
+
+            return writeln!(writer);
+        }
+
+        let simplified_target = if target.contains("::") {
+            // Just take the last component of the module path
+            let parts: Vec<&str> = target.split("::").collect();
+            if parts.len() > 1 {
+                // If we have a structure like "a::b::c::d", just take "c::d"
+                // but prefix it with the first two characters of the first part
+                // i.e, crown::kernel::boot -> [cr] kernel::boot
+                if parts.len() > 2 {
+                    format!(
+                        "[{}] {}::{}",
+                        parts[0].chars().take(2).collect::<String>(),
+                        parts[parts.len() - 2],
+                        parts[parts.len() - 1]
+                    )
+                } else {
+                    parts
+                        .last()
+                        .unwrap_or_else(|| {
+                            panic!(
+                                "Panicked at {}:{} (git sha: {:?})",
+                                file!(),
+                                line!(),
+                                option_env!("GIT_SHA")
+                            )
+                        })
+                        .to_string()
+                }
+            } else {
+                target.to_string()
+            }
+        } else {
+            target.to_string()
+        };
+
+        // Write the simplified target in grey and italics
+        write!(writer, "\x1B[3;90m{}\x1B[0m: ", simplified_target)?;
+
+        // Write the fields (the actual log message)
+        ctx.field_format().format_fields(writer.by_ref(), event)?;
+
+        writeln!(writer)
+    }
+}
+
+/// Initialize tracing with appropriate configuration based on CLI arguments.
+///
+/// This function sets up logging with different profiles:
+/// - Production mode: Full verbose logging as specified by log_level
+/// - Development mode: Cleaner, less noisy logging focused on application code
+///
+/// In development mode, the base filter is set to INFO level, with application
+/// modules set to DEBUG. Additional modules can be specified with dev_modules.
+pub fn init_default_tracing(cli: &Cli) {
+    let filter = EnvFilter::new(std::env::var("RUST_LOG").unwrap_or_else(|_| "trace".to_string()));
+    let use_ansi = cli.color == ColorChoice::Auto || cli.color == ColorChoice::Always;
+
+    // Build and initialize the subscriber based on format and mode
+    match std::env::var("MINIMAL_LOG_FORMAT").unwrap_or_else(|_| "false".to_string()) == "true" {
+        // Default pretty format for production
+        false => {
+            tracing_subscriber::registry()
+                .with(
+                    fmt::layer()
+                        .with_ansi(use_ansi)
+                        .with_target(true)
+                        .with_level(true),
+                )
+                .with(filter)
+                .init();
+        }
+        // Development mode with minimal formatter
+        true => {
+            let fmt_layer = fmt::layer()
+                .with_ansi(use_ansi)
+                .event_format(MinimalFormatter);
+
+            tracing_subscriber::registry()
+                .with(fmt_layer)
+                .with(filter)
+                .init();
+        }
+    }
+}
+
+pub async fn setup(
+    jam: &[u8],
+    cli: Option<Cli>,
+    hot_state: &[HotEntry],
+    name: &str,
+    data_dir: Option<PathBuf>,
+) -> Result<NockApp, Box<dyn std::error::Error>> {
+    let result = setup_(
+        jam,
+        cli.unwrap_or_else(|| default_boot_cli(false)),
+        hot_state,
+        name,
+        data_dir,
+    )
+    .await?;
+    match result {
+        SetupResult::App(app) => Ok(app),
+        SetupResult::ExportedState => {
+            info!("Exiting after successful state export");
+            std::process::exit(0);
+        }
+    }
+}
+
+pub async fn setup_(
+    jam: &[u8],
+    cli: Cli,
+    hot_state: &[HotEntry],
+    name: &str,
+    data_dir: Option<PathBuf>,
+) -> Result<SetupResult, Box<dyn std::error::Error>> {
+    let data_dir = if let Some(data_path) = data_dir.clone() {
+        data_path.join(name)
+    } else {
+        default_data_dir(name)
+    };
+    let pma_dir = data_dir.join("pma");
+    let jams_dir = data_dir.join("checkpoints");
+
+    if !jams_dir.exists() {
+        std::fs::create_dir_all(&jams_dir)?;
+        info!("Created jams directory: {:?}", jams_dir);
+    }
+
+    if pma_dir.exists() {
+        std::fs::remove_dir_all(&pma_dir)?;
+        info!("Deleted existing pma directory: {:?}", pma_dir);
+    }
+
+    if cli.new && jams_dir.exists() {
+        std::fs::remove_dir_all(&jams_dir)?;
+        info!("Deleted existing checkpoint directory: {:?}", jams_dir);
+    }
+
+    let jam_paths = JamPaths::new(&jams_dir);
+    info!("kernel: starting");
+    debug!("kernel: pma directory: {:?}", pma_dir);
+    debug!(
+        "kernel: jam buffer paths: {:?}, {:?}",
+        jam_paths.0, jam_paths.1
+    );
+
+    let mut kernel = if let Some(state_path) = cli.state_jam {
+        let state_bytes = fs::read(&state_path)?;
+        debug!("kernel: loading state from jam file: {:?}", state_path);
+        Kernel::load_with_kernel_state(pma_dir, jam_paths, jam, &state_bytes, hot_state, cli.trace)
+            .await?
+    } else {
+        Kernel::load_with_hot_state(pma_dir, jam_paths, jam, hot_state, cli.trace).await?
+    };
+
+    if let Some(export_path) = cli.export_state_jam.clone() {
+        export_kernel_state(&mut kernel, &export_path).await?;
+        return Ok(SetupResult::ExportedState);
+    }
+
+    let save_interval = std::time::Duration::from_millis(cli.save_interval);
+
+    let app = NockApp::new(kernel, save_interval).await;
+
+    Ok(SetupResult::App(app))
+}
+
+/// Exports the kernel state to a jam file at the specified path
+async fn export_kernel_state(
+    kernel: &mut Kernel,
+    export_path: &str,
+) -> Result<(), Box<dyn std::error::Error>> {
+    info!("Extracting kernel state to file: {:?}", export_path);
+    let state_bytes = kernel.create_state_bytes().await?;
+    fs::write(export_path, state_bytes)?;
+    info!("Successfully exported kernel state to: {:?}", export_path);
+    Ok(())
+}

+ 254 - 0
crates/nockapp/crown/src/kernel/checkpoint.rs

@@ -0,0 +1,254 @@
+use crate::{JammedNoun, NounExt};
+use bincode::config::{self, Configuration};
+use bincode::{encode_to_vec, Decode, Encode};
+use blake3::{Hash, Hasher};
+use bytes::Bytes;
+use std::path::{Path, PathBuf};
+use sword::jets::cold::{Cold, Nounable};
+use sword::mem::NockStack;
+use sword::noun::{Noun, T};
+use sword_macros::tas;
+use thiserror::Error;
+use tracing::{debug, error, info, warn};
+
+#[derive(Clone)]
+pub struct Checkpoint {
+    /// Magic bytes to identify checkpoint format
+    pub magic_bytes: u64,
+    /// Version of checkpoint
+    pub version: u32,
+    /// The buffer that this checkpoint was saved to, either 0 or 1.
+    pub buff_index: bool,
+    /// Hash of the boot kernel
+    pub ker_hash: Hash,
+    /// Event number
+    pub event_num: u64,
+    /// State of the kernel
+    pub ker_state: Noun,
+    /// Cold state
+    pub cold: Cold,
+}
+
+impl std::fmt::Debug for Checkpoint {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("Checkpoint")
+            .field("magic_bytes", &self.magic_bytes)
+            .field("version", &self.version)
+            .field("buff_index", &self.buff_index)
+            .field("ker_hash", &self.ker_hash)
+            .field("event_num", &self.event_num)
+            .field("ker_state", &self.ker_state)
+            .finish()
+    }
+}
+
+impl Checkpoint {
+    pub fn load(stack: &mut NockStack, jam: JammedCheckpoint) -> Result<Self, CheckpointError> {
+        let cell = <Noun as NounExt>::cue_bytes(stack, &jam.jam.0)
+            .map_err(|_| CheckpointError::SwordInterpreterError)?
+            .as_cell()?;
+
+        let cold_mem = Cold::from_noun(stack, &cell.tail())?;
+        let cold = Cold::from_vecs(stack, cold_mem.0, cold_mem.1, cold_mem.2);
+
+        Ok(Self {
+            magic_bytes: jam.magic_bytes,
+            version: jam.version,
+            buff_index: jam.buff_index,
+            ker_hash: jam.ker_hash,
+            event_num: jam.event_num,
+            ker_state: cell.head(),
+            cold,
+        })
+    }
+}
+
+#[derive(Encode, Decode, PartialEq, Debug)]
+pub struct JammedCheckpoint {
+    /// Magic bytes to identify checkpoint format
+    pub magic_bytes: u64,
+    /// Version of checkpoint
+    pub version: u32,
+    /// The buffer this checkpoint was saved to, either 0 or 1
+    pub buff_index: bool,
+    /// Hash of the boot kernel
+    #[bincode(with_serde)]
+    pub ker_hash: Hash,
+    /// Checksum derived from event_num and jam (the entries below)
+    #[bincode(with_serde)]
+    pub checksum: Hash,
+    /// Event number
+    pub event_num: u64,
+    /// Jammed noun of [kernel_state cold_state]
+    pub jam: JammedNoun,
+}
+
+/// A structure for exporting just the kernel state, without the cold state
+#[derive(Encode, Decode, PartialEq, Debug)]
+pub struct ExportedState {
+    /// Magic bytes to identify exported state format
+    pub magic_bytes: u64,
+    /// Version of exported state
+    pub version: u32,
+    /// Hash of the boot kernel
+    #[bincode(with_serde)]
+    pub ker_hash: Hash,
+    /// Event number
+    pub event_num: u64,
+    /// Jammed noun of kernel_state
+    pub jam: JammedNoun,
+}
+
+impl ExportedState {
+    pub fn new(
+        stack: &mut NockStack,
+        version: u32,
+        ker_hash: Hash,
+        event_num: u64,
+        ker_state: &Noun,
+    ) -> Self {
+        let jam = JammedNoun::from_noun(stack, *ker_state);
+        Self {
+            magic_bytes: tas!(b"EXPJAM"),
+            version,
+            ker_hash,
+            event_num,
+            jam,
+        }
+    }
+
+    pub fn encode(&self) -> Result<Vec<u8>, bincode::error::EncodeError> {
+        encode_to_vec(self, config::standard())
+    }
+}
+
+impl JammedCheckpoint {
+    pub fn new(
+        stack: &mut NockStack,
+        version: u32,
+        buff_index: bool,
+        ker_hash: Hash,
+        event_num: u64,
+        cold: &Cold,
+        ker_state: &Noun,
+    ) -> Self {
+        let cold_noun = (*cold).into_noun(stack);
+        let cell = T(stack, &[*ker_state, cold_noun]);
+        let jam = JammedNoun::from_noun(stack, cell);
+        let checksum = Self::checksum(event_num, &jam.0);
+        Self {
+            magic_bytes: tas!(b"CHKJAM"),
+            version,
+            buff_index,
+            ker_hash,
+            checksum,
+            event_num,
+            jam,
+        }
+    }
+    pub fn validate(&self) -> bool {
+        self.checksum == Self::checksum(self.event_num, &self.jam.0)
+    }
+    pub fn encode(&self) -> Result<Vec<u8>, bincode::error::EncodeError> {
+        encode_to_vec(self, config::standard())
+    }
+    fn checksum(event_num: u64, jam: &Bytes) -> Hash {
+        let jam_len = jam.len();
+        let mut hasher = Hasher::new();
+        hasher.update(&event_num.to_le_bytes());
+        hasher.update(&jam_len.to_le_bytes());
+        hasher.update(jam);
+        hasher.finalize()
+    }
+}
+
+#[derive(Error, Debug)]
+pub enum CheckpointError<'a> {
+    #[error("IO error: {0}")]
+    IOError(#[from] std::io::Error),
+    #[error("Bincode error: {0}")]
+    DecodeError(#[from] bincode::error::DecodeError),
+    #[error("Invalid checksum at {0}")]
+    InvalidChecksum(&'a PathBuf),
+    #[error("Sword noun error: {0}")]
+    SwordNounError(#[from] sword::noun::Error),
+    #[error("Sword cold error: {0}")]
+    FromNounError(#[from] sword::jets::cold::FromNounError),
+    #[error("Both checkpoints failed: {0}, {1}")]
+    BothCheckpointsFailed(Box<CheckpointError<'a>>, Box<CheckpointError<'a>>),
+    #[error("Sword interpreter error")]
+    SwordInterpreterError,
+}
+
+#[derive(Debug, Clone)]
+pub struct JamPaths(pub PathBuf, pub PathBuf);
+
+impl JamPaths {
+    pub fn new(dir: &Path) -> Self {
+        let path_0 = dir.join("0.chkjam");
+        let path_1 = dir.join("1.chkjam");
+        Self(path_0, path_1)
+    }
+
+    pub fn checkpoint_exists(&self) -> bool {
+        self.0.exists() || self.1.exists()
+    }
+
+    // TODO return checkpoint and which buffer is being loaded so we can set the buffer toggle
+    pub fn load_checkpoint<'a>(
+        &'a self,
+        stack: &'a mut NockStack,
+    ) -> Result<Checkpoint, CheckpointError<'a>> {
+        let (chk_0, chk_1) = [&self.0, &self.1].map(Self::decode_jam).into();
+
+        match (chk_0, chk_1) {
+            (Ok(a), Ok(b)) => {
+                let chosen = if a.event_num > b.event_num {
+                    debug!(
+                        "Loading checkpoint at: {}, checksum: {}",
+                        self.0.display(),
+                        a.checksum
+                    );
+                    a
+                } else {
+                    debug!(
+                        "Loading checkpoint at: {}, checksum: {}",
+                        self.1.display(),
+                        b.checksum
+                    );
+                    b
+                };
+                Checkpoint::load(stack, chosen)
+            }
+            (Ok(c), Err(e)) | (Err(e), Ok(c)) => {
+                warn!("{e}");
+                info!("Loading checkpoint, checksum: {}", c.checksum);
+                Checkpoint::load(stack, c)
+            }
+            (Err(e1), Err(e2)) => {
+                error!("{e1}");
+                error!("{e2}");
+                // TODO: Why is this a panic?
+                // panic!("Error loading both checkpoints");
+                Err(CheckpointError::BothCheckpointsFailed(
+                    Box::new(e1),
+                    Box::new(e2),
+                ))
+            }
+        }
+    }
+
+    pub fn decode_jam(jam_path: &PathBuf) -> Result<JammedCheckpoint, CheckpointError> {
+        let jam: Vec<u8> = std::fs::read(jam_path.as_path())?;
+
+        let config = bincode::config::standard();
+        let (checkpoint, _) =
+            bincode::decode_from_slice::<JammedCheckpoint, Configuration>(&jam, config)?;
+
+        if checkpoint.validate() {
+            Ok(checkpoint)
+        } else {
+            Err(CheckpointError::InvalidChecksum(jam_path))
+        }
+    }
+}

+ 1200 - 0
crates/nockapp/crown/src/kernel/form.rs

@@ -0,0 +1,1200 @@
+#![allow(dead_code)]
+use crate::noun::slab::NounSlab;
+use blake3::{Hash, Hasher};
+use byteorder::{LittleEndian, WriteBytesExt};
+use std::any::Any;
+use std::fs::File;
+use std::path::PathBuf;
+use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
+use std::sync::Arc;
+use std::time::Instant;
+use sword::hamt::Hamt;
+use sword::interpreter::{self, interpret, Error, Mote};
+use sword::jets::cold::{Cold, Nounable};
+use sword::jets::hot::{HotEntry, URBIT_HOT_STATE};
+use sword::jets::nock::util::mook;
+use sword::mem::NockStack;
+use sword::mug::met3_usize;
+use sword::noun::{Atom, Cell, DirectAtom, IndirectAtom, Noun, Slots, D, T};
+use sword::trace::{path_to_cord, write_serf_trace_safe, TraceInfo};
+use sword_macros::tas;
+use tracing::{debug, error, info, warn};
+
+use crate::kernel::checkpoint::{Checkpoint, ExportedState, JamPaths, JammedCheckpoint};
+use crate::nockapp::wire::{wire_to_noun, WireRepr};
+use crate::noun::slam;
+use crate::utils::{create_context, current_da, NOCK_STACK_SIZE};
+use crate::{AtomExt, CrownError, NounExt, Result, ToBytesExt};
+use bincode::config::Configuration;
+
+use tokio::sync::{mpsc, oneshot};
+use tokio::time::Duration;
+
+pub(crate) const STATE_AXIS: u64 = 6;
+const LOAD_AXIS: u64 = 4;
+const PEEK_AXIS: u64 = 22;
+const POKE_AXIS: u64 = 23;
+
+const SNAPSHOT_VERSION: u32 = 0;
+
+const SERF_FINISHED_INTERVAL: Duration = Duration::from_millis(100);
+const SERF_THREAD_STACK_SIZE: usize = 8 * 1024 * 1024; // 8MB
+
+// Actions to request of the serf thread
+pub enum SerfAction {
+    // Extract this state into the serf
+    LoadState {
+        state: Vec<u8>,
+        result: oneshot::Sender<Result<()>>,
+    },
+    // Make a CheckPoint
+    Checkpoint {
+        result: oneshot::Sender<JammedCheckpoint>,
+    },
+    // Get the state of the serf
+    GetStateBytes {
+        result: oneshot::Sender<Result<Vec<u8>>>,
+    },
+    // Get the state noun of the kernel as a slab
+    GetKernelStateSlab {
+        result: oneshot::Sender<Result<NounSlab>>,
+    },
+    // Get the cold state as a NounSlab
+    GetColdStateSlab {
+        result: oneshot::Sender<NounSlab>,
+    },
+    // Run a peek
+    Peek {
+        ovo: NounSlab,
+        result: oneshot::Sender<Result<NounSlab>>,
+    },
+    // Run a poke
+    //
+    // TODO: send back the event number after each poke
+    Poke {
+        wire: WireRepr,
+        cause: NounSlab,
+        result: oneshot::Sender<Result<NounSlab>>,
+    },
+    // Stop the loop
+    Stop,
+}
+
+pub(crate) struct SerfThread {
+    handle: std::thread::JoinHandle<()>,
+    action_sender: mpsc::Sender<SerfAction>,
+    /// Jam persistence buffer paths.
+    pub jam_paths: Arc<JamPaths>,
+    /// Buffer toggle for writing to the jam buffer.
+    pub buffer_toggle: Arc<AtomicBool>,
+    pub event_number: Arc<AtomicU64>,
+}
+
+impl SerfThread {
+    async fn new(
+        nock_stack_size: usize,
+        jam_paths: Arc<JamPaths>,
+        kernel_bytes: Vec<u8>,
+        constant_hot_state: Vec<HotEntry>,
+        trace: bool,
+    ) -> Result<Self> {
+        let jam_paths_cloned = jam_paths.clone();
+        let (action_sender, action_receiver) = mpsc::channel(1);
+        let (buffer_toggle_sender, buffer_toggle_receiver) = oneshot::channel();
+        let (event_number_sender, event_number_receiver) = oneshot::channel();
+        std::fs::create_dir_all(jam_paths.0.parent().unwrap_or_else(|| {
+            panic!(
+                "Panicked at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        }))
+        .unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+        let handle = std::thread::Builder::new()
+            .name("serf".to_string())
+            .stack_size(SERF_THREAD_STACK_SIZE)
+            .spawn(move || {
+                let mut stack = NockStack::new(nock_stack_size, 0);
+                let checkpoint = if jam_paths.checkpoint_exists() {
+                    info!("Checkpoint file(s) found, validating and loading from jam");
+                    jam_paths.load_checkpoint(&mut stack).ok()
+                } else {
+                    info!("No checkpoint file found, starting from scratch");
+                    None
+                };
+                let buffer_toggle = Arc::new(AtomicBool::new(
+                    checkpoint
+                        .as_ref()
+                        .map_or_else(|| false, |checkpoint| !checkpoint.buff_index),
+                ));
+                buffer_toggle_sender
+                    .send(buffer_toggle.clone())
+                    .expect("Could not send buffer toggle out of serf thread");
+                let serf = Serf::new(stack, checkpoint, &kernel_bytes, &constant_hot_state, trace);
+                event_number_sender
+                    .send(serf.event_num.clone())
+                    .expect("Could not send event number out of serf thread");
+                serf_loop(serf, action_receiver, buffer_toggle);
+            })?;
+
+        let buffer_toggle = buffer_toggle_receiver.await?;
+        let event_number = event_number_receiver.await?;
+        Ok(SerfThread {
+            buffer_toggle,
+            handle,
+            action_sender,
+            jam_paths: jam_paths_cloned,
+            event_number,
+        })
+    }
+
+    // Future which completes when the serf thread finishes. Since rust threads only support polling or blocking joining, not notification,
+    // we have to poll on a timer.
+    pub async fn finished(&self) {
+        let mut interval = tokio::time::interval(Duration::from_millis(100));
+        loop {
+            interval.tick().await;
+            if self.handle.is_finished() {
+                info!("Serf finished");
+                break;
+            }
+        }
+    }
+
+    pub async fn stop(&self) {
+        self.action_sender
+            .send(SerfAction::Stop)
+            .await
+            .expect("Failed to send stop action");
+        self.finished().await;
+    }
+
+    pub fn join(self) -> Result<(), Box<dyn Any + Send + 'static>> {
+        self.handle.join()
+    }
+
+    pub(crate) async fn get_kernel_state_slab(&self) -> Result<NounSlab> {
+        let (result, result_fut) = oneshot::channel();
+        self.action_sender
+            .send(SerfAction::GetKernelStateSlab { result })
+            .await?;
+        result_fut.await?
+    }
+
+    pub(crate) async fn get_cold_state_slab(&self) -> Result<NounSlab> {
+        let (result, result_fut) = oneshot::channel();
+        self.action_sender
+            .send(SerfAction::GetColdStateSlab { result })
+            .await?;
+        Ok(result_fut.await?)
+    }
+
+    pub async fn peek(&self, ovo: NounSlab) -> Result<NounSlab> {
+        let (result, result_fut) = oneshot::channel();
+        self.action_sender
+            .send(SerfAction::Peek { ovo, result })
+            .await?;
+        result_fut.await?
+    }
+
+    pub async fn poke(&self, wire: WireRepr, cause: NounSlab) -> Result<NounSlab> {
+        let (result, result_fut) = oneshot::channel();
+        self.action_sender
+            .send(SerfAction::Poke {
+                wire,
+                cause,
+                result,
+            })
+            .await?;
+        result_fut.await?
+    }
+
+    pub fn poke_sync(&self, wire: WireRepr, cause: NounSlab) -> Result<NounSlab> {
+        let (result, result_fut) = oneshot::channel();
+        self.action_sender.blocking_send(SerfAction::Poke {
+            wire,
+            cause,
+            result,
+        })?;
+        result_fut.blocking_recv()?
+    }
+
+    pub fn peek_sync(&self, ovo: NounSlab) -> Result<NounSlab> {
+        let (result, result_fut) = oneshot::channel();
+        self.action_sender
+            .blocking_send(SerfAction::Peek { ovo, result })?;
+        result_fut.blocking_recv()?
+    }
+
+    pub async fn load_state_from_bytes(&self, state: Vec<u8>) -> Result<()> {
+        let (result, result_fut) = oneshot::channel();
+        self.action_sender
+            .send(SerfAction::LoadState { state, result })
+            .await?;
+        result_fut.await?
+    }
+
+    pub async fn create_state_bytes(&self) -> Result<Vec<u8>> {
+        let (result, result_fut) = oneshot::channel();
+        self.action_sender
+            .send(SerfAction::GetStateBytes { result })
+            .await?;
+        result_fut.await?
+    }
+
+    pub async fn checkpoint(&self) -> Result<JammedCheckpoint> {
+        let (result, result_fut) = oneshot::channel();
+        self.action_sender
+            .send(SerfAction::Checkpoint { result })
+            .await?;
+        Ok(result_fut.await?)
+    }
+}
+
+fn load_state_from_bytes(serf: &mut Serf, state_bytes: &[u8]) -> Result<()> {
+    let noun = extract_state_from_bytes(serf.stack(), state_bytes)?;
+    serf.load(noun)?;
+    Ok(())
+}
+
+fn serf_loop(
+    mut serf: Serf,
+    mut action_receiver: mpsc::Receiver<SerfAction>,
+    buffer_toggle: Arc<AtomicBool>,
+) {
+    loop {
+        let Some(action) = action_receiver.blocking_recv() else {
+            break;
+        };
+        match action {
+            SerfAction::LoadState { state, result } => {
+                let res = load_state_from_bytes(&mut serf, &state);
+                result.send(res).expect("Could not send load state result");
+            }
+            SerfAction::Stop => {
+                break;
+            }
+            SerfAction::GetStateBytes { result } => {
+                let state_bytes = create_state_bytes(&mut serf);
+                result
+                    .send(state_bytes)
+                    .expect("Could not send state bytes");
+            }
+            SerfAction::GetKernelStateSlab { result } => {
+                let kernel_state_noun = serf.arvo.slot(STATE_AXIS);
+                let kernel_state_slab = kernel_state_noun.map_or_else(
+                    |err| Err(CrownError::from(err)),
+                    |noun| {
+                        let mut slab = NounSlab::new();
+                        slab.copy_into(noun);
+                        Ok(slab)
+                    },
+                );
+                result
+                    .send(kernel_state_slab)
+                    .expect("Could not send kernel state slab");
+            }
+            SerfAction::GetColdStateSlab { result } => {
+                let cold_state_noun = serf.context.cold.into_noun(serf.stack());
+                let cold_state_slab = {
+                    let mut slab = NounSlab::new();
+                    slab.copy_into(cold_state_noun);
+                    slab
+                };
+                result
+                    .send(cold_state_slab)
+                    .expect("Could not send cold state slab");
+            }
+            SerfAction::Checkpoint { result } => {
+                let checkpoint = create_checkpoint(&mut serf, buffer_toggle.clone());
+                result.send(checkpoint).expect("Could not send checkpoint");
+            }
+            SerfAction::Peek { ovo, result } => {
+                let ovo_noun = ovo.copy_to_stack(serf.stack());
+                let noun_res = serf.peek(ovo_noun);
+                let noun_slab_res = noun_res.map(|noun| {
+                    let mut slab = NounSlab::new();
+                    slab.copy_into(noun);
+                    slab
+                });
+                result
+                    .send(noun_slab_res)
+                    .expect("Failed to send peek result from serf thread");
+            }
+            SerfAction::Poke {
+                wire,
+                cause,
+                result,
+            } => {
+                let cause_noun = cause.copy_to_stack(serf.stack());
+                let noun_res = serf.poke(wire, cause_noun);
+                let noun_slab_res = noun_res.map(|noun| {
+                    let mut slab = NounSlab::new();
+                    slab.copy_into(noun);
+                    slab
+                });
+                result
+                    .send(noun_slab_res)
+                    .expect("Failed to send poke result from serf thread");
+            }
+        }
+    }
+}
+
+/// Extracts the kernel state from a jammed checkpoint or exported state
+fn extract_state_from_bytes(stack: &mut NockStack, state_bytes: &[u8]) -> Result<Noun> {
+    // First try to decode as JammedCheckpoint
+    match extract_from_checkpoint(stack, state_bytes) {
+        Ok(noun) => {
+            info!("Successfully loaded state from JammedCheckpoint format");
+            Ok(noun)
+        }
+        Err(e1) => {
+            debug!("Failed to load as JammedCheckpoint: {}", e1);
+
+            // Then try to decode as ExportedState
+            match extract_from_exported_state(stack, state_bytes) {
+                Ok(noun) => {
+                    info!("Successfully loaded state from ExportedState format");
+                    Ok(noun)
+                }
+                Err(e2) => {
+                    warn!("Failed to load as ExportedState: {}", e2);
+                    warn!("State bytes format is not recognized");
+                    Err(CrownError::StateJamFormatError)
+                }
+            }
+        }
+    }
+}
+
+/// Extracts the kernel state from an ExportedState
+fn extract_from_exported_state(stack: &mut NockStack, state_jam: &[u8]) -> Result<Noun> {
+    let config = bincode::config::standard();
+
+    // Try to decode as ExportedState
+    let (exported, _) = bincode::decode_from_slice::<ExportedState, Configuration>(
+        state_jam, config,
+    )
+    .map_err(|e| {
+        debug!("Failed to decode state jam as ExportedState: {}", e);
+        CrownError::StateJamFormatError
+    })?;
+
+    // Verify the magic bytes
+    if exported.magic_bytes != tas!(b"EXPJAM") {
+        debug!("Invalid magic bytes for ExportedState: expected EXPJAM");
+        return Err(CrownError::StateJamFormatError);
+    }
+
+    // Extract the kernel state from the jammed noun
+    let noun = <Noun as NounExt>::cue_bytes(stack, &exported.jam.0).map_err(|e| {
+        warn!("Failed to cue bytes from exported state jam: {:?}", e);
+        CrownError::StateJamFormatError
+    })?;
+
+    debug!("Successfully extracted kernel state from ExportedState");
+    Ok(noun)
+}
+
+/// Extracts the kernel state from a JammedCheckpoint
+fn extract_from_checkpoint(stack: &mut NockStack, state_jam: &[u8]) -> Result<Noun> {
+    let config = bincode::config::standard();
+
+    // Try to decode as JammedCheckpoint
+    let (checkpoint, _) = bincode::decode_from_slice::<JammedCheckpoint, Configuration>(
+        state_jam, config,
+    )
+    .map_err(|e| {
+        debug!("Failed to decode state jam as JammedCheckpoint: {}", e);
+        CrownError::StateJamFormatError
+    })?;
+
+    // Verify the magic bytes
+    if checkpoint.magic_bytes != tas!(b"CHKJAM") {
+        debug!("Invalid magic bytes for JammedCheckpoint: expected CHKJAM");
+        return Err(CrownError::StateJamFormatError);
+    }
+
+    // Validate the checkpoint
+    if !checkpoint.validate() {
+        warn!("Checkpoint validation failed");
+        return Err(CrownError::StateJamFormatError);
+    }
+
+    // Extract the kernel state from the jammed noun
+    let cell = <Noun as NounExt>::cue_bytes(stack, &checkpoint.jam.0)
+        .map_err(|e| {
+            warn!("Failed to cue bytes from checkpoint jam: {:?}", e);
+            CrownError::StateJamFormatError
+        })?
+        .as_cell()
+        .map_err(|e| {
+            warn!("Failed to convert noun to cell: {}", e);
+            CrownError::StateJamFormatError
+        })?;
+
+    // The kernel state is the head of the cell
+    debug!("Successfully extracted kernel state from JammedCheckpoint");
+    Ok(cell.head())
+}
+
+/// Creates a serialized byte array from the current kernel state
+fn create_state_bytes(serf: &mut Serf) -> Result<Vec<u8>> {
+    let version = serf.version;
+    let ker_hash = serf.ker_hash;
+    let event_num = serf.event_num.load(Ordering::SeqCst);
+    let ker_state = serf.arvo.slot(STATE_AXIS).unwrap_or_else(|err| {
+        panic!(
+            "Panicked with {err:?} at {}:{} (git sha: {:?})",
+            file!(),
+            line!(),
+            option_env!("GIT_SHA")
+        )
+    });
+
+    let exported_state = ExportedState::new(serf.stack(), version, ker_hash, event_num, &ker_state);
+
+    let encoded = exported_state
+        .encode()
+        .map_err(|_| CrownError::StateJamFormatError)?;
+
+    Ok(encoded)
+}
+
+fn create_checkpoint(serf: &mut Serf, buffer_toggle: Arc<AtomicBool>) -> JammedCheckpoint {
+    let version = serf.version;
+    let ker_hash = serf.ker_hash;
+    let event_num = serf.event_num.load(Ordering::SeqCst);
+    let ker_state = serf.arvo.slot(STATE_AXIS).unwrap_or_else(|err| {
+        panic!(
+            "Panicked with {err:?} at {}:{} (git sha: {:?})",
+            file!(),
+            line!(),
+            option_env!("GIT_SHA")
+        )
+    });
+    let cold = serf.context.cold;
+    let buff_index = buffer_toggle.load(Ordering::SeqCst);
+    JammedCheckpoint::new(
+        serf.stack(),
+        version,
+        buff_index,
+        ker_hash,
+        event_num,
+        &cold,
+        &ker_state,
+    )
+}
+
+/// Represents a Sword kernel, containing a Serf and snapshot location.
+pub struct Kernel {
+    /// The Serf managing the interface to the Sword.
+    pub(crate) serf: SerfThread,
+    /// Directory path for storing pma snapshots.
+    pma_dir: Arc<PathBuf>,
+    /// Atomic flag for terminating the kernel.
+    pub terminator: Arc<AtomicBool>,
+}
+
+impl Kernel {
+    /// Loads a kernel with a custom hot state.
+    ///
+    /// # Arguments
+    ///
+    /// * `snap_dir` - Directory for storing snapshots.
+    /// * `kernel` - Byte slice containing the kernel as a jammed noun.
+    /// * `hot_state` - Custom hot state entries.
+    /// * `trace` - Whether to enable tracing.
+    ///
+    /// # Returns
+    ///
+    /// A new `Kernel` instance.
+    pub async fn load_with_hot_state(
+        pma_dir: PathBuf,
+        jam_paths: JamPaths,
+        kernel: &[u8],
+        hot_state: &[HotEntry],
+        trace: bool,
+    ) -> Result<Self> {
+        let jam_paths_arc = Arc::new(jam_paths);
+        let kernel_vec = Vec::from(kernel);
+        let hot_state_vec = Vec::from(hot_state);
+        let pma_dir_arc = Arc::new(pma_dir);
+        let serf = SerfThread::new(
+            NOCK_STACK_SIZE, jam_paths_arc, kernel_vec, hot_state_vec, trace,
+        )
+        .await?;
+        let terminator = Arc::new(AtomicBool::new(false));
+        Ok(Self {
+            serf,
+            pma_dir: pma_dir_arc,
+            terminator,
+        })
+    }
+
+    /// Loads a kernel with default hot state.
+    ///
+    /// # Arguments
+    ///
+    /// * `snap_dir` - Directory for storing snapshots.
+    /// * `kernel` - Byte slice containing the kernel code.
+    /// * `trace` - Whether to enable tracing.
+    ///
+    /// # Returns
+    ///
+    /// A new `Kernel` instance.
+    pub async fn load(
+        pma_dir: PathBuf,
+        jam_paths: JamPaths,
+        kernel: &[u8],
+        trace: bool,
+    ) -> Result<Self> {
+        Self::load_with_hot_state(pma_dir, jam_paths, kernel, &Vec::new(), trace).await
+    }
+
+    /// Loads a kernel with state from jammed bytes
+    pub async fn load_with_kernel_state(
+        pma_dir: PathBuf,
+        jam_paths: JamPaths,
+        kernel_jam: &[u8],
+        state_bytes: &[u8],
+        hot_state: &[HotEntry],
+        trace: bool,
+    ) -> Result<Self> {
+        let kernel =
+            Self::load_with_hot_state(pma_dir, jam_paths, kernel_jam, hot_state, trace).await?;
+
+        match kernel
+            .serf
+            .load_state_from_bytes(Vec::from(state_bytes))
+            .await
+        {
+            Ok(_) => {
+                info!("Successfully loaded state from bytes");
+                Ok(kernel)
+            }
+            Err(e) => {
+                error!("Failed to load state from state bytes: {}", e);
+                error!("The state bytes format is not recognized. It should be either a JammedCheckpoint or an ExportedState.");
+                Err(e)
+            }
+        }
+    }
+
+    /// Produces a checkpoint of the kernel state.
+    pub async fn checkpoint(&self) -> Result<JammedCheckpoint> {
+        self.serf.checkpoint().await
+    }
+
+    #[tracing::instrument(name = "crown::Kernel::poke", skip(self, cause))]
+    pub async fn poke(&self, wire: WireRepr, cause: NounSlab) -> Result<NounSlab> {
+        self.serf.poke(wire, cause).await
+    }
+
+    pub fn poke_sync(&self, wire: WireRepr, cause: NounSlab) -> Result<NounSlab> {
+        self.serf.poke_sync(wire, cause)
+    }
+
+    pub fn peek_sync(&self, ovo: NounSlab) -> Result<NounSlab> {
+        self.serf.peek_sync(ovo)
+    }
+
+    #[tracing::instrument(name = "crown::Kernel::peek", skip_all)]
+    pub async fn peek(&mut self, ovo: NounSlab) -> Result<NounSlab> {
+        self.serf.peek(ovo).await
+    }
+
+    pub async fn create_state_bytes(&self) -> Result<Vec<u8>> {
+        self.serf.create_state_bytes().await
+    }
+}
+
+/// Represents the Serf, which maintains context and provides an interface to
+/// the Sword.
+pub struct Serf {
+    /// Version number of snapshot
+    pub version: u32,
+    /// Hash of boot kernel
+    pub ker_hash: Hash,
+    /// The current Arvo state.
+    pub arvo: Noun,
+    /// The interpreter context.
+    pub context: interpreter::Context,
+    /// The current event number.
+    pub event_num: Arc<AtomicU64>,
+}
+
+impl Serf {
+    /// Creates a new Serf instance.
+    ///
+    /// # Arguments
+    ///
+    /// * `stack` - The Nock stack.
+    /// * `checkpoint` - Optional checkpoint to restore from.
+    /// * `kernel_bytes` - Byte slice containing the kernel code.
+    /// * `constant_hot_state` - Custom hot state entries.
+    /// * `trace` - Bool indicating whether to enable sword tracing.
+    ///
+    /// # Returns
+    ///
+    /// A new `Serf` instance.
+    fn new(
+        mut stack: NockStack,
+        checkpoint: Option<Checkpoint>,
+        kernel_bytes: &[u8],
+        constant_hot_state: &[HotEntry],
+        trace: bool,
+    ) -> Self {
+        let hot_state = [URBIT_HOT_STATE, constant_hot_state].concat();
+
+        let (cold, event_num_raw) = checkpoint.as_ref().map_or_else(
+            || (Cold::new(&mut stack), 0),
+            |snapshot| (snapshot.cold, snapshot.event_num),
+        );
+
+        let event_num = Arc::new(AtomicU64::new(event_num_raw));
+
+        let trace_info = if trace {
+            let file = File::create("trace.json").expect("Cannot create trace file trace.json");
+            let pid = std::process::id();
+            let process_start = std::time::Instant::now();
+            Some(TraceInfo {
+                file,
+                pid,
+                process_start,
+            })
+        } else {
+            None
+        };
+
+        let mut context = create_context(stack, &hot_state, cold, trace_info);
+
+        let version = checkpoint
+            .as_ref()
+            .map_or_else(|| SNAPSHOT_VERSION, |snapshot| snapshot.version);
+
+        let mut arvo = {
+            let kernel_trap = Noun::cue_bytes_slice(&mut context.stack, kernel_bytes)
+                .expect("invalid kernel jam");
+            let fol = T(&mut context.stack, &[D(9), D(2), D(0), D(1)]);
+
+            if context.trace_info.is_some() {
+                let start = Instant::now();
+                let arvo = interpret(&mut context, kernel_trap, fol).unwrap_or_else(|err| {
+                    panic!(
+                        "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                        file!(),
+                        line!(),
+                        option_env!("GIT_SHA")
+                    )
+                });
+                write_serf_trace_safe(&mut context, "boot", start);
+                arvo
+            } else {
+                interpret(&mut context, kernel_trap, fol).unwrap_or_else(|err| {
+                    panic!(
+                        "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                        file!(),
+                        line!(),
+                        option_env!("GIT_SHA")
+                    )
+                })
+            }
+        };
+
+        let mut hasher = Hasher::new();
+        hasher.update(kernel_bytes);
+        let ker_hash = hasher.finalize();
+
+        let mut serf = Self {
+            version,
+            ker_hash,
+            arvo,
+            context,
+            event_num,
+        };
+
+        if let Some(checkpoint) = checkpoint {
+            if ker_hash != checkpoint.ker_hash {
+                info!(
+                    "Kernel hash changed: {:?} -> {:?}",
+                    checkpoint.ker_hash, ker_hash
+                );
+            }
+            arvo = serf.load(checkpoint.ker_state).expect("serf: load failed");
+        }
+
+        unsafe {
+            serf.event_update(event_num_raw, arvo);
+            serf.preserve_event_update_leftovers();
+        }
+        serf
+    }
+
+    /// Performs a peek operation on the Arvo state.
+    ///
+    /// # Arguments
+    ///
+    /// * `ovo` - The peek request noun.
+    ///
+    /// # Returns
+    ///
+    /// Result containing the peeked data or an error.
+    #[tracing::instrument(skip_all)]
+    pub fn peek(&mut self, ovo: Noun) -> Result<Noun> {
+        if self.context.trace_info.is_some() {
+            let trace_name = "peek";
+            let start = Instant::now();
+            let slam_res = self.slam(PEEK_AXIS, ovo);
+            write_serf_trace_safe(&mut self.context, trace_name, start);
+
+            slam_res
+        } else {
+            self.slam(PEEK_AXIS, ovo)
+        }
+    }
+
+    /// Generates a goof (error) noun.
+    ///
+    /// # Arguments
+    ///
+    /// * `mote` - The error mote.
+    /// * `traces` - Trace information.
+    ///
+    /// # Returns
+    ///
+    /// A noun representing the error.
+    pub fn goof(&mut self, mote: Mote, traces: Noun) -> Noun {
+        let tone = Cell::new(&mut self.context.stack, D(2), traces);
+        let tang = mook(&mut self.context, tone, false)
+            .expect("serf: goof: +mook crashed on bail")
+            .tail();
+        T(&mut self.context.stack, &[D(mote as u64), tang])
+    }
+
+    /// Performs a load operation on the Arvo state.
+    ///
+    /// # Arguments
+    ///
+    /// * `old` - The state to load.
+    ///
+    /// # Returns
+    ///
+    /// Result containing the loaded kernel or an error.
+    pub fn load(&mut self, old: Noun) -> Result<Noun> {
+        match self.soft(old, LOAD_AXIS, Some("load".to_string())) {
+            Ok(res) => Ok(res),
+            Err(goof) => {
+                self.print_goof(goof);
+                Err(CrownError::SerfLoadError)
+            }
+        }
+    }
+
+    pub fn print_goof(&mut self, goof: Noun) {
+        let tang = goof
+            .as_cell()
+            .expect("print goof: expected goof to be a cell")
+            .tail();
+        tang.list_iter().for_each(|tank: Noun| {
+            //  TODO: Slogger should be emitting Results in case of failure
+            self.context.slogger.slog(&mut self.context.stack, 1, tank);
+        });
+    }
+
+    /// Performs a poke operation on the Arvo state.
+    ///
+    /// # Arguments
+    ///
+    /// * `job` - The poke job noun.
+    ///
+    /// # Returns
+    ///
+    /// Result containing the poke response or an error.
+    #[tracing::instrument(level = "info", skip_all)]
+    pub fn do_poke(&mut self, job: Noun) -> Result<Noun> {
+        match self.soft(job, POKE_AXIS, Some("poke".to_string())) {
+            Ok(res) => {
+                let cell = res.as_cell().expect("serf: poke: +slam returned atom");
+                let mut fec = cell.head();
+                let eve = self.event_num.load(Ordering::SeqCst);
+
+                unsafe {
+                    self.event_update(eve + 1, cell.tail());
+                    self.stack().preserve(&mut fec);
+                    self.preserve_event_update_leftovers();
+                }
+                Ok(fec)
+            }
+            Err(goof) => self.poke_swap(job, goof),
+        }
+    }
+
+    /// Slams (applies) a gate at a specific axis of Arvo.
+    ///
+    /// # Arguments
+    ///
+    /// * `axis` - The axis to slam.
+    /// * `ovo` - The sample noun.
+    ///
+    /// # Returns
+    ///
+    /// Result containing the slammed result or an error.
+    pub fn slam(&mut self, axis: u64, ovo: Noun) -> Result<Noun> {
+        let arvo = self.arvo;
+        slam(&mut self.context, arvo, axis, ovo)
+    }
+
+    /// Performs a "soft" computation, handling errors gracefully.
+    ///
+    /// # Arguments
+    ///
+    /// * `ovo` - The input noun.
+    /// * `axis` - The axis to slam.
+    /// * `trace_name` - Optional name for tracing.
+    ///
+    /// # Returns
+    ///
+    /// Result containing the computed noun or an error noun.
+    fn soft(&mut self, ovo: Noun, axis: u64, trace_name: Option<String>) -> Result<Noun, Noun> {
+        let slam_res = if self.context.trace_info.is_some() {
+            let start = Instant::now();
+            let slam_res = self.slam(axis, ovo);
+            write_serf_trace_safe(
+                &mut self.context,
+                trace_name.as_ref().unwrap_or_else(|| {
+                    panic!(
+                        "Panicked at {}:{} (git sha: {:?})",
+                        file!(),
+                        line!(),
+                        option_env!("GIT_SHA")
+                    )
+                }),
+                start,
+            );
+
+            slam_res
+        } else {
+            self.slam(axis, ovo)
+        };
+
+        match slam_res {
+            Ok(res) => Ok(res),
+            Err(error) => match error {
+                CrownError::InterpreterError(e) => {
+                    let (mote, traces) = match e.0 {
+                        Error::Deterministic(mote, traces)
+                        | Error::NonDeterministic(mote, traces) => (mote, traces),
+                        Error::ScryBlocked(_) | Error::ScryCrashed(_) => {
+                            panic!("serf: soft: .^ invalid outside of virtual Nock")
+                        }
+                    };
+
+                    Err(self.goof(mote, traces))
+                }
+                _ => Err(D(0)),
+            },
+        }
+    }
+
+    /// Plays a list of events.
+    ///
+    /// # Arguments
+    ///
+    /// * `lit` - The list of events to play.
+    ///
+    /// # Returns
+    ///
+    /// Result containing the final Arvo state or an error.
+    fn play_list(&mut self, mut lit: Noun) -> Result<Noun> {
+        let mut eve = self.event_num.load(Ordering::SeqCst);
+        while let Ok(cell) = lit.as_cell() {
+            let ovo = cell.head();
+            lit = cell.tail();
+            let trace_name = if self.context.trace_info.is_some() {
+                Some(format!("play [{}]", eve))
+            } else {
+                None
+            };
+
+            match self.soft(ovo, POKE_AXIS, trace_name) {
+                Ok(res) => {
+                    let arvo = res.as_cell()?.tail();
+                    eve += 1;
+
+                    unsafe {
+                        self.event_update(eve, arvo);
+                        self.context.stack.preserve(&mut lit);
+                        self.preserve_event_update_leftovers();
+                    }
+                }
+                Err(goof) => {
+                    return Err(CrownError::KernelError(Some(goof)));
+                }
+            }
+        }
+        Ok(self.arvo)
+    }
+
+    /// Handles a poke error by swapping in a new event.
+    ///
+    /// # Arguments
+    ///
+    /// * `job` - The original poke job.
+    /// * `goof` - The error noun.
+    ///
+    /// # Returns
+    ///
+    /// Result containing the new event or an error.
+    fn poke_swap(&mut self, job: Noun, goof: Noun) -> Result<Noun> {
+        let stack = &mut self.context.stack;
+        self.context.cache = Hamt::<Noun>::new(stack);
+        let job_cell = job.as_cell().expect("serf: poke: job not a cell");
+        // job data is job without event_num
+        let job_data = job_cell
+            .tail()
+            .as_cell()
+            .expect("serf: poke: data not a cell");
+        //  job input is job without event_num or wire
+        let job_input = job_data.tail();
+        let wire = T(stack, &[D(0), D(tas!(b"arvo")), D(0)]);
+        let crud = DirectAtom::new_panic(tas!(b"crud"));
+        let event_num = D(self.event_num.load(Ordering::SeqCst) + 1);
+
+        let mut ovo = T(stack, &[event_num, wire, goof, job_input]);
+        let trace_name = if self.context.trace_info.is_some() {
+            Some(Self::poke_trace_name(
+                &mut self.context.stack,
+                wire,
+                crud.as_atom(),
+            ))
+        } else {
+            None
+        };
+
+        match self.soft(ovo, POKE_AXIS, trace_name) {
+            Ok(res) => {
+                let cell = res.as_cell().expect("serf: poke: crud +slam returned atom");
+                let mut fec = cell.head();
+                let eve = self.event_num.load(Ordering::SeqCst);
+
+                unsafe {
+                    self.event_update(eve + 1, cell.tail());
+                    self.context.stack.preserve(&mut ovo);
+                    self.context.stack.preserve(&mut fec);
+                    self.preserve_event_update_leftovers();
+                }
+                Ok(self.poke_bail(eve, eve, ovo, fec))
+            }
+            Err(goof_crud) => {
+                let stack = &mut self.context.stack;
+                let lud = T(stack, &[goof_crud, goof, D(0)]);
+                Ok(self.poke_bail_noun(lud))
+            }
+        }
+    }
+
+    /// Generates a trace name for a poke operation.
+    ///
+    /// # Arguments
+    ///
+    /// * `stack` - The Nock stack.
+    /// * `wire` - The wire noun.
+    /// * `vent` - The vent atom.
+    ///
+    /// # Returns
+    ///
+    /// A string representing the trace name.
+    fn poke_trace_name(stack: &mut NockStack, wire: Noun, vent: Atom) -> String {
+        let wpc = path_to_cord(stack, wire);
+        let wpc_len = met3_usize(wpc);
+        let wpc_bytes = &wpc.as_ne_bytes()[0..wpc_len];
+        let wpc_str = match std::str::from_utf8(wpc_bytes) {
+            Ok(valid) => valid,
+            Err(error) => {
+                let (valid, _) = wpc_bytes.split_at(error.valid_up_to());
+                unsafe { std::str::from_utf8_unchecked(valid) }
+            }
+        };
+
+        let vc_len = met3_usize(vent);
+        let vc_bytes = &vent.as_ne_bytes()[0..vc_len];
+        let vc_str = match std::str::from_utf8(vc_bytes) {
+            Ok(valid) => valid,
+            Err(error) => {
+                let (valid, _) = vc_bytes.split_at(error.valid_up_to());
+                unsafe { std::str::from_utf8_unchecked(valid) }
+            }
+        };
+
+        format!("poke [{} {}]", wpc_str, vc_str)
+    }
+
+    /// Performs a poke operation with a given cause.
+    ///
+    /// # Arguments
+    ///
+    /// * `wire` - The wire noun.
+    /// * `cause` - The cause noun.
+    ///
+    /// # Returns
+    ///
+    /// Result containing the poke response or an error.
+    #[tracing::instrument(level = "info", skip_all, fields(
+        wire_source = wire.source,
+        wire_version = wire.version,
+        wire_tags = wire.tags_as_csv(),
+    ))]
+    pub fn poke(&mut self, wire: WireRepr, cause: Noun) -> Result<Noun> {
+        let random_bytes = rand::random::<u64>();
+        let bytes = random_bytes.as_bytes()?;
+        let eny: Atom = Atom::from_bytes(&mut self.context.stack, &bytes);
+        let our = <sword::noun::Atom as AtomExt>::from_value(&mut self.context.stack, 0)?; // Using 0 as default value
+        let now: Atom = unsafe {
+            let mut t_vec: Vec<u8> = vec![];
+            t_vec.write_u128::<LittleEndian>(current_da().0)?;
+            IndirectAtom::new_raw_bytes(&mut self.context.stack, 16, t_vec.as_slice().as_ptr())
+                .normalize_as_atom()
+        };
+
+        let event_num = D(self.event_num.load(Ordering::SeqCst) + 1);
+        let base_wire_noun = wire_to_noun(&mut self.context.stack, &wire);
+        let wire = T(&mut self.context.stack, &[D(tas!(b"poke")), base_wire_noun]);
+        let poke = T(
+            &mut self.context.stack,
+            &[event_num, wire, eny.as_noun(), our.as_noun(), now.as_noun(), cause],
+        );
+
+        self.do_poke(poke)
+    }
+
+    /// Updates the Serf's state after an event.
+    ///
+    /// # Arguments
+    ///
+    /// * `new_event_num` - The new event number.
+    /// * `new_arvo` - The new Arvo state.
+    ///
+    /// # Safety
+    ///
+    /// This function is unsafe because it modifies the Serf's state directly.
+    #[tracing::instrument(level = "info", skip_all)]
+    pub unsafe fn event_update(&mut self, new_event_num: u64, new_arvo: Noun) {
+        self.arvo = new_arvo;
+        self.event_num.store(new_event_num, Ordering::SeqCst);
+
+        self.context.cache = Hamt::new(&mut self.context.stack);
+        self.context.scry_stack = D(0);
+    }
+
+    /// Preserves leftovers after an event update.
+    ///
+    /// # Safety
+    ///
+    /// This function is unsafe because it modifies the Serf's state directly.
+    #[tracing::instrument(level = "info", skip_all)]
+    pub unsafe fn preserve_event_update_leftovers(&mut self) {
+        let stack = &mut self.context.stack;
+        stack.preserve(&mut self.context.warm);
+        stack.preserve(&mut self.context.hot);
+        stack.preserve(&mut self.context.cache);
+        stack.preserve(&mut self.context.cold);
+        stack.preserve(&mut self.arvo);
+        stack.flip_top_frame(0);
+    }
+
+    /// Returns a mutable reference to the Nock stack.
+    ///
+    /// # Returns
+    ///
+    /// A mutable reference to the `NockStack`.
+    pub fn stack(&mut self) -> &mut NockStack {
+        &mut self.context.stack
+    }
+
+    /// Creates a poke swap noun.
+    ///
+    /// # Arguments
+    ///
+    /// * `eve` - The event number.
+    /// * `mug` - The mug value.
+    /// * `ovo` - The original noun.
+    /// * `fec` - The effect noun.
+    ///
+    /// # Returns
+    ///
+    /// A noun representing the poke swap.
+    pub fn poke_bail(&mut self, eve: u64, mug: u64, ovo: Noun, fec: Noun) -> Noun {
+        T(
+            self.stack(),
+            &[D(tas!(b"poke")), D(tas!(b"swap")), D(eve), D(mug), ovo, fec],
+        )
+    }
+
+    /// Creates a poke bail noun.
+    ///
+    /// # Arguments
+    ///
+    /// * `lud` - The lud noun.
+    ///
+    /// # Returns
+    ///
+    /// A noun representing the poke bail.
+    pub fn poke_bail_noun(&mut self, lud: Noun) -> Noun {
+        T(self.stack(), &[D(tas!(b"poke")), D(tas!(b"bail")), lud])
+    }
+}
+
+fn slot(noun: Noun, axis: u64) -> Result<Noun> {
+    Ok(noun.slot(axis)?)
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use std::fs;
+    use std::path::Path;
+    use tempfile::TempDir;
+
+    async fn setup_kernel(jam: &str) -> (Kernel, TempDir) {
+        let temp_dir = TempDir::new().expect("Failed to create temp directory");
+        let snap_dir = temp_dir.path().to_path_buf();
+        let jam_paths = JamPaths::new(&snap_dir);
+        let jam_path = Path::new(env!("CARGO_MANIFEST_DIR"))
+            .join("..")
+            .join("assets")
+            .join(jam);
+        let jam_bytes =
+            fs::read(jam_path).unwrap_or_else(|_| panic!("Failed to read {} file", jam));
+        let kernel = Kernel::load(snap_dir, jam_paths, &jam_bytes, false)
+            .await
+            .expect("Could not load kernel");
+        (kernel, temp_dir)
+    }
+
+    // Convert this to an integration test and feed it the kernel.jam from Choo in CI/CD
+    // https://www.youtube.com/watch?v=4m1EFMoRFvY
+    // #[test]
+    // #[cfg_attr(miri, ignore)]
+    // fn test_kernel_boot() {
+    //     let _ = setup_kernel("dumb.jam");
+    // }
+
+    // To test your own kernel, place a `kernel.jam` file in the `assets` directory
+    // and uncomment the following test:
+    //
+    // #[test]
+    // fn test_custom_kernel() {
+    //     let (kernel, _temp_dir) = setup_kernel("kernel.jam");
+    //     // Add your custom assertions here to test the kernel's behavior
+    // }
+}

+ 3 - 0
crates/nockapp/crown/src/kernel/mod.rs

@@ -0,0 +1,3 @@
+pub mod boot;
+pub mod checkpoint;
+pub mod form;

+ 56 - 0
crates/nockapp/crown/src/lib.rs

@@ -0,0 +1,56 @@
+//! # Crown
+//!
+//! The Crown library provides a set of modules and utilities for working with
+//! the Sword runtime. It includes functionality for handling jammed nouns, kernels (as jammed nouns),
+//! and various types and utilities that make sword easier to use.
+//!
+//! ## Modules
+//!
+//! - `kernel`: Sword runtime interface.
+//! - `noun`: Extensions and utilities for working with Urbit nouns.
+//! - `utils`: Errors, misc functions and extensions.
+//!
+pub mod drivers;
+pub mod kernel;
+pub mod nockapp;
+pub mod noun;
+pub mod observability;
+pub mod utils;
+
+pub use bytes::*;
+pub use nockapp::NockApp;
+pub use noun::{AtomExt, JammedNoun, NounExt};
+pub use sword::noun::Noun;
+pub use utils::bytes::{ToBytes, ToBytesExt};
+pub use utils::error::{CrownError, Result};
+
+pub use drivers::*;
+
+use std::path::PathBuf;
+
+/// Returns the default directory where kernel data is stored.
+///
+/// # Arguments
+///
+/// * `dir` - A string slice that holds the kernel identifier.
+///
+/// # Example
+///
+/// ```
+///
+/// use std::path::PathBuf;
+/// use crown::default_data_dir;
+/// let dir = default_data_dir("crown");
+/// assert_eq!(dir, PathBuf::from("./.data.crown"));
+/// ```
+pub fn default_data_dir(dir_name: &str) -> PathBuf {
+    PathBuf::from(format!("./.data.{}", dir_name))
+}
+
+pub fn system_data_dir() -> PathBuf {
+    let home_dir = dirs::home_dir().expect("Failed to get home directory");
+    home_dir.join(".nockapp")
+}
+
+/// Default size for the Nock stack (1 GB)
+pub const DEFAULT_NOCK_STACK_SIZE: usize = 1 << 27;

+ 0 - 0
crates/nockapp/crown/src/nockapp/actors/kernel.rs


+ 2 - 0
crates/nockapp/crown/src/nockapp/actors/mod.rs

@@ -0,0 +1,2 @@
+pub(crate) mod kernel;
+pub(crate) mod save;

+ 102 - 0
crates/nockapp/crown/src/nockapp/actors/save.rs

@@ -0,0 +1,102 @@
+use std::{path::PathBuf, sync::{atomic::{AtomicBool, Ordering}, Arc}, time::Duration};
+
+use tokio::{fs, io::AsyncWriteExt as _, sync::mpsc};
+use tracing::{error, trace};
+
+use crate::{kernel::checkpoint::JammedCheckpoint, nockapp::NockAppError};
+
+// Save actor messages
+pub(crate) enum SaveMessage {
+    SaveCheckpoint(JammedCheckpoint),
+    // SaveCheckpoint(JammedCheckpoint, Option<tokio::sync::oneshot::Sender<NockAppResult>>),
+    // SaveCheckpointWithResponse(JammedCheckpoint, tokio::sync::oneshot::Sender<NockAppResult>),
+    // IntervalSaveCheckpoint(JammedCheckpoint),
+    Shutdown,
+}
+
+// enum ShutdownMessage {
+//     Shutdown,
+//     ShutdownNow,
+// }
+
+pub(crate) struct SaveActor {
+    receiver: mpsc::Receiver<SaveMessage>,
+    jam_paths: (PathBuf, PathBuf),
+    save_interval: Duration,
+    last_save: std::time::Instant,
+    buffer_toggle: Arc<AtomicBool>,
+    event_sender: tokio::sync::watch::Sender<u64>,
+}
+
+impl SaveActor {
+    pub(crate) fn new(
+        receiver: mpsc::Receiver<SaveMessage>,
+        jam_paths: (PathBuf, PathBuf),
+        save_interval: Duration,
+        buffer_toggle: Arc<AtomicBool>,
+        event_sender: tokio::sync::watch::Sender<u64>,
+    ) -> Self {
+        Self {
+            receiver,
+            jam_paths,
+            save_interval,
+            last_save: std::time::Instant::now(),
+            buffer_toggle,
+            event_sender,
+        }
+    }
+
+    pub(crate) async fn run(mut self) {
+        while let Some(msg) = self.receiver.recv().await {
+            match msg {
+                SaveMessage::SaveCheckpoint(checkpoint) => {
+                    if let Err(e) = self.handle_save(checkpoint).await {
+                        error!("Save error: {:?}", e);
+                    }
+                }
+                SaveMessage::Shutdown => break,
+            }
+        }
+    }
+
+    async fn handle_save(&mut self, checkpoint: JammedCheckpoint) -> Result<(), NockAppError> {
+        // Enforce minimum interval between saves
+        let elapsed = self.last_save.elapsed();
+        if elapsed < self.save_interval {
+            tokio::time::sleep(self.save_interval - elapsed).await;
+        }
+
+        let bytes = checkpoint.encode()?;
+        let path = if self.buffer_toggle.load(Ordering::SeqCst) {
+            &self.jam_paths.1
+        } else {
+            &self.jam_paths.0
+        };
+
+        let mut file = fs::File::create(path)
+            .await
+            .map_err(NockAppError::SaveError)?;
+
+        file.write_all(&bytes)
+            .await
+            .map_err(NockAppError::SaveError)?;
+
+        file.sync_all()
+            .await
+            .map_err(NockAppError::SaveError)?;
+
+        trace!(
+            "Write to {:?} successful, ker_hash: {}, event: {}",
+            path.display(),
+            checkpoint.ker_hash,
+            checkpoint.event_num
+        );
+
+        // Flip toggle after successful write
+        self.buffer_toggle.store(!self.buffer_toggle.load(Ordering::SeqCst), Ordering::SeqCst);
+        self.event_sender.send(checkpoint.event_num)?;
+        self.last_save = std::time::Instant::now();
+
+        Ok(())
+    }
+}

+ 188 - 0
crates/nockapp/crown/src/nockapp/driver.rs

@@ -0,0 +1,188 @@
+use crate::noun::slab::NounSlab;
+use futures::future::Future;
+use std::pin::Pin;
+use tokio::sync::{broadcast, mpsc, oneshot, Mutex};
+use tokio::task::JoinSet;
+use tracing::instrument;
+
+use super::error::NockAppError;
+use super::wire::WireRepr;
+
+pub type IODriverFuture = Pin<Box<dyn Future<Output = Result<(), NockAppError>> + Send>>;
+pub type IODriverFn = Box<dyn FnOnce(NockAppHandle) -> IODriverFuture>;
+pub type TaskJoinSet = JoinSet<Result<(), NockAppError>>;
+pub type ActionSender = mpsc::Sender<IOAction>;
+pub type ActionReceiver = mpsc::Receiver<IOAction>;
+pub type EffectSender = broadcast::Sender<NounSlab>;
+pub type EffectReceiver = broadcast::Receiver<NounSlab>;
+
+/// Result of a poke: either Ack if it succeeded or Nack if it failed
+#[derive(Debug)]
+pub enum PokeResult {
+    Ack,
+    Nack,
+}
+
+pub enum Operation {
+    Poke,
+    Peek,
+}
+
+pub fn make_driver<F, Fut>(f: F) -> IODriverFn
+where
+    F: FnOnce(NockAppHandle) -> Fut + Send + 'static,
+    Fut: Future<Output = Result<(), NockAppError>> + Send + 'static,
+{
+    Box::new(move |handle| Box::pin(f(handle)))
+}
+
+pub struct NockAppHandle {
+    pub io_sender: ActionSender,
+    pub effect_sender: EffectSender,
+    pub effect_receiver: Mutex<EffectReceiver>,
+    pub exit: mpsc::Sender<usize>,
+}
+
+/// IO actions sent between [`NockAppHandle`] and [`crate::NockApp`] over channels.
+///
+/// Used by [`NockAppHandle`] to send poke/peek requests to [`crate::NockApp`] ,
+/// which processes them against the Nock kernel and returns results
+/// via oneshot channels.
+#[derive(Debug)]
+pub enum IOAction {
+    /// Poke request to [`crate::NockApp`]
+    Poke {
+        wire: WireRepr,
+        poke: NounSlab,
+        ack_channel: oneshot::Sender<PokeResult>,
+    },
+    /// Peek request to [`crate::NockApp`]
+    Peek {
+        path: NounSlab,
+        result_channel: oneshot::Sender<Option<NounSlab>>,
+    },
+}
+
+impl NockAppHandle {
+    #[tracing::instrument(name = "crown::NockAppHandle::send_poke", skip_all)]
+    pub async fn send_poke(
+        &self,
+        ack_channel: oneshot::Sender<PokeResult>,
+        wire: WireRepr,
+        poke: NounSlab,
+    ) -> Result<(), NockAppError> {
+        self.io_sender
+            .send(IOAction::Poke {
+                wire,
+                poke,
+                ack_channel,
+            })
+            .await?;
+        Ok(())
+    }
+
+    #[tracing::instrument(name = "crown::NockAppHandle::try_send_poke", skip_all)]
+    /// Tries to send a poke. Returns NockAppError::MPSCSendError if the channel is closed. If the channel is full, the result is given back in the Some branch of the Option. If the channel is empty
+    pub fn try_send_poke(
+        &self,
+        ack_channel: oneshot::Sender<PokeResult>,
+        wire: WireRepr,
+        poke: NounSlab,
+    ) -> Result<(), NockAppError> {
+        Ok(self.io_sender.try_send(IOAction::Poke {
+            wire,
+            poke,
+            ack_channel,
+        })?)
+    }
+
+    #[tracing::instrument(name = "crown::NockAppHandle::poke", skip_all)]
+    pub async fn poke(&self, wire: WireRepr, poke: NounSlab) -> Result<PokeResult, NockAppError> {
+        let (ack_channel, ack_future) = oneshot::channel();
+        self.send_poke(ack_channel, wire, poke).await?;
+        Ok(ack_future.await?)
+    }
+
+    // This is still async because we still await the ack future on success.
+    #[tracing::instrument(name = "crown::NockAppHandle::try_poke", skip_all)]
+    pub async fn try_poke(
+        &self,
+        wire: WireRepr,
+        poke: NounSlab,
+    ) -> Result<PokeResult, NockAppError> {
+        let (ack_channel, ack_future) = oneshot::channel();
+        self.try_send_poke(ack_channel, wire, poke)?;
+        Ok(ack_future.await?)
+    }
+
+    #[tracing::instrument(name = "crown::NockAppHandle::try_send_peek", skip_all)]
+    pub fn try_send_peek(
+        &self,
+        path: NounSlab,
+        result_channel: oneshot::Sender<Option<NounSlab>>,
+    ) -> Result<(), NockAppError> {
+        Ok(self.io_sender.try_send(IOAction::Peek {
+            path,
+            result_channel,
+        })?)
+    }
+
+    #[tracing::instrument(name = "crown::NockAppHandle::send_peek", skip_all)]
+    async fn send_peek(
+        &self,
+        path: NounSlab,
+        result_channel: oneshot::Sender<Option<NounSlab>>,
+    ) -> Result<(), NockAppError> {
+        self.io_sender
+            .send(IOAction::Peek {
+                path,
+                result_channel,
+            })
+            .await?;
+        Ok(())
+    }
+
+    #[tracing::instrument(name = "crown::NockAppHandle::peek", skip_all)]
+    pub async fn peek(&self, path: NounSlab) -> Result<Option<NounSlab>, NockAppError> {
+        let (result_channel, result_future) = oneshot::channel();
+        self.send_peek(path, result_channel).await?;
+        Ok(result_future.await?)
+    }
+
+    // Still async because we need to await the result future
+    #[tracing::instrument(name = "crown::NockAppHandle::try_peek", skip_all)]
+    pub async fn try_peek(&self, path: NounSlab) -> Result<Option<NounSlab>, NockAppError> {
+        let (result_channel, result_future) = oneshot::channel();
+        self.send_peek(path, result_channel).await?;
+        Ok(result_future.await?)
+    }
+
+    #[instrument(skip(self))]
+    pub async fn next_effect(&self) -> Result<NounSlab, NockAppError> {
+        let mut effect_receiver = self.effect_receiver.lock().await;
+        tracing::debug!("Waiting for recv on next effect");
+        Ok(effect_receiver.recv().await?)
+    }
+
+    #[instrument(skip(self))]
+    pub fn dup(self) -> (Self, Self) {
+        let io_sender = self.io_sender.clone();
+        let effect_sender = self.effect_sender.clone();
+        let effect_receiver = Mutex::new(effect_sender.subscribe());
+        let exit = self.exit.clone();
+        (
+            self,
+            NockAppHandle {
+                io_sender,
+                effect_sender,
+                effect_receiver,
+                exit,
+            },
+        )
+    }
+
+    #[instrument(skip(self))]
+    pub fn clone_io_sender(&self) -> ActionSender {
+        self.io_sender.clone()
+    }
+}

+ 67 - 0
crates/nockapp/crown/src/nockapp/error.rs

@@ -0,0 +1,67 @@
+use crate::noun::slab::CueError;
+use crate::CrownError;
+use std::any::Any;
+use thiserror::Error;
+use tokio::sync::mpsc::error::{SendError, TrySendError};
+use tracing::error;
+
+use super::driver::IOAction;
+
+/// Error type for NockApps
+#[derive(Debug, Error)]
+pub enum NockAppError {
+    /// NockApp exited with a code, shouldn't ever be 0, that's a Done/Success.
+    #[error("NockApp exited with error code: {0}")]
+    Exit(usize),
+    #[error("Timeout")]
+    Timeout,
+    #[error("IO error: {0}")]
+    IoError(#[source] std::io::Error),
+    #[error("Save error: {0}")]
+    SaveError(#[source] std::io::Error),
+    #[error("MPSC send error (probably trying to send a poke): {0}")]
+    MPSCSendError(#[from] tokio::sync::mpsc::error::SendError<IOAction>),
+    #[error("MPSC full error (you probably used try_poke and the channel was full)")]
+    MPSCFullError(IOAction),
+    #[error("Oneshot receive error (sender dropped): {0}")]
+    OneShotRecvError(#[from] tokio::sync::oneshot::error::RecvError),
+    #[error("Error cueing jam buffer: {0}")]
+    CueError(#[from] CueError),
+    #[error("Error receiving effect broadcast: {0}")]
+    BroadcastRecvError(#[from] tokio::sync::broadcast::error::RecvError),
+    #[error("Error joining task (probably the task panicked: {0}")]
+    JoinError(#[from] tokio::task::JoinError),
+    #[error("Error converting string: {0}")]
+    FromUtf8Error(#[from] std::string::FromUtf8Error),
+    #[error("Crown error: {0}")]
+    CrownError(#[from] CrownError),
+    #[error("Channel closed error")]
+    ChannelClosedError,
+    #[error("Other error")]
+    OtherError,
+    #[error("Peek failed")]
+    PeekFailed,
+    #[error("Poke failed")]
+    PokeFailed,
+    #[error("Unexpected result")]
+    UnexpectedResult,
+    #[error("sword error: {0}")]
+    SwordError(#[from] sword::noun::Error),
+    #[error("Save error: {0}")]
+    EncodeError(#[from] bincode::error::EncodeError),
+    #[error("Decode error: {0}")]
+    DecodeError(#[from] bincode::error::DecodeError),
+    #[error("Send error: {0}")]
+    SendError(#[from] tokio::sync::watch::error::SendError<u64>),
+    #[error("Serf thread error")]
+    SerfThreadError(Box<dyn Any + Send + 'static>),
+}
+
+impl From<TrySendError<IOAction>> for NockAppError {
+    fn from(tse: TrySendError<IOAction>) -> NockAppError {
+        match tse {
+            TrySendError::Closed(act) => NockAppError::MPSCSendError(SendError(act)),
+            TrySendError::Full(act) => NockAppError::MPSCFullError(act),
+        }
+    }
+}

+ 11 - 0
crates/nockapp/crown/src/nockapp/metrics.rs

@@ -0,0 +1,11 @@
+use gnort::*;
+
+metrics_struct![
+    NockAppMetrics,
+    (handle_shutdown, "nockapp.handle_shutdown", Count),
+    (handle_save_permit_res, "nockapp.handle_save_permit_res", Count),
+    (handle_action, "nockapp.handle_action", Count),
+    (handle_exit, "nockapp.handle_exit", Count),
+    (poke_during_exit, "nockapp.poke_during_exit", Count),
+    (peek_during_exit, "nockapp.peek_during_exit", Count)
+];

+ 518 - 0
crates/nockapp/crown/src/nockapp/mod.rs

@@ -0,0 +1,518 @@
+// pub(crate) mod actors;
+pub mod driver;
+pub mod error;
+pub(crate) mod metrics;
+pub mod test;
+pub mod wire;
+
+pub use error::NockAppError;
+
+use std::path::PathBuf;
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::Arc;
+
+use futures::FutureExt;
+use tokio::io::AsyncWriteExt;
+use tokio::sync::{broadcast, mpsc, Mutex, OwnedMutexGuard};
+use tokio::time::{interval, Duration, Interval};
+use tokio::{fs, select};
+use tokio_util::task::TaskTracker;
+use tracing::{debug, error, info, instrument, trace};
+
+use crate::kernel::form::Kernel;
+use crate::noun::slab::NounSlab;
+
+use driver::{IOAction, IODriverFn, NockAppHandle, PokeResult};
+use metrics::*;
+use wire::WireRepr;
+
+type NockAppResult = Result<(), NockAppError>;
+
+pub struct NockApp {
+    /// Nock kernel
+    pub(crate) kernel: Kernel,
+    /// Current join handles for IO drivers (parallel to `drivers`)
+    pub(crate) tasks: tokio_util::task::TaskTracker,
+    /// Exit signal sender
+    exit_send: mpsc::Sender<usize>,
+    /// Exit signal receiver
+    exit_recv: mpsc::Receiver<usize>,
+    /// Exit status
+    exit_status: AtomicBool,
+    /// Save event num sender
+    watch_send: Arc<Mutex<tokio::sync::watch::Sender<u64>>>,
+    /// Save event num receiver
+    watch_recv: tokio::sync::watch::Receiver<u64>,
+    /// IO action channel
+    action_channel: mpsc::Receiver<IOAction>,
+    /// IO action channel sender
+    action_channel_sender: mpsc::Sender<IOAction>,
+    /// Effect broadcast channel
+    effect_broadcast: broadcast::Sender<NounSlab>,
+    /// Save interval
+    save_interval: Interval,
+    /// Mutex to ensure only one save at a time
+    pub(crate) save_mutex: Arc<Mutex<()>>,
+    /// Shutdown oneshot sender
+    shutdown_send: Option<tokio::sync::oneshot::Sender<NockAppResult>>,
+    /// Shutdown oneshot receiver
+    shutdown_recv: tokio::sync::oneshot::Receiver<NockAppResult>,
+    // cancel_token: tokio_util::sync::CancellationToken,
+    pub npc_socket_path: Option<PathBuf>,
+    metrics: NockAppMetrics,
+}
+
+pub enum NockAppRun {
+    Pending,
+    Done,
+}
+
+impl NockApp {
+    /// This constructs a Tokio interval, even though it doesn't look like it, a Tokio runtime is _required_.
+    pub async fn new(kernel: Kernel, save_interval_duration: Duration) -> Self {
+        // important: we are tracking this separately here because
+        // what matters is the last poke *we* received an ack for. Using
+        // the Arc in the serf would result in a race condition!
+
+        let (action_channel_sender, action_channel) = mpsc::channel(100);
+        let (effect_broadcast, _) = broadcast::channel(100);
+        // let tasks = Arc::new(Mutex::new(TaskJoinSet::new()));
+        // let tasks = TaskJoinSet::new();
+        // let tasks = Arc::new(TaskJoinSet::new());
+        let tasks = TaskTracker::new();
+        let (exit_send, exit_recv) = mpsc::channel(1);
+        let mut save_interval = interval(save_interval_duration);
+        save_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); // important so we don't stack ticks when lagging
+        let save_mutex = Arc::new(Mutex::new(()));
+        let (watch_send, watch_recv) =
+            tokio::sync::watch::channel(kernel.serf.event_number.load(Ordering::SeqCst));
+        let watch_send = Arc::new(Mutex::new(watch_send.clone()));
+        let exit_status = AtomicBool::new(false);
+        let (shutdown_send, shutdown_recv) = tokio::sync::oneshot::channel();
+        // let cancel_token = tokio_util::sync::CancellationToken::new();
+        let metrics = NockAppMetrics::register(gnort::global_metrics_registry())
+            .expect("Failed to register metrics!");
+        Self {
+            kernel,
+            tasks,
+            exit_send,
+            exit_recv,
+            exit_status,
+            watch_send,
+            watch_recv,
+            action_channel,
+            action_channel_sender,
+            effect_broadcast,
+            save_interval,
+            save_mutex,
+            shutdown_send: Some(shutdown_send),
+            shutdown_recv,
+            // cancel_token,
+            npc_socket_path: None,
+            metrics,
+        }
+    }
+
+    pub fn get_handle(&self) -> NockAppHandle {
+        NockAppHandle {
+            io_sender: self.action_channel_sender.clone(),
+            effect_sender: self.effect_broadcast.clone(),
+            effect_receiver: Mutex::new(self.effect_broadcast.subscribe()),
+            exit: self.exit_send.clone(),
+        }
+    }
+
+    /// Assume at-least-once processing and track the state necessary to know whether
+    /// all critical IO actions have been performed correctly or not from the jammed state.
+    #[tracing::instrument(skip(self, driver))]
+    pub async fn add_io_driver(&mut self, driver: IODriverFn) {
+        let io_sender = self.action_channel_sender.clone();
+        let effect_sender = self.effect_broadcast.clone();
+        let effect_receiver = Mutex::new(self.effect_broadcast.subscribe());
+        let exit = self.exit_send.clone();
+        let fut = driver(NockAppHandle {
+            io_sender,
+            effect_sender,
+            effect_receiver,
+            exit,
+        });
+        // TODO: Stop using the task tracker for user code?
+        self.tasks.spawn(fut);
+    }
+
+    /// Purely for testing purposes (injecting delays) for now.
+    #[instrument(skip(self, f, save_permit))]
+    pub(crate) async fn save_f(
+        &mut self,
+        f: impl std::future::Future<Output = ()> + Send + 'static,
+        save_permit: OwnedMutexGuard<()>,
+    ) -> Result<tokio::task::JoinHandle<NockAppResult>, NockAppError> {
+        let toggle = self.kernel.serf.buffer_toggle.clone();
+        let jam_paths = self.kernel.serf.jam_paths.clone();
+        let checkpoint = self.kernel.checkpoint().await?;
+        let bytes = checkpoint.encode()?;
+        let send_lock = self.watch_send.clone();
+
+        let join_handle = self.tasks.spawn(async move {
+            f.await;
+            let path = if toggle.load(Ordering::SeqCst) {
+                &jam_paths.1
+            } else {
+                &jam_paths.0
+            };
+            let mut file = fs::File::create(path)
+                .await
+                .map_err(NockAppError::SaveError)?;
+
+            file.write_all(&bytes)
+                .await
+                .map_err(NockAppError::SaveError)?;
+            file.sync_all().await.map_err(NockAppError::SaveError)?;
+
+            trace!(
+                "Write to {:?} successful, checksum: {}, event: {}",
+                path.display(),
+                checkpoint.checksum,
+                checkpoint.event_num
+            );
+
+            // Flip toggle after successful write
+            toggle.store(!toggle.load(Ordering::SeqCst), Ordering::SeqCst);
+            let send = send_lock.lock().await;
+            send.send(checkpoint.event_num)?;
+            drop(save_permit);
+            Ok::<(), NockAppError>(())
+        });
+        // We don't want to close and re-open the tasktracker from multiple places
+        // so we're just returning the join_handle to let the caller decide.
+        Ok(join_handle)
+    }
+
+    /// Except in tests, save should only be called by the permit handler.
+    pub(crate) async fn save(&mut self, save_permit: OwnedMutexGuard<()>) -> NockAppResult {
+        let _join_handle = self.save_f(async {}, save_permit).await?;
+        Ok(())
+    }
+
+    pub async fn save_locked(&mut self) -> NockAppResult {
+        let guard = self.save_mutex.clone().lock_owned().await;
+        self.save(guard).await.map_err(|e| {
+            error!("Failed to save: {:?}", e);
+            e
+        })?;
+        Ok(())
+    }
+
+    /// Peek at a noun in the kernel, blocking operation
+    #[tracing::instrument(skip(self, path))]
+    pub fn peek_sync(&mut self, path: NounSlab) -> Result<NounSlab, NockAppError> {
+        trace!("Peeking at noun: {:?}", path);
+        let res = self.kernel.peek_sync(path)?;
+        trace!("Peeked noun: {:?}", res);
+        Ok(res)
+    }
+
+    #[tracing::instrument(skip(self, path))]
+    pub async fn peek(&mut self, path: NounSlab) -> Result<NounSlab, NockAppError> {
+        trace!("Peeking at noun: {:?}", path);
+        let res = self.kernel.peek(path).await?;
+        trace!("Peeked noun: {:?}", res);
+        Ok(res)
+    }
+
+    /// Poke at a noun in the kernel, blocking operation
+    #[tracing::instrument(skip(self, wire, cause))]
+    pub fn poke_sync(
+        &mut self,
+        wire: WireRepr,
+        cause: NounSlab,
+    ) -> Result<Vec<NounSlab>, NockAppError> {
+        // let wire_noun = wire.copy_to_stack(self.kernel.serf.stack());
+        let effects_slab = self.kernel.poke_sync(wire, cause)?;
+        Ok(effects_slab.to_vec())
+    }
+
+    #[tracing::instrument(skip(self, wire, cause))]
+    pub async fn poke(
+        &mut self,
+        wire: WireRepr,
+        cause: NounSlab,
+    ) -> Result<Vec<NounSlab>, NockAppError> {
+        let effects_slab = self.kernel.poke(wire, cause).await?;
+        Ok(effects_slab.to_vec())
+    }
+
+    /// Runs until the nockapp is done (returns exit 0 or an error)
+    #[instrument(skip(self))]
+    pub async fn run_no_join(&mut self) -> NockAppResult {
+        debug!("Starting nockapp run");
+        // Reset NockApp for next run
+        // self.reset();
+        // debug!("Reset NockApp for next run");
+        loop {
+            let work_res = self.work().await;
+            match work_res {
+                Ok(nockapp_run) => match nockapp_run {
+                    crate::nockapp::NockAppRun::Pending => continue,
+                    crate::nockapp::NockAppRun::Done => break Ok(()),
+                },
+                Err(NockAppError::Exit(code)) => {
+                    if code == 0 {
+                        // zero is success, we're simply done.
+                        info!("nockapp exited successfully with code: {}", code);
+                        break Ok(());
+                    } else {
+                        error!("nockapp exited with error code: {}", code);
+                        break Err(NockAppError::Exit(code));
+                    }
+                }
+                Err(e) => {
+                    error!("Got error running nockapp: {:?}", e);
+                    break Err(e);
+                }
+            };
+        }
+    }
+
+    pub async fn join(self) -> NockAppResult {
+        info!("Awaiting serf stop");
+        self.kernel.serf.stop().await;
+        info!("Joining serf thread");
+        self.kernel
+            .serf
+            .join()
+            .map_err(|e| NockAppError::SerfThreadError(e))?;
+        info!("Serf thread joined");
+        Ok(())
+    }
+
+    pub async fn run(mut self) -> NockAppResult {
+        let res = self.run_no_join().await;
+        self.join().await?;
+        res
+    }
+
+    #[instrument(skip(socket))]
+    fn cleanup_socket_(socket: &Option<PathBuf>) {
+        // Clean up npc socket file if it exists
+        if let Some(socket) = socket {
+            if socket.exists() {
+                if let Err(e) = std::fs::remove_file(socket) {
+                    error!("Failed to remove npc socket file before exit: {}", e);
+                }
+            }
+        }
+    }
+
+    #[instrument(skip(self))]
+    fn cleanup_socket(&self) {
+        // Clean up npc socket file if it exists
+        Self::cleanup_socket_(&self.npc_socket_path);
+    }
+
+    async fn work(&mut self) -> Result<NockAppRun, NockAppError> {
+        // Fires when there is a save interval tick *and* an available permit in the save semaphore
+        let save_ready = self
+            .save_interval
+            .tick()
+            .then(|_| self.save_mutex.clone().lock_owned());
+        select!(
+        _ = self.kernel.serf.finished() => {
+                debug!("Serf thread exited");
+                Ok(NockAppRun::Done)
+            },
+            shutdown = &mut self.shutdown_recv => {
+                info!("Shutdown channel received");
+                self.metrics.handle_shutdown.increment();
+                self.cleanup_socket();
+                match shutdown {
+                    Ok(Ok(())) => {
+                        info!("Shutdown triggered, exiting");
+                        Ok(NockAppRun::Done)
+                    },
+                    Ok(Err(e)) => {
+                        error!("Shutdown triggered with error: {}", e);
+                        Err(e)
+                    },
+                    // Err(_recv_error) => {},
+                    Err(_recv_error) => {
+                        error!("Shutdown channel closed prematurely");
+                        Err(NockAppError::ChannelClosedError)
+                    },
+                }
+            },
+            save_guard = save_ready => {
+                self.metrics.handle_save_permit_res.increment();
+                self.handle_save_permit_res(save_guard).await
+            },
+            exit = self.exit_recv.recv() => {
+                self.metrics.handle_exit.increment();
+                info!("Exit signal received");
+                if let Some(code) = exit {
+                    self.handle_exit(code).await
+                } else {
+                    error!("Exit signal channel closed prematurely");
+                    Err(NockAppError::ChannelClosedError)
+                }
+            }
+            // FIXME: This shouldn't be hanging the event loop on the kernel poke/peek etc.
+            action_res = self.action_channel.recv() => {
+                self.metrics.handle_action.increment();
+                trace!("Action channel received");
+                match action_res {
+                    Some(action) => {
+                        self.handle_action(action).await;
+                        Ok(NockAppRun::Pending)
+                    }
+                    None => {
+                        error!("Action channel closed prematurely");
+                        Err(NockAppError::ChannelClosedError)
+                    }
+                }
+            }
+        )
+    }
+
+    #[instrument(skip_all, level = "trace")]
+    async fn handle_save_permit_res(
+        &mut self,
+        save_guard: OwnedMutexGuard<()>,
+    ) -> Result<NockAppRun, NockAppError> {
+        //  Check if we should write in the first place
+        let curr_event_num = self.kernel.serf.event_number.load(Ordering::SeqCst);
+        let saved_event_num = self.watch_recv.borrow();
+        if curr_event_num <= *saved_event_num {
+            return Ok(NockAppRun::Pending);
+        }
+        drop(saved_event_num);
+
+        let res = self.save(save_guard).await;
+
+        res.map(|_| NockAppRun::Pending)
+    }
+
+    #[instrument(skip_all)]
+    async fn handle_action(&mut self, action: IOAction) {
+        // Stop processing events if we are exiting
+        if self.exit_status.load(Ordering::SeqCst) {
+            if let IOAction::Poke { .. } = action {
+                self.metrics.poke_during_exit.increment();
+                debug!("Poked during exit. Ignoring.")
+            } else {
+                self.metrics.peek_during_exit.increment();
+                debug!("Peeked during exit. Ignoring.")
+            }
+            return;
+        }
+        match action {
+            IOAction::Poke {
+                wire,
+                poke,
+                ack_channel,
+            } => self.handle_poke(wire, poke, ack_channel).await,
+            IOAction::Peek {
+                path,
+                result_channel,
+            } => self.handle_peek(path, result_channel).await,
+        }
+    }
+
+    #[instrument(skip_all)]
+    async fn handle_poke(
+        &mut self,
+        wire: WireRepr,
+        cause: NounSlab,
+        ack_channel: tokio::sync::oneshot::Sender<PokeResult>,
+    ) {
+        let poke_result = self.kernel.poke(wire, cause).await;
+        match poke_result {
+            Ok(effects) => {
+                let _ = ack_channel.send(PokeResult::Ack);
+                for effect_slab in effects.to_vec() {
+                    let _ = self.effect_broadcast.send(effect_slab);
+                }
+            }
+            Err(_) => {
+                let _ = ack_channel.send(PokeResult::Nack);
+            }
+        }
+    }
+
+    #[instrument(skip_all)]
+    async fn handle_peek(
+        &mut self,
+        path: NounSlab,
+        result_channel: tokio::sync::oneshot::Sender<Option<NounSlab>>,
+    ) {
+        let peek_res = self.kernel.peek(path).await;
+
+        match peek_res {
+            Ok(res_slab) => {
+                let _ = result_channel.send(Some(res_slab));
+            }
+            Err(e) => {
+                error!("Peek error: {:?}", e);
+                let _ = result_channel.send(None);
+            }
+        }
+    }
+
+    // TODO: We should explicitly kick off a save somehow
+    #[instrument(skip_all)]
+    async fn handle_exit(&mut self, code: usize) -> Result<NockAppRun, NockAppError> {
+        // `cargo nextest run`
+        // 2025-01-23T01:11:52.365215Z  INFO crown::nockapp::nockapp: Exit request received, waiting for save checkpoint with event_num 60
+        // 2025-01-23T01:11:52.403120Z ERROR crown::nockapp::nockapp: Action channel closed prematurely
+        // 2025-01-23T01:11:52.403132Z ERROR crown::nockapp::nockapp: Got error running nockapp: ChannelClosedError
+        // test tests::test_compile_test_app ... FAILED
+        // self.action_channel.close();
+        // TODO: See if exit_status is duplicative of what the cancel token is for.
+        self.exit_status.store(true, Ordering::SeqCst);
+        let exit_event_num = self.kernel.serf.event_number.load(Ordering::SeqCst);
+        info!(
+            "Exit request received, waiting for save checkpoint with event_num {}",
+            exit_event_num
+        );
+
+        let mut recv = self.watch_recv.clone();
+        // let cancel_token = self.cancel_token.clone();
+        let shutdown_send = self.shutdown_send.take().unwrap_or_else(|| {
+            panic!(
+                "Panicked at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+        // self.tasks.close();
+        // self.tasks.wait().await;
+        // recv from the watch channel until we reach the exit event_num, wrapped up in a future
+        // that will send the shutdown result when we're done.
+        let socket_path = self.npc_socket_path.clone();
+        // TODO: Break this out as a separate select! handler with no spawn
+        self.tasks.spawn(async move {
+            recv.wait_for(|&new| {
+                assert!(
+                    new <= exit_event_num,
+                    "new {new:?} exit_event_num {exit_event_num:?}"
+                );
+                new == exit_event_num
+            })
+            .await
+            .expect("Failed to wait for saves to catch up to exit_event_num");
+            Self::cleanup_socket_(&socket_path);
+            info!("Save event_num reached, finishing with code {}", code);
+            let shutdown_result = if code == 0 {
+                Ok(())
+            } else {
+                Err(NockAppError::Exit(code))
+            };
+            // Ensure we send the shutdown result before canceling so that
+            // we don't get a race condition where the yielded result is
+            // "canceled" instead of the actual result.
+            info!("Sending shutdown result");
+            let _ = shutdown_send.send(shutdown_result);
+        });
+        Ok(NockAppRun::Pending)
+    }
+}

+ 546 - 0
crates/nockapp/crown/src/nockapp/test.rs

@@ -0,0 +1,546 @@
+use std::fs;
+use std::path::Path;
+use tempfile::TempDir;
+
+use crate::kernel::checkpoint::JamPaths;
+use crate::kernel::form::Kernel;
+
+use super::NockApp;
+
+pub async fn setup_nockapp(jam: &str) -> (TempDir, NockApp) {
+    let temp_dir = TempDir::new().expect("Failed to create temp directory");
+    let snap_dir = temp_dir.path().to_path_buf();
+    let jam_paths = JamPaths::new(&snap_dir);
+    // Try multiple possible locations for the jam file
+    let possible_paths = [
+        Path::new(env!("CARGO_MANIFEST_DIR"))
+            .join("test-jams")
+            .join(jam),
+        Path::new("open/crates/nockapp/crown/test-jams").join(jam),
+        Path::new("test-jams").join(jam),
+        // Add other potential paths
+    ];
+
+    let jam_bytes = possible_paths
+        .iter()
+        .find_map(|path| fs::read(path).ok())
+        .unwrap_or_else(|| panic!("Failed to read {} file from any known location", jam));
+
+    let kernel = Kernel::load(snap_dir, jam_paths, &jam_bytes, false)
+        .await
+        .expect("Could not load kernel");
+    (
+        temp_dir,
+        NockApp::new(kernel, std::time::Duration::from_secs(1)).await,
+    )
+}
+
+#[cfg(test)]
+pub mod tests {
+    use super::setup_nockapp;
+    use crate::nockapp::wire::{SystemWire, Wire};
+    use crate::noun::slab::{slab_equality, slab_noun_equality, NounSlab};
+    use crate::utils::NOCK_STACK_SIZE;
+    use crate::{NockApp, NounExt};
+    use bytes::Bytes;
+    use sword::mem::NockStack;
+    use tracing::info;
+
+    use std::sync::atomic::Ordering;
+    use std::time::Duration;
+    use sword::jets::cold::Nounable;
+    use sword::jets::util::slot;
+    use sword::noun::{Noun, D, T};
+    use sword::serialization::{cue, jam};
+    use sword::unifying_equality::unifying_equality;
+    use sword_macros::tas;
+
+    use tracing_test::traced_test;
+
+    async fn save_nockapp(nockapp: &mut NockApp) {
+        nockapp.tasks.close();
+        let permit = nockapp.save_mutex.clone().lock_owned().await;
+        let _ = nockapp.save(permit).await;
+        let _ = nockapp.tasks.wait().await;
+        nockapp.tasks.reopen();
+    }
+
+    // Panics if checkpoint failed to load, only permissible because this is expressly for testing
+    async fn spawn_save_t(nockapp: &mut NockApp, sleep_t: std::time::Duration) {
+        let sleepy_time = tokio::time::sleep(sleep_t);
+        let permit = nockapp.save_mutex.clone().lock_owned().await;
+        let _join_handle = nockapp
+            .save_f(sleepy_time, permit)
+            .await
+            .expect("Failed to spawn nockapp save task");
+        // join_handle.await.expect("Failed to save nockapp").expect("Failed to save nockapp 2");
+    }
+
+    // Test nockapp save
+    // TODO: bump the actual serf event number (can we do a poke to the test kernel?)
+    #[test]
+    #[traced_test]
+    #[cfg_attr(miri, ignore)]
+    fn test_nockapp_save_race_condition() {
+        let runtime = tokio::runtime::Builder::new_multi_thread()
+            .worker_threads(2)
+            .enable_all()
+            .build()
+            .unwrap_or_else(|err| {
+                panic!(
+                    "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            });
+        let (_temp, mut nockapp) = runtime.block_on(setup_nockapp("test-ker.jam"));
+        assert_eq!(nockapp.kernel.serf.event_number.load(Ordering::SeqCst), 0);
+        // first run
+        runtime.block_on(spawn_save_t(&mut nockapp, Duration::from_millis(1000)));
+        // second run
+        nockapp.kernel.serf.event_number.store(1, Ordering::SeqCst); // we need to set the actual serf event number
+        runtime.block_on(spawn_save_t(&mut nockapp, Duration::from_millis(5000)));
+        // Simulate what the event handlers would be doing and wait for the task tracker to be done
+        nockapp.tasks.close();
+        runtime.block_on(nockapp.tasks.wait());
+        nockapp.tasks.reopen();
+        // Shutdown the runtime immediately
+        runtime.shutdown_timeout(std::time::Duration::from_secs(0));
+        let mut stack = NockStack::new(NOCK_STACK_SIZE, 0);
+        let checkpoint = nockapp
+            .kernel
+            .serf
+            .jam_paths
+            .load_checkpoint(&mut stack)
+            .expect("Failed to get checkpoint");
+        info!("checkpoint: {:?}", checkpoint);
+        assert_eq!(checkpoint.event_num, 1);
+        assert_ne!(
+            &nockapp.kernel.serf.jam_paths.0, &nockapp.kernel.serf.jam_paths.1,
+            "After a new checkpoint the jam_paths should be different"
+        );
+    }
+
+    // Test nockapp save
+    // TODO: need a way to grab arvo state from the serf. Probably a serf action
+    // TODO: use slab equality, not unifying equality
+    #[tokio::test]
+    #[traced_test]
+    #[cfg_attr(miri, ignore)]
+    async fn test_nockapp_save() {
+        // console_subscriber::init();
+        let (_temp, mut nockapp) = setup_nockapp("test-ker.jam").await;
+        let arvo = nockapp
+            .kernel
+            .serf
+            .get_kernel_state_slab()
+            .await
+            .expect("Could not get arvo state");
+        let jam_paths = nockapp.kernel.serf.jam_paths.clone();
+        assert_eq!(nockapp.kernel.serf.event_number.load(Ordering::SeqCst), 0);
+        // Save
+        save_nockapp(&mut nockapp).await;
+        // Permit should be dropped
+
+        // A valid checkpoint should exist in one of the jam files
+        let mut checkpoint_stack = NockStack::new(NOCK_STACK_SIZE, 0);
+        let checkpoint = jam_paths.load_checkpoint(&mut checkpoint_stack);
+        assert!(checkpoint.is_ok());
+        let checkpoint = checkpoint.unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+
+        let checkpoint_state_slab = {
+            let mut slab = NounSlab::new();
+            slab.copy_into(checkpoint.ker_state);
+            slab
+        };
+
+        // Checkpoint event number should be 0
+        assert_eq!(checkpoint.event_num, 0);
+
+        // Checkpoint kernel should be equal to the saved kernel
+        assert!(slab_equality(&checkpoint_state_slab, &arvo));
+
+        info!("8");
+        // Checkpoint cold state should be equal to the saved cold state
+        let cold_chk_noun = checkpoint.cold.into_noun(&mut checkpoint_stack);
+        let cold_chk_slab = {
+            let mut slab = NounSlab::new();
+            slab.copy_into(cold_chk_noun);
+            slab
+        };
+        let cold_noun = nockapp
+            .kernel
+            .serf
+            .get_cold_state_slab()
+            .await
+            .expect("Failed to get cold state slab");
+        assert!(slab_equality(&cold_noun, &cold_chk_slab));
+    }
+
+    // Test nockapp poke
+    #[tokio::test]
+    #[traced_test]
+    #[cfg_attr(miri, ignore)]
+    async fn test_nockapp_poke_save() {
+        let (_temp, mut nockapp) = setup_nockapp("test-ker.jam").await;
+        assert_eq!(nockapp.kernel.serf.event_number.load(Ordering::SeqCst), 0);
+        let state_before_poke = nockapp
+            .kernel
+            .serf
+            .get_kernel_state_slab()
+            .await
+            .expect("Failed to get kernel state slab");
+
+        let poke_noun = D(tas!(b"inc"));
+        let poke = {
+            let mut slab = NounSlab::new();
+            slab.copy_into(poke_noun);
+            slab
+        };
+
+        let wire = SystemWire.to_wire();
+        let _ = nockapp.kernel.poke(wire, poke).await.unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+
+        // Save
+        save_nockapp(&mut nockapp).await;
+
+        // A valid checkpoint should exist in one of the jam files
+        let jam_paths = &nockapp.kernel.serf.jam_paths;
+        let mut checkpoint_stack = NockStack::new(NOCK_STACK_SIZE, 0);
+        let checkpoint = jam_paths.load_checkpoint(&mut checkpoint_stack);
+        assert!(checkpoint.is_ok());
+        let checkpoint = checkpoint.unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+
+        let checkpoint_state_slab = {
+            let mut slab = NounSlab::new();
+            slab.copy_into(checkpoint.ker_state);
+            slab
+        };
+
+        // Checkpoint event number should be 1
+        assert!(checkpoint.event_num == 1);
+        let state_after_poke = nockapp
+            .kernel
+            .serf
+            .get_kernel_state_slab()
+            .await
+            .expect("Failed to get kernel state slab");
+        assert!(slab_equality(&checkpoint_state_slab, &state_after_poke));
+        assert!(!slab_equality(&checkpoint_state_slab, &state_before_poke));
+        // Checkpoint cold state should be equal to the saved cold state
+        let mut cold_chk_noun = checkpoint.cold.into_noun(&mut checkpoint_stack);
+        let cold_slab = nockapp
+            .kernel
+            .serf
+            .get_cold_state_slab()
+            .await
+            .expect("Failed to get cold state slab");
+        let mut kernel_cold = cold_slab.copy_to_stack(&mut checkpoint_stack);
+        unsafe {
+            assert!(unifying_equality(
+                &mut checkpoint_stack, &mut cold_chk_noun, &mut kernel_cold
+            ));
+        };
+    }
+
+    #[tokio::test]
+    #[traced_test]
+    #[cfg_attr(miri, ignore)]
+    async fn test_nockapp_save_multiple() {
+        let (_temp, mut nockapp) = setup_nockapp("test-ker.jam").await;
+        assert_eq!(nockapp.kernel.serf.event_number.load(Ordering::SeqCst), 0);
+        let jam_paths = nockapp.kernel.serf.jam_paths.clone();
+        let mut stack = NockStack::new(NOCK_STACK_SIZE, 0);
+
+        for i in 1..4 {
+            // Poke to increment the state
+            let poke_noun = D(tas!(b"inc"));
+            let poke = {
+                let mut slab = NounSlab::new();
+                slab.copy_into(poke_noun);
+                slab
+            };
+            let wire = SystemWire.to_wire();
+            let _ = nockapp.kernel.poke(wire, poke).await.unwrap_or_else(|err| {
+                panic!(
+                    "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            });
+
+            // Save
+            save_nockapp(&mut nockapp).await;
+
+            // A valid checkpoint should exist in one of the jam files
+            let checkpoint = jam_paths.load_checkpoint(&mut stack);
+            assert!(checkpoint.is_ok());
+            let checkpoint = checkpoint.unwrap_or_else(|err| {
+                panic!(
+                    "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            });
+
+            // Checkpoint event number should be i
+            assert!(checkpoint.event_num == i);
+
+            // Checkpointed state should have been incremented
+            let peek_noun = T(&mut stack, &[D(tas!(b"state")), D(0)]);
+            let peek = {
+                let mut slab = NounSlab::new();
+                slab.copy_into(peek_noun);
+                slab
+            };
+
+            // res should be [~ ~ [%0 val]]
+            let mut res = nockapp.kernel.peek(peek).await.unwrap_or_else(|err| {
+                panic!(
+                    "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            });
+            res.modify_noun(|r| {
+                slot(r, 7)
+                    .unwrap_or_else(|err| {
+                        panic!(
+                            "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                            file!(),
+                            line!(),
+                            option_env!("GIT_SHA")
+                        )
+                    })
+                    .as_cell()
+                    .unwrap_or_else(|err| {
+                        panic!(
+                            "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                            file!(),
+                            line!(),
+                            option_env!("GIT_SHA")
+                        )
+                    })
+                    .tail()
+            });
+
+            let comp = {
+                let mut slab = NounSlab::new();
+                slab.copy_into(D(i));
+                slab
+            };
+
+            assert!(
+                slab_equality(&res, &comp),
+                "res: {:?} != comp: {:?}",
+                res,
+                comp
+            );
+        }
+    }
+
+    // Tests for fallback to previous checkpoint if checkpoint is corrupt
+    #[tokio::test]
+    #[traced_test]
+    #[cfg_attr(miri, ignore)]
+    async fn test_nockapp_corrupt_check() {
+        let (_temp, mut nockapp) = setup_nockapp("test-ker.jam").await;
+        assert_eq!(nockapp.kernel.serf.event_number.load(Ordering::SeqCst), 0);
+        let jam_paths = nockapp.kernel.serf.jam_paths.clone();
+
+        // Save a valid checkpoint
+        save_nockapp(&mut nockapp).await;
+
+        // Assert the checkpoint exists
+        assert!(jam_paths.0.exists());
+
+        // Generate an invalid checkpoint by incrementing the event number
+        let mut invalid = nockapp
+            .kernel
+            .checkpoint()
+            .await
+            .expect("Could not get kernel checkpoint");
+        invalid.event_num += 1;
+        assert!(!invalid.validate());
+
+        // The invalid checkpoint has a higher event number than the valid checkpoint
+        let mut checkpoint_stack = NockStack::new(NOCK_STACK_SIZE, 0);
+        let valid = jam_paths
+            .load_checkpoint(&mut checkpoint_stack)
+            .unwrap_or_else(|err| {
+                panic!(
+                    "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            });
+        assert!(valid.event_num < invalid.event_num);
+
+        // Save the corrupted checkpoint, because of the toggle buffer, we will write to jam file 1
+        assert!(!jam_paths.1.exists());
+        let jam_path = &jam_paths.1;
+        let jam_bytes = invalid.encode().unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+        tokio::fs::write(jam_path, jam_bytes)
+            .await
+            .unwrap_or_else(|err| {
+                panic!(
+                    "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            });
+
+        // The loaded checkpoint will be the valid one
+        let chk = jam_paths
+            .load_checkpoint(&mut checkpoint_stack)
+            .unwrap_or_else(|err| {
+                panic!(
+                    "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            });
+        assert!(chk.event_num == valid.event_num);
+    }
+
+    #[tokio::test]
+    #[cfg_attr(miri, ignore)]
+    async fn test_jam_equality_stack() {
+        let (_temp, nockapp) = setup_nockapp("test-ker.jam").await;
+        let kernel = nockapp.kernel;
+        let mut jam_stack = NockStack::new(NOCK_STACK_SIZE, 0);
+        let arvo_slab = kernel
+            .serf
+            .get_kernel_state_slab()
+            .await
+            .expect("Could not get kernel state slab");
+        let mut arvo = arvo_slab.copy_to_stack(&mut jam_stack);
+        let j = jam(&mut jam_stack, arvo);
+        let mut c = cue(&mut jam_stack, j).unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+        // new nockstack
+        unsafe { assert!(unifying_equality(&mut jam_stack, &mut arvo, &mut c)) }
+    }
+
+    // This actually gets used to test with miri
+    // but when it was successful it took too long.
+    #[test]
+    #[cfg_attr(miri, ignore)]
+    fn test_jam_equality_slab_no_driver() {
+        let bytes = include_bytes!("../../test-jams/test-ker.jam");
+        let mut slab1 = NounSlab::new();
+        slab1
+            .cue_into(Bytes::from(Vec::from(bytes)))
+            .unwrap_or_else(|err| {
+                panic!(
+                    "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            });
+        let jammed_bytes = slab1.jam();
+        let mut slab2 = NounSlab::new();
+        let c = slab2.cue_into(jammed_bytes).unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+        unsafe { assert!(slab_noun_equality(slab1.root(), &c)) }
+    }
+
+    #[tokio::test]
+    #[cfg_attr(miri, ignore)]
+    async fn test_jam_equality_slab() {
+        let (_temp, nockapp) = setup_nockapp("test-ker.jam").await;
+        let kernel = nockapp.kernel;
+        let mut state_slab = kernel
+            .serf
+            .get_kernel_state_slab()
+            .await
+            .expect("Could not get kernel state slab");
+        let bytes = state_slab.jam();
+        let c = state_slab.cue_into(bytes).unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+        unsafe { assert!(slab_noun_equality(state_slab.root(), &c)) }
+    }
+
+    #[tokio::test]
+    #[cfg_attr(miri, ignore)]
+    async fn test_jam_equality_slab_stack() {
+        let (_temp, nockapp) = setup_nockapp("test-ker.jam").await;
+        let kernel = nockapp.kernel;
+        let mut stack = NockStack::new(NOCK_STACK_SIZE, 0);
+        let state_slab = kernel
+            .serf
+            .get_kernel_state_slab()
+            .await
+            .expect("Failed to get kernel state slab");
+        // Use slab to jam
+        let bytes = state_slab.jam();
+        // Use the stack to cue
+        let mut c = Noun::cue_bytes(&mut stack, &bytes).unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+        let mut state_stack = state_slab.copy_to_stack(&mut stack);
+        unsafe {
+            // check for equality
+            assert!(unifying_equality(&mut stack, &mut state_stack, &mut c))
+        }
+    }
+}

+ 307 - 0
crates/nockapp/crown/src/nockapp/wire.rs

@@ -0,0 +1,307 @@
+use sword::noun::{Noun, NounAllocator, D, T};
+
+use crate::utils::make_tas;
+
+/// Standardized wire format for kernel interaction.
+pub trait Wire: Sized {
+    /// Protocol version
+    const VERSION: u64;
+
+    /// Driver/Module identifier
+    const SOURCE: &'static str;
+
+    /// Get wire for this driver. Specific implementations can add more tags to the wire as they wish but the default is just [`Self::SOURCE`] and [`Self::VERSION`].
+    fn to_wire(&self) -> WireRepr {
+        WireRepr::no_tags(Self::SOURCE, Self::VERSION)
+    }
+}
+
+/// Converts a wire to a Noun by allocating it on the kernel's stack.
+pub(crate) fn wire_to_noun<A: NounAllocator>(stack: &mut A, wire: &WireRepr) -> Noun {
+    let source_atom = make_tas(stack, wire.source);
+    let version_atom: Noun = D(wire.version);
+    if wire.tags.is_empty() {
+        T(stack, &[source_atom.as_noun(), version_atom, D(0)])
+    } else {
+        let mut wire_noun = Vec::with_capacity(wire.tags.len() + 3);
+        wire_noun.push(make_tas(stack, wire.source).as_noun());
+        wire_noun.push(D(wire.version));
+        for tag in &wire.tags {
+            wire_noun.push(tag.as_noun(stack));
+        }
+        wire_noun.push(D(0));
+
+        T(stack, &wire_noun)
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum WireTag {
+    Direct(u64),
+    String(String),
+}
+
+impl WireTag {
+    pub fn as_noun<A: NounAllocator>(&self, stack: &mut A) -> Noun {
+        match self {
+            WireTag::Direct(d) => D(*d),
+            WireTag::String(s) => make_tas(stack, s).as_noun(),
+        }
+    }
+}
+
+impl std::fmt::Display for WireTag {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            WireTag::Direct(d) => write!(f, "{}", d),
+            WireTag::String(s) => write!(f, "{}", s),
+        }
+    }
+}
+
+impl From<u8> for WireTag {
+    fn from(d: u8) -> Self {
+        WireTag::Direct(d as u64)
+    }
+}
+impl From<String> for WireTag {
+    fn from(s: String) -> Self {
+        WireTag::String(s)
+    }
+}
+
+impl From<u64> for WireTag {
+    fn from(d: u64) -> Self {
+        WireTag::Direct(d)
+    }
+}
+
+impl From<&u64> for WireTag {
+    fn from(d: &u64) -> Self {
+        WireTag::Direct(*d)
+    }
+}
+
+impl From<&str> for WireTag {
+    fn from(s: &str) -> Self {
+        WireTag::String(s.to_string())
+    }
+}
+
+/// WireRepr is intended to make the default scenario (no custom tags beyond source and version) not allocate on the heap up-front.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct WireRepr {
+    pub source: &'static str,
+    pub version: u64,
+    pub tags: Vec<WireTag>,
+}
+
+impl WireRepr {
+    pub fn new(source: &'static str, version: u64, tags: Vec<WireTag>) -> Self {
+        WireRepr {
+            source,
+            version,
+            tags,
+        }
+    }
+    pub fn no_tags(source: &'static str, version: u64) -> Self {
+        WireRepr {
+            source,
+            version,
+            tags: Vec::new(),
+        }
+    }
+    pub fn tags_as_csv(&self) -> String {
+        let mut tags = Vec::with_capacity(self.tags.len() + 2);
+        tags.push(self.source.to_string());
+        tags.push(self.version.to_string());
+        for tag in &self.tags {
+            match tag {
+                WireTag::Direct(d) => tags.push(d.to_string()),
+                WireTag::String(s) => tags.push(s.clone()),
+            }
+        }
+        tags.join(",")
+    }
+}
+
+/// System wire to use when no other wire is specified
+pub struct SystemWire;
+
+impl Wire for SystemWire {
+    const VERSION: u64 = 1;
+    const SOURCE: &'static str = "sys";
+}
+
+#[cfg(test)]
+mod test {
+    use sword_macros::tas;
+    use tracing::debug;
+
+    use crate::noun::slab::NounSlab;
+
+    use super::*;
+
+    enum NpcWire {
+        Poke(u64),
+        Pack(u64),
+        Nack(u64),
+        Bind(u64),
+    }
+
+    impl Wire for NpcWire {
+        const VERSION: u64 = 1;
+        const SOURCE: &'static str = "npc";
+
+        fn to_wire(&self) -> WireRepr {
+            let tags = match self {
+                NpcWire::Poke(pid) => vec!["poke".into(), pid.into()],
+                NpcWire::Pack(pid) => vec!["pack".into(), pid.into()],
+                NpcWire::Nack(pid) => vec!["nack".into(), pid.into()],
+                NpcWire::Bind(pid) => vec!["bind".into(), pid.into()],
+            };
+            WireRepr::new(Self::SOURCE, Self::VERSION, tags)
+        }
+    }
+
+    #[tokio::test]
+    #[cfg_attr(miri, ignore)]
+    async fn test_npc_wire_variants() {
+        let test_cases = [
+            (NpcWire::Poke(123), tas!(b"poke")),
+            (NpcWire::Pack(456), tas!(b"pack")),
+            (NpcWire::Nack(789), tas!(b"nack")),
+            (NpcWire::Bind(101), tas!(b"bind")),
+        ];
+        let wires = test_cases
+            .iter()
+            .map(|(wire, _)| wire.to_wire())
+            .collect::<Vec<_>>();
+        assert_eq!(
+            wires,
+            vec![
+                WireRepr::new("npc", 1, vec!["poke".into(), 123u64.into()]),
+                WireRepr::new("npc", 1, vec!["pack".into(), 456u64.into()]),
+                WireRepr::new("npc", 1, vec!["nack".into(), 789u64.into()]),
+                WireRepr::new("npc", 1, vec!["bind".into(), 101u64.into()]),
+            ]
+        );
+    }
+
+    #[test]
+    #[cfg_attr(miri, ignore)]
+    fn test_npc_poke_wire_format() {
+        let wire = NpcWire::Poke(123).to_wire();
+        let mut slab = NounSlab::new();
+        let wire_noun = wire_to_noun(&mut slab, &wire);
+        slab.set_root(wire_noun);
+        // Check the wire format
+        let root = unsafe { slab.root() };
+        let cell = root.as_cell().unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+        debug!("Wire: {:?}", sword::noun::DebugPath(&cell));
+        // First should be direct it's "npc", 1, "poke", 123
+        assert_eq!(
+            cell.head()
+                .as_direct()
+                .unwrap_or_else(|err| {
+                    panic!(
+                        "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                        file!(),
+                        line!(),
+                        option_env!("GIT_SHA")
+                    )
+                })
+                .as_ne_bytes(),
+            make_tas(&mut slab, NpcWire::SOURCE)
+                .as_noun()
+                .as_direct()
+                .unwrap_or_else(|err| {
+                    panic!(
+                        "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                        file!(),
+                        line!(),
+                        option_env!("GIT_SHA")
+                    )
+                })
+                .as_ne_bytes()
+        );
+
+        // Test version
+        let rest = cell.tail().as_cell().unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+        assert_eq!(
+            rest.head()
+                .as_direct()
+                .unwrap_or_else(|err| {
+                    panic!(
+                        "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                        file!(),
+                        line!(),
+                        option_env!("GIT_SHA")
+                    )
+                })
+                .data(),
+            1
+        );
+
+        // Test tag and pid
+        let tag_cell = rest.tail().as_cell().unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+        assert_eq!(
+            tag_cell
+                .head()
+                .as_direct()
+                .unwrap_or_else(|err| {
+                    panic!(
+                        "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                        file!(),
+                        line!(),
+                        option_env!("GIT_SHA")
+                    )
+                })
+                .data(),
+            tas!(b"poke")
+        );
+
+        let pid_cell = tag_cell.tail().as_cell().unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+        assert_eq!(
+            pid_cell
+                .head()
+                .as_direct()
+                .unwrap_or_else(|err| panic!(
+                    "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                ))
+                .data(),
+            123
+        );
+    }
+}

+ 280 - 0
crates/nockapp/crown/src/noun/extensions.rs

@@ -0,0 +1,280 @@
+use sword::interpreter::Error;
+use sword::mem::NockStack;
+
+use crate::noun::slab::NounSlab;
+use crate::{Noun, Result, ToBytes, ToBytesExt};
+use bincode::{Decode, Encode};
+use bytes::Bytes;
+use core::str;
+use std::iter::Iterator;
+use sword::noun::{Atom, IndirectAtom, NounAllocator, D};
+use sword::serialization::{cue, jam};
+
+pub trait NounExt {
+    fn cue_bytes(stack: &mut NockStack, bytes: &Bytes) -> Result<Noun, Error>;
+    fn cue_bytes_slice(stack: &mut NockStack, bytes: &[u8]) -> Result<Noun, Error>;
+    fn jam_self(self, stack: &mut NockStack) -> JammedNoun;
+    fn list_iter(self) -> impl Iterator<Item = Noun>;
+    fn eq_bytes(self, bytes: impl AsRef<[u8]>) -> bool;
+}
+
+impl NounExt for Noun {
+    fn cue_bytes(stack: &mut NockStack, bytes: &Bytes) -> Result<Noun, Error> {
+        let atom = Atom::from_bytes(stack, bytes);
+        cue(stack, atom)
+    }
+
+    // generally, we should be using `cue_bytes`, but if we're not going to be passing it around
+    // its OK to just cue a byte slice to avoid copying.
+    fn cue_bytes_slice(stack: &mut NockStack, bytes: &[u8]) -> Result<Noun, Error> {
+        let atom = unsafe {
+            IndirectAtom::new_raw_bytes(stack, bytes.len(), bytes.as_ptr()).normalize_as_atom()
+        };
+        cue(stack, atom)
+    }
+
+    fn jam_self(self, stack: &mut NockStack) -> JammedNoun {
+        JammedNoun::from_noun(stack, self)
+    }
+
+    fn list_iter(self) -> impl Iterator<Item = Noun> {
+        NounListIterator(self)
+    }
+
+    fn eq_bytes(self, bytes: impl AsRef<[u8]>) -> bool {
+        if let Ok(a) = self.as_atom() {
+            a.eq_bytes(bytes)
+        } else {
+            false
+        }
+    }
+}
+
+// TODO: This exists largely because crown doesn't own the [`Atom`] type from [`sword`].
+// TODO: The next step for this should be to lower the methods on this trait to a concrete `impl` stanza for [`Atom`] in [`sword`].
+// TODO: In the course of doing so, we should split out a serialization trait that has only the [`AtomExt::from_value`] method as a public API in [`sword`].
+// The goal would be to canonicalize the Atom representations of various Rust types. When it needs to be specialized, users can make a newtype.
+pub trait AtomExt {
+    fn from_bytes<A: NounAllocator>(allocator: &mut A, bytes: &Bytes) -> Atom;
+    fn from_value<A: NounAllocator, T: ToBytes>(allocator: &mut A, value: T) -> Result<Atom>;
+    fn eq_bytes(self, bytes: impl AsRef<[u8]>) -> bool;
+    fn to_bytes_until_nul(self) -> Result<Vec<u8>>;
+    fn into_string(self) -> Result<String>;
+}
+
+impl AtomExt for Atom {
+    // TODO: This is iffy. What byte representation is it expecting and why?
+    fn from_bytes<A: NounAllocator>(allocator: &mut A, bytes: &Bytes) -> Atom {
+        unsafe {
+            IndirectAtom::new_raw_bytes(allocator, bytes.len(), bytes.as_ptr()).normalize_as_atom()
+        }
+    }
+
+    // TODO: This is worth making into a public/supported part of [`sword`]'s API.
+    fn from_value<A: NounAllocator, T: ToBytes>(allocator: &mut A, value: T) -> Result<Atom> {
+        unsafe {
+            let data: Bytes = value.as_bytes()?;
+            Ok(
+                IndirectAtom::new_raw_bytes(allocator, data.len(), data.as_ptr())
+                    .normalize_as_atom(),
+            )
+        }
+    }
+
+    /** Test for byte equality, ignoring trailing 0s in the Atom representation
+        beyond the length of the bytes compared to
+    */
+    fn eq_bytes(self, bytes: impl AsRef<[u8]>) -> bool {
+        let bytes_ref = bytes.as_ref();
+        let atom_bytes = self.as_ne_bytes();
+        // TODO: Turn this into a match on a cmp?
+        #[allow(clippy::comparison_chain)]
+        if bytes_ref.len() > atom_bytes.len() {
+            false
+        } else if bytes_ref.len() == atom_bytes.len() {
+            atom_bytes == bytes_ref
+        } else {
+            // check for nul bytes beyond comparing bytestring
+            for b in &atom_bytes[bytes_ref.len()..] {
+                if *b != 0u8 {
+                    return false;
+                }
+            }
+            &atom_bytes[0..bytes_ref.len()] == bytes_ref
+        }
+    }
+
+    fn to_bytes_until_nul(self) -> Result<Vec<u8>> {
+        let bytes = str::from_utf8(self.as_ne_bytes())?;
+        Ok(bytes.trim_end_matches('\0').as_bytes().to_vec())
+    }
+
+    fn into_string(self) -> Result<String> {
+        let str = str::from_utf8(self.as_ne_bytes())?;
+        Ok(str.trim_end_matches('\0').to_string())
+    }
+}
+
+#[derive(Clone, PartialEq, Debug, Encode, Decode)]
+pub struct JammedNoun(#[bincode(with_serde)] pub Bytes);
+
+impl JammedNoun {
+    pub fn new(bytes: Bytes) -> Self {
+        Self(bytes)
+    }
+
+    pub fn from_noun(stack: &mut NockStack, noun: Noun) -> Self {
+        let jammed_atom = jam(stack, noun);
+        JammedNoun(Bytes::copy_from_slice(jammed_atom.as_ne_bytes()))
+    }
+
+    pub fn cue_self(&self, stack: &mut NockStack) -> Result<Noun, Error> {
+        let atom = unsafe {
+            IndirectAtom::new_raw_bytes(stack, self.0.len(), self.0.as_ptr()).normalize_as_atom()
+        };
+        cue(stack, atom)
+    }
+}
+
+impl From<&[u8]> for JammedNoun {
+    fn from(bytes: &[u8]) -> Self {
+        JammedNoun::new(Bytes::copy_from_slice(bytes))
+    }
+}
+
+impl From<Vec<u8>> for JammedNoun {
+    fn from(byte_vec: Vec<u8>) -> Self {
+        JammedNoun::new(Bytes::from(byte_vec))
+    }
+}
+
+impl AsRef<Bytes> for JammedNoun {
+    fn as_ref(&self) -> &Bytes {
+        &self.0
+    }
+}
+
+impl AsRef<[u8]> for JammedNoun {
+    fn as_ref(&self) -> &[u8] {
+        self.0.as_ref()
+    }
+}
+
+impl Default for JammedNoun {
+    fn default() -> Self {
+        JammedNoun::new(Bytes::new())
+    }
+}
+
+pub struct NounListIterator(Noun);
+
+impl Iterator for NounListIterator {
+    type Item = Noun;
+    fn next(&mut self) -> Option<Self::Item> {
+        if let Ok(it) = self.0.as_cell() {
+            self.0 = it.tail();
+            Some(it.head())
+        } else if unsafe { self.0.raw_equals(&D(0)) } {
+            None
+        } else {
+            panic!("Improper list terminator: {:?}", self.0)
+        }
+    }
+}
+
+pub trait IntoNoun {
+    fn into_noun(self) -> Noun;
+}
+
+impl IntoNoun for Atom {
+    fn into_noun(self) -> Noun {
+        self.as_noun()
+    }
+}
+impl IntoNoun for u64 {
+    fn into_noun(self) -> Noun {
+        unsafe { Atom::from_raw(self).into_noun() }
+    }
+}
+
+impl FromAtom for u64 {
+    fn from_atom(atom: Atom) -> Self {
+        atom.as_u64().unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        })
+    }
+}
+
+impl IntoNoun for Noun {
+    fn into_noun(self) -> Noun {
+        self
+    }
+}
+impl IntoNoun for &str {
+    fn into_noun(self) -> Noun {
+        let mut slab = NounSlab::new();
+        let contents_atom = unsafe {
+            let bytes = self.to_bytes().unwrap_or_else(|err| {
+                panic!(
+                    "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            });
+            IndirectAtom::new_raw_bytes_ref(&mut slab, bytes.as_slice()).normalize_as_atom()
+        };
+        Noun::from_atom(contents_atom)
+    }
+}
+
+pub trait AsSlabVec {
+    fn as_slab_vec(&self) -> Vec<NounSlab>;
+}
+
+impl AsSlabVec for Noun {
+    fn as_slab_vec(&self) -> Vec<NounSlab> {
+        let noun_list = *self;
+        let mut slab_vec = Vec::new();
+        for noun in noun_list.list_iter() {
+            let mut new_slab = NounSlab::new();
+            new_slab.copy_into(noun);
+            slab_vec.push(new_slab);
+        }
+        slab_vec
+    }
+}
+
+impl AsSlabVec for NounSlab {
+    fn as_slab_vec(&self) -> Vec<NounSlab> {
+        let noun_list = unsafe { self.root() };
+        noun_list.as_slab_vec()
+    }
+}
+
+pub trait FromAtom {
+    fn from_atom(atom: Atom) -> Self;
+}
+impl FromAtom for Noun {
+    fn from_atom(atom: Atom) -> Self {
+        atom.as_noun()
+    }
+}
+
+pub trait IntoSlab {
+    fn into_slab(self) -> NounSlab;
+}
+
+impl IntoSlab for &str {
+    fn into_slab(self) -> NounSlab {
+        let mut slab = NounSlab::new();
+        let noun = self.into_noun();
+        slab.set_root(noun);
+        slab
+    }
+}

+ 5 - 0
crates/nockapp/crown/src/noun/mod.rs

@@ -0,0 +1,5 @@
+mod extensions;
+mod ops;
+pub mod slab;
+pub use extensions::*;
+pub use ops::*;

+ 28 - 0
crates/nockapp/crown/src/noun/ops.rs

@@ -0,0 +1,28 @@
+use crate::utils::Result;
+use crate::CrownError;
+use sword::interpreter::{interpret, Context};
+use sword::noun::{Noun, D, T};
+use tracing::{span, Level};
+
+/// Slams (applies) a gate at a specific axis of the supplied kernel.
+///
+/// # Arguments
+/// * `context` - The interpreter cotnext.
+/// * `arvo` - The kernel.
+/// * `axis` - The axis to slam.
+/// * `ovo` - The sample noun.
+///
+/// # Returns
+///
+/// Result containing the slammed result or an error.
+#[tracing::instrument(skip(context, arvo, axis, ovo))]
+pub fn slam(context: &mut Context, arvo: Noun, axis: u64, ovo: Noun) -> Result<Noun> {
+    let stack = &mut context.stack;
+    let pul = T(stack, &[D(9), D(axis), D(0), D(2)]);
+    let sam = T(stack, &[D(6), D(0), D(7)]);
+    let fol = T(stack, &[D(8), pul, D(9), D(2), D(10), sam, D(0), D(2)]);
+    let sub = T(stack, &[arvo, ovo]);
+
+    span!(Level::DEBUG, "interpret")
+        .in_scope(|| interpret(context, sub, fol).map_err(CrownError::from))
+}

+ 1020 - 0
crates/nockapp/crown/src/noun/slab.rs

@@ -0,0 +1,1020 @@
+use crate::noun::NounExt;
+use bitvec::prelude::{BitSlice, BitVec, Lsb0};
+use bitvec::view::BitView;
+use bitvec::{bits, bitvec};
+use bytes::Bytes;
+use either::Either;
+use intmap::IntMap;
+use std::alloc::Layout;
+use std::mem::size_of;
+use std::ptr::copy_nonoverlapping;
+use sword::mem::NockStack;
+use sword::mug::{calc_atom_mug_u32, calc_cell_mug_u32, get_mug, set_mug};
+use sword::noun::{Atom, Cell, CellMemory, DirectAtom, IndirectAtom, Noun, NounAllocator, D};
+use sword::serialization::{met0_u64_to_usize, met0_usize};
+use thiserror::Error;
+
+const CELL_MEM_WORD_SIZE: usize = (size_of::<CellMemory>() + 7) >> 3;
+
+/// A (mostly*) self-contained arena for allocating nouns.
+///
+/// *Nouns may contain references to the PMA, but not other allocation arenas.
+#[derive(Debug)]
+pub struct NounSlab {
+    root: Noun,
+    slabs: Vec<(*mut u8, Layout)>,
+    allocation_start: *mut u64,
+    allocation_stop: *mut u64,
+}
+
+impl NounSlab {
+    unsafe fn raw_alloc(new_layout: Layout) -> *mut u8 {
+        if new_layout.size() == 0 {
+            std::alloc::handle_alloc_error(new_layout);
+        }
+        assert!(new_layout.align().is_power_of_two(), "Invalid alignment");
+        let slab = std::alloc::alloc(new_layout);
+        if slab.is_null() {
+            std::alloc::handle_alloc_error(new_layout);
+        } else {
+            slab
+        }
+    }
+
+    pub fn to_vec(&self) -> Vec<Self> {
+        self.root
+            .list_iter()
+            .map(|n| {
+                let mut slab = Self::new();
+                slab.copy_into(n);
+                slab
+            })
+            .collect()
+    }
+
+    pub fn modify<F: FnOnce(Noun) -> Vec<Noun>>(&mut self, f: F) {
+        let new_root_base = f(self.root);
+        let new_root = sword::noun::T(self, &new_root_base);
+        self.set_root(new_root);
+    }
+
+    pub fn modify_noun<F: FnOnce(Noun) -> Noun>(&mut self, f: F) {
+        let new_root = f(self.root);
+        self.set_root(new_root);
+    }
+
+    pub fn modify_with_imports3<F: FnOnce((Noun, Noun, Noun), Noun) -> Vec<Noun>>(
+        &mut self,
+        f: F,
+        imports: (Noun, Noun, Noun),
+    ) {
+        self.copy_into(imports.0);
+        self.copy_into(imports.1);
+        self.copy_into(imports.2);
+        let new_root_base = f(imports, self.root);
+        let new_root = sword::noun::T(self, &new_root_base);
+        self.set_root(new_root);
+    }
+}
+
+impl Clone for NounSlab {
+    fn clone(&self) -> Self {
+        let mut slab = Self::new();
+        slab.copy_into(self.root);
+        slab
+    }
+}
+
+impl NounAllocator for NounSlab {
+    unsafe fn alloc_indirect(&mut self, words: usize) -> *mut u64 {
+        let raw_size = words + 2;
+
+        // Make sure we have enough space
+        if self.allocation_start.is_null()
+            || self.allocation_start.add(raw_size) > self.allocation_stop
+        {
+            let next_idx = std::cmp::max(self.slabs.len(), min_idx_for_size(raw_size));
+            self.slabs
+                .resize(next_idx + 1, (std::ptr::null_mut(), Layout::new::<u8>()));
+            let new_size = idx_to_size(next_idx);
+            let new_layout = Layout::array::<u64>(new_size).unwrap_or_else(|err| {
+                panic!(
+                    "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            });
+            let new_slab = Self::raw_alloc(new_layout);
+            let new_slab_u64 = new_slab as *mut u64;
+            self.slabs[next_idx] = (new_slab, new_layout);
+            self.allocation_start = new_slab_u64;
+            self.allocation_stop = new_slab_u64.add(new_size);
+        }
+
+        let new_indirect_ptr = self.allocation_start;
+        self.allocation_start = self.allocation_start.add(raw_size);
+        new_indirect_ptr
+    }
+    unsafe fn alloc_cell(&mut self) -> *mut CellMemory {
+        if self.allocation_start.is_null()
+            || self.allocation_start.add(CELL_MEM_WORD_SIZE) > self.allocation_stop
+        // || (self.allocation_start as usize) + CELL_MEM_WORD_SIZE > (self.allocation_stop as usize)
+        // || (self.allocation_start.expose_provenance()) + CELL_MEM_WORD_SIZE > (self.allocation_stop.expose_provenance())
+        // || (self.allocation_start as usize) + (CELL_MEM_WORD_SIZE * std::mem::size_of::<u64>()) > (self.allocation_stop as usize)
+        {
+            let next_idx = std::cmp::max(self.slabs.len(), min_idx_for_size(CELL_MEM_WORD_SIZE));
+            self.slabs
+                .resize(next_idx + 1, (std::ptr::null_mut(), Layout::new::<u8>()));
+            let new_size = idx_to_size(next_idx);
+            let new_layout = Layout::array::<u64>(new_size).unwrap_or_else(|err| {
+                panic!(
+                    "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            });
+            let new_slab = Self::raw_alloc(new_layout);
+            let new_slab_u64 = new_slab as *mut u64;
+            self.slabs[next_idx] = (new_slab, new_layout);
+            self.allocation_start = new_slab_u64;
+            self.allocation_stop = new_slab_u64.add(new_size);
+        }
+        let new_cell_ptr = self.allocation_start as *mut CellMemory;
+        // self.allocation_start = ((self.allocation_start.expose_provenance()) + CELL_MEM_WORD_SIZE) as *mut u64;
+        self.allocation_start = std::ptr::with_exposed_provenance_mut(
+            self.allocation_start.expose_provenance()
+                + (CELL_MEM_WORD_SIZE * std::mem::size_of::<u64>()),
+        );
+        new_cell_ptr
+    }
+
+    unsafe fn alloc_struct<T>(&mut self, count: usize) -> *mut T {
+        let layout = Layout::array::<T>(count).expect("Bad layout in alloc_struct");
+        let word_size = (layout.size() + 7) >> 3;
+        assert!(layout.align() <= std::mem::size_of::<u64>());
+        if self.allocation_start.is_null()
+            || self.allocation_start.add(word_size) > self.allocation_stop
+        {
+            let next_idx = std::cmp::max(self.slabs.len(), min_idx_for_size(word_size));
+            self.slabs
+                .resize(next_idx + 1, (std::ptr::null_mut(), Layout::new::<u8>()));
+            let new_size = idx_to_size(next_idx);
+            let new_layout = Layout::array::<u64>(new_size).unwrap_or_else(|err| {
+                panic!(
+                    "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            });
+            let new_slab = Self::raw_alloc(new_layout);
+            let new_slab_u64 = new_slab as *mut u64;
+            self.slabs[next_idx] = (new_slab, new_layout);
+            self.allocation_start = new_slab_u64;
+            self.allocation_stop = new_slab_u64.add(new_size);
+        }
+        let new_struct_ptr = self.allocation_start as *mut T;
+        self.allocation_start = self.allocation_start.add(word_size);
+        new_struct_ptr
+    }
+}
+
+/// # Safety: no noun in this slab references a noun outside the slab, except in the PMA
+unsafe impl Send for NounSlab {}
+
+impl Default for NounSlab {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl From<Noun> for NounSlab {
+    fn from(noun: Noun) -> Self {
+        let mut slab = Self::new();
+        slab.copy_into(noun);
+        slab
+    }
+}
+
+impl<const N: usize> From<[Noun; N]> for NounSlab {
+    fn from(nouns: [Noun; N]) -> Self {
+        let mut slab = Self::new();
+        let new_root = sword::noun::T(&mut slab, &nouns);
+        slab.set_root(new_root);
+        slab
+    }
+}
+
+impl NounSlab {
+    /// Make a new noun slab with D(0) as the root
+    #[tracing::instrument]
+    pub fn new() -> Self {
+        let slabs = Vec::new();
+        let allocation_start: *mut u64 = std::ptr::null_mut();
+        let allocation_stop: *mut u64 = std::ptr::null_mut();
+        let root: Noun = D(0);
+        NounSlab {
+            root,
+            slabs,
+            allocation_start,
+            allocation_stop,
+        }
+    }
+
+    /// Copy a noun into this slab, only leaving references into the PMA. Set that noun as the root
+    /// noun.
+    pub fn copy_into(&mut self, copy_root: Noun) {
+        let mut copied: IntMap<u64, Noun> = IntMap::new();
+        // let mut copy_stack = vec![(copy_root, &mut self.root as *mut Noun)];
+        let mut copy_stack = vec![(copy_root, std::ptr::addr_of_mut!(self.root))];
+        while let Some((noun, dest)) = copy_stack.pop() {
+            match noun.as_either_direct_allocated() {
+                Either::Left(_direct) => {
+                    unsafe { *dest = noun };
+                }
+                Either::Right(allocated) => match allocated.as_either() {
+                    Either::Left(indirect) => {
+                        let indirect_ptr = unsafe { indirect.to_raw_pointer() };
+                        let indirect_mem_size = indirect.raw_size();
+                        if let Some(copied_noun) = copied.get(indirect_ptr as u64) {
+                            unsafe { *dest = *copied_noun };
+                            continue;
+                        }
+                        let indirect_new_mem = unsafe { self.alloc_indirect(indirect.size()) };
+                        unsafe {
+                            copy_nonoverlapping(indirect_ptr, indirect_new_mem, indirect_mem_size)
+                        };
+                        let copied_noun = unsafe {
+                            IndirectAtom::from_raw_pointer(indirect_new_mem)
+                                .as_atom()
+                                .as_noun()
+                        };
+                        copied.insert(indirect_ptr as u64, copied_noun);
+                        unsafe { *dest = copied_noun };
+                    }
+                    Either::Right(cell) => {
+                        let cell_ptr = unsafe { cell.to_raw_pointer() };
+                        if let Some(copied_noun) = copied.get(cell_ptr as u64) {
+                            unsafe { *dest = *copied_noun };
+                            continue;
+                        }
+                        let cell_new_mem = unsafe { self.alloc_cell() };
+                        unsafe { copy_nonoverlapping(cell_ptr, cell_new_mem, 1) };
+                        let copied_noun = unsafe { Cell::from_raw_pointer(cell_new_mem).as_noun() };
+                        copied.insert(cell_ptr as u64, copied_noun);
+                        unsafe { *dest = copied_noun };
+                        unsafe {
+                            // copy_stack
+                            //     .push((cell.tail(), &mut (*cell_new_mem).tail as *mut Noun));
+                            // copy_stack
+                            //     .push((cell.head(), &mut (*cell_new_mem).head as *mut Noun));
+                            copy_stack
+                                .push((cell.tail(), std::ptr::addr_of_mut!((*cell_new_mem).tail)));
+                            copy_stack
+                                .push((cell.head(), std::ptr::addr_of_mut!((*cell_new_mem).head)));
+                        }
+                    }
+                },
+            }
+        }
+    }
+
+    /// Copy the root noun from this slab into the given NockStack, only leaving references into the PMA
+    ///
+    /// Note that this consumes the slab, the slab will be freed after and the root noun returned
+    /// referencing the stack. Nouns referencing the slab should not be used past this point.
+    #[tracing::instrument(skip(self, stack), level = "trace")]
+    pub fn copy_to_stack(self, stack: &mut NockStack) -> Noun {
+        let mut res = D(0);
+        let mut copy_stack = vec![(self.root, &mut res as *mut Noun)];
+        while let Some((noun, dest)) = copy_stack.pop() {
+            if let Ok(allocated) = noun.as_allocated() {
+                if let Some(forward) = unsafe { allocated.forwarding_pointer() } {
+                    unsafe { *dest = forward.as_noun() };
+                } else {
+                    match allocated.as_either() {
+                        Either::Left(mut indirect) => {
+                            let raw_pointer = unsafe { indirect.to_raw_pointer() };
+                            let raw_size = indirect.raw_size();
+                            unsafe {
+                                let indirect_mem = stack.alloc_indirect(indirect.size());
+                                std::ptr::copy_nonoverlapping(raw_pointer, indirect_mem, raw_size);
+                                indirect.set_forwarding_pointer(indirect_mem);
+                                *dest = IndirectAtom::from_raw_pointer(indirect_mem)
+                                    .as_atom()
+                                    .as_noun();
+                            }
+                        }
+                        Either::Right(mut cell) => {
+                            let raw_pointer = unsafe { cell.to_raw_pointer() };
+                            unsafe {
+                                let cell_mem = stack.alloc_cell();
+                                copy_nonoverlapping(raw_pointer, cell_mem, 1);
+                                copy_stack.push((cell.tail(), &mut (*cell_mem).tail as *mut Noun));
+                                copy_stack.push((cell.head(), &mut (*cell_mem).head as *mut Noun));
+                                cell.set_forwarding_pointer(cell_mem);
+                                *dest = Cell::from_raw_pointer(cell_mem).as_noun()
+                            }
+                        }
+                    }
+                }
+            } else {
+                unsafe {
+                    *dest = noun;
+                } // Direct atom
+            }
+        }
+        res
+    }
+
+    /// Set the root of the noun slab.
+    ///
+    /// Panics if the given root is not in the noun slab or PMA.
+    pub fn set_root(&mut self, root: Noun) {
+        if let Ok(allocated) = root.as_allocated() {
+            match allocated.as_either() {
+                Either::Left(indirect) => {
+                    let ptr = unsafe { indirect.to_raw_pointer() };
+                    let u8_ptr = ptr as *const u8;
+                    for slab in &self.slabs {
+                        if unsafe { u8_ptr >= slab.0 && u8_ptr < slab.0.add(slab.1.size()) } {
+                            self.root = root;
+                            return;
+                        }
+                    }
+                    panic!("Set root of NounSlab to noun from outside slab");
+                }
+                Either::Right(cell) => {
+                    let ptr = unsafe { cell.to_raw_pointer() };
+                    let u8_ptr = ptr as *const u8;
+                    for slab in &self.slabs {
+                        if unsafe { u8_ptr >= slab.0 && u8_ptr < slab.0.add(slab.1.size()) } {
+                            self.root = root;
+                            return;
+                        }
+                    }
+                    panic!("Set root of NounSlab to noun from outside slab");
+                }
+            }
+        }
+        self.root = root;
+    }
+
+    pub fn jam(&self) -> Bytes {
+        let mut backref_map = NounMap::<usize>::new();
+        let mut stack = vec![self.root];
+        let mut buffer = bitvec![u8, Lsb0; 0; 0];
+        while let Some(noun) = stack.pop() {
+            if let Some(backref) = backref_map.get(noun) {
+                if let Ok(atom) = noun.as_atom() {
+                    if met0_u64_to_usize(*backref as u64) < met0_usize(atom) {
+                        mat_backref(&mut buffer, *backref);
+                    } else {
+                        mat_atom(&mut buffer, atom)
+                    }
+                } else {
+                    mat_backref(&mut buffer, *backref);
+                }
+            } else {
+                backref_map.insert(noun, buffer.len());
+                match noun.as_either_atom_cell() {
+                    Either::Left(atom) => {
+                        mat_atom(&mut buffer, atom);
+                    }
+                    Either::Right(cell) => {
+                        buffer.extend_from_bitslice(bits![u8, Lsb0; 1, 0]); // cell tag
+                        stack.push(cell.tail());
+                        stack.push(cell.head());
+                    }
+                }
+            }
+        }
+        Bytes::copy_from_slice(buffer.as_raw_slice())
+    }
+
+    pub fn cue_into(&mut self, jammed: Bytes) -> Result<Noun, CueError> {
+        let mut backref_map = IntMap::new();
+        let bitslice = jammed.view_bits::<Lsb0>();
+        let mut cursor = 0usize;
+        let mut res = D(0);
+        let mut stack = vec![CueStackEntry::DestinationPointer(&mut res)];
+        loop {
+            match stack.pop() {
+                Some(CueStackEntry::DestinationPointer(dest)) => {
+                    let backref = cursor as u64;
+                    if bitslice[cursor] {
+                        // 1
+                        cursor += 1;
+                        if bitslice[cursor] {
+                            // 1 - backref
+                            cursor += 1;
+                            let backref = rub_backref(&mut cursor, bitslice)?;
+                            if let Some(noun) = backref_map.get(backref as u64) {
+                                unsafe {
+                                    *dest = *noun;
+                                }
+                            } else {
+                                Err(CueError::BadBackref)?
+                            }
+                        } else {
+                            // 0 - cell
+                            cursor += 1;
+                            let (cell, cell_mem) = unsafe { Cell::new_raw_mut(self) };
+                            unsafe {
+                                *dest = cell.as_noun();
+                            }
+                            unsafe {
+                                stack.push(CueStackEntry::BackRef(backref, dest as *const Noun));
+                                stack
+                                    .push(CueStackEntry::DestinationPointer(&mut (*cell_mem).tail));
+                                stack
+                                    .push(CueStackEntry::DestinationPointer(&mut (*cell_mem).head));
+                            }
+                        }
+                    } else {
+                        // 0 - atom
+                        cursor += 1;
+                        unsafe { *dest = rub_atom(self, &mut cursor, bitslice)?.as_noun() };
+                        backref_map.insert(backref, unsafe { *dest });
+                    }
+                }
+                Some(CueStackEntry::BackRef(backref, noun_ptr)) => {
+                    backref_map.insert(backref, unsafe { *noun_ptr });
+                }
+                None => {
+                    break;
+                }
+            }
+        }
+        Ok(res)
+    }
+
+    /// Get the root noun
+    ///
+    /// # Safety: The noun must not be used past the lifetime of the slab.
+    pub unsafe fn root(&self) -> &Noun {
+        &self.root
+    }
+
+    /// Get the root noun
+    ///
+    /// # Safety: The noun must not be used past the lifetime of the slab.
+    pub unsafe fn root_mut(&mut self) -> &mut Noun {
+        &mut self.root
+    }
+}
+
+impl Drop for NounSlab {
+    fn drop(&mut self) {
+        for slab in self.slabs.drain(..) {
+            if !slab.0.is_null() {
+                unsafe { std::alloc::dealloc(slab.0, slab.1) };
+            }
+        }
+    }
+}
+
+fn mat_backref(buffer: &mut BitVec<u8, Lsb0>, backref: usize) {
+    if backref == 0 {
+        buffer.extend_from_bitslice(bits![u8, Lsb0; 1, 1, 1]);
+        return;
+    }
+    let backref_sz = met0_u64_to_usize(backref as u64);
+    let backref_sz_sz = met0_u64_to_usize(backref_sz as u64);
+    buffer.extend_from_bitslice(bits![u8, Lsb0; 1, 1]); // backref tag
+    let buffer_len = buffer.len();
+    buffer.resize(buffer_len + backref_sz_sz, false);
+    buffer.push(true);
+    buffer.extend_from_bitslice(
+        &BitSlice::<_, Lsb0>::from_element(&backref_sz)[0..backref_sz_sz - 1],
+    );
+    buffer.extend_from_bitslice(&BitSlice::<_, Lsb0>::from_element(&backref)[0..backref_sz]);
+}
+
+fn mat_atom(buffer: &mut BitVec<u8, Lsb0>, atom: Atom) {
+    if unsafe { atom.as_noun().raw_equals(&D(0)) } {
+        buffer.extend_from_bitslice(bits![u8, Lsb0; 0, 1]);
+        return;
+    }
+    let atom_sz = met0_usize(atom);
+    let atom_sz_sz = met0_u64_to_usize(atom_sz as u64);
+    buffer.push(false); // atom tag
+    let buffer_len = buffer.len();
+    buffer.resize(buffer_len + atom_sz_sz, false);
+    buffer.push(true);
+    buffer.extend_from_bitslice(&BitSlice::<_, Lsb0>::from_element(&atom_sz)[0..atom_sz_sz - 1]);
+    buffer.extend_from_bitslice(&atom.as_bitslice()[0..atom_sz]);
+}
+
+fn rub_backref(cursor: &mut usize, buffer: &BitSlice<u8, Lsb0>) -> Result<usize, CueError> {
+    if let Some(idx) = buffer[*cursor..].first_one() {
+        if idx == 0 {
+            *cursor += 1;
+            Ok(0)
+        } else {
+            *cursor += idx + 1;
+            let mut sz = 0usize;
+            let sz_slice = BitSlice::<_, Lsb0>::from_element_mut(&mut sz);
+            if buffer.len() < *cursor + idx - 1 {
+                Err(CueError::TruncatedBuffer)?;
+            };
+            sz_slice[0..idx - 1].clone_from_bitslice(&buffer[*cursor..*cursor + idx - 1]);
+            sz_slice.set(idx - 1, true);
+            *cursor += idx - 1;
+            if sz > size_of::<usize>() << 3 {
+                Err(CueError::BackrefTooBig)?;
+            }
+            if buffer.len() < *cursor + sz {
+                Err(CueError::TruncatedBuffer)?;
+            }
+            let mut backref = 0usize;
+            let backref_slice = BitSlice::<_, Lsb0>::from_element_mut(&mut backref);
+            backref_slice[0..sz].clone_from_bitslice(&buffer[*cursor..*cursor + sz]);
+            *cursor += sz;
+            Ok(backref)
+        }
+    } else {
+        Err(CueError::TruncatedBuffer)
+    }
+}
+
+fn rub_atom(
+    slab: &mut NounSlab,
+    cursor: &mut usize,
+    buffer: &BitSlice<u8, Lsb0>,
+) -> Result<Atom, CueError> {
+    if let Some(idx) = buffer[*cursor..].first_one() {
+        if idx == 0 {
+            *cursor += 1;
+            unsafe { Ok(DirectAtom::new_unchecked(0).as_atom()) }
+        } else {
+            *cursor += idx + 1;
+            let mut sz = 0usize;
+            let sz_slice = BitSlice::<_, Lsb0>::from_element_mut(&mut sz);
+            if buffer.len() < *cursor + idx - 1 {
+                Err(CueError::TruncatedBuffer)?;
+            }
+            sz_slice[0..idx - 1].clone_from_bitslice(&buffer[*cursor..*cursor + idx - 1]);
+            sz_slice.set(idx - 1, true);
+            *cursor += idx - 1;
+            if buffer.len() < *cursor + sz {
+                Err(CueError::TruncatedBuffer)?;
+            }
+            if sz < 64 {
+                // Direct atom: less than 64 bits
+                let mut data = 0u64;
+                let atom_slice = BitSlice::<_, Lsb0>::from_element_mut(&mut data);
+                atom_slice[0..sz].clone_from_bitslice(&buffer[*cursor..*cursor + sz]);
+                *cursor += sz;
+                Ok(unsafe { DirectAtom::new_unchecked(data).as_atom() })
+            } else {
+                // Indirect atom
+                let indirect_words = (sz + 63) >> 6; // fast round to 64-bit words
+                let (mut indirect, slice) =
+                    unsafe { IndirectAtom::new_raw_mut_bitslice(slab, indirect_words) };
+                slice[0..sz].clone_from_bitslice(&buffer[*cursor..*cursor + sz]);
+                *cursor += sz;
+                Ok(unsafe { indirect.normalize_as_atom() })
+            }
+        }
+    } else {
+        Err(CueError::TruncatedBuffer)
+    }
+}
+
+#[derive(Debug, Error)]
+pub enum CueError {
+    #[error("cue: Bad backref")]
+    BadBackref,
+    #[error("cue: backref too big")]
+    BackrefTooBig,
+    #[error("cue: truncated buffer")]
+    TruncatedBuffer,
+}
+
+/// Slab size from vector index, in 8-byte words
+fn idx_to_size(idx: usize) -> usize {
+    1 << (2 * idx + 9)
+}
+
+/// Inverse of idx_to_size
+fn min_idx_for_size(sz: usize) -> usize {
+    let mut log2sz = sz.ilog2() as usize;
+    let round_sz = 1 << log2sz;
+    if round_sz != sz {
+        log2sz += 1;
+    };
+    if log2sz <= 9 {
+        0
+    } else {
+        (log2sz - 9).div_ceil(2)
+    }
+}
+
+struct NounMap<V>(IntMap<u64, Vec<(Noun, V)>>);
+
+impl<V> NounMap<V> {
+    fn new() -> Self {
+        NounMap(IntMap::new())
+    }
+    fn insert(&mut self, key: Noun, value: V) {
+        let key_mug = slab_mug(key) as u64;
+        if let Some(vec) = self.0.get_mut(key_mug) {
+            let mut chain_iter = vec[..].iter_mut();
+            if let Some(entry) = chain_iter.find(|entry| slab_noun_equality(&key, &entry.0)) {
+                entry.1 = value;
+            } else {
+                vec.push((key, value))
+            }
+        } else {
+            self.0.insert(key_mug, vec![(key, value)]);
+        }
+    }
+
+    fn get(&mut self, key: Noun) -> Option<&V> {
+        let key_mug = slab_mug(key) as u64;
+        if let Some(vec) = self.0.get(key_mug) {
+            let mut chain_iter = vec[..].iter();
+            if let Some(entry) =
+                chain_iter.find(|entry| slab_noun_equality(&(key as Noun), &entry.0))
+            {
+                Some(&entry.1)
+            } else {
+                None
+            }
+        } else {
+            None
+        }
+    }
+}
+
+pub fn slab_equality(a: &NounSlab, b: &NounSlab) -> bool {
+    slab_noun_equality(&a.root, &b.root)
+}
+
+// Does not unify: slabs are collected all-at-once so there's no point.
+pub fn slab_noun_equality(a: &Noun, b: &Noun) -> bool {
+    let mut stack = vec![(a, b)];
+    loop {
+        if let Some((a, b)) = stack.pop() {
+            if unsafe { a.raw_equals(b) } {
+                continue;
+            }
+
+            match (
+                a.as_ref_either_direct_allocated(),
+                b.as_ref_either_direct_allocated(),
+            ) {
+                (Either::Right(a_allocated), Either::Right(b_allocated)) => {
+                    if let Some(a_mug) = a_allocated.get_cached_mug() {
+                        if let Some(b_mug) = b_allocated.get_cached_mug() {
+                            if a_mug != b_mug {
+                                break false;
+                            }
+                        }
+                    };
+
+                    match (a_allocated.as_ref_either(), b_allocated.as_ref_either()) {
+                        (Either::Left(a_indirect), Either::Left(b_indirect)) => {
+                            if a_indirect.as_slice() != b_indirect.as_slice() {
+                                break false;
+                            }
+                            continue;
+                        }
+                        (Either::Right(a_cell), Either::Right(b_cell)) => {
+                            stack.push((a_cell.tail_ref(), b_cell.tail_ref()));
+                            stack.push((a_cell.tail_ref(), b_cell.tail_ref()));
+                            continue;
+                        }
+                        _ => {
+                            break false;
+                        }
+                    }
+                }
+                _ => {
+                    break false;
+                }
+            }
+        } else {
+            break true;
+        }
+    }
+}
+
+fn slab_mug(a: Noun) -> u32 {
+    let mut stack = vec![a];
+    while let Some(noun) = stack.pop() {
+        if let Ok(mut allocated) = noun.as_allocated() {
+            if allocated.get_cached_mug().is_none() {
+                match allocated.as_either() {
+                    Either::Left(indirect) => unsafe {
+                        set_mug(&mut allocated, calc_atom_mug_u32(indirect.as_atom()));
+                    },
+                    Either::Right(cell) => match (get_mug(cell.head()), get_mug(cell.tail())) {
+                        (Some(head_mug), Some(tail_mug)) => unsafe {
+                            set_mug(&mut allocated, calc_cell_mug_u32(head_mug, tail_mug));
+                        },
+                        _ => {
+                            stack.push(noun);
+                            stack.push(cell.tail());
+                            stack.push(cell.head());
+                        }
+                    },
+                }
+            }
+        }
+    }
+    get_mug(a).expect("Noun should have a mug once mugged.")
+}
+
+enum CueStackEntry {
+    DestinationPointer(*mut Noun),
+    BackRef(u64, *const Noun),
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::AtomExt;
+    use bitvec::prelude::*;
+    use sword::noun::{D, T};
+    use sword_macros::tas;
+
+    #[test]
+    #[cfg_attr(miri, ignore)]
+    fn test_jam() {
+        let mut slab = NounSlab::new();
+        let test_noun = T(
+            &mut slab,
+            &[D(tas!(b"request")), D(tas!(b"block")), D(tas!(b"by-id")), D(0)],
+        );
+        slab.set_root(test_noun);
+        let jammed: Vec<u8> = slab.jam().to_vec();
+        println!("jammed: {:?}", jammed);
+
+        let mut stack = NockStack::new(1000, 0);
+        let mut sword_jammed: Vec<u8> = sword::serialization::jam(&mut stack, test_noun)
+            .as_ne_bytes()
+            .to_vec();
+        let sword_suffix: Vec<u8> = sword_jammed.split_off(jammed.len());
+        println!("sword_jammed: {:?}", sword_jammed);
+
+        assert_eq!(jammed, sword_jammed, "Jammed results should be identical");
+        assert!(
+            sword_suffix.iter().all(|b| { *b == 0 }),
+            "Extra bytes in sword jam should all be 0"
+        );
+    }
+
+    #[test]
+    fn test_jam_cue_roundtrip() {
+        let mut original_slab = NounSlab::new();
+        let original_noun = T(&mut original_slab, &[D(5), D(23)]);
+        println!("original_noun: {:?}", original_noun);
+        original_slab.set_root(original_noun);
+
+        // Jam the original noun
+        let jammed: Vec<u8> = original_slab.jam().to_vec();
+
+        // Cue the jammed data into a new slab
+        let mut cued_slab = NounSlab::new();
+        let cued_noun = cued_slab
+            .cue_into(jammed.into())
+            .expect("Cue should succeed");
+
+        println!("cued_noun: {:?}", cued_noun);
+
+        // Compare the original and cued nouns
+        assert!(
+            slab_noun_equality(unsafe { original_slab.root() }, &cued_noun),
+            "Original and cued nouns should be equal"
+        );
+    }
+
+    #[test]
+    fn test_complex_noun() {
+        let mut slab = NounSlab::new();
+        let complex_noun = T(
+            &mut slab,
+            &[D(tas!(b"request")), D(tas!(b"block")), D(tas!(b"by-id")), D(0)],
+        );
+        slab.set_root(complex_noun);
+
+        let jammed = slab.jam();
+        let mut cued_slab = NounSlab::new();
+        let cued_noun = cued_slab.cue_into(jammed).expect("Cue should succeed");
+
+        assert!(
+            slab_noun_equality(unsafe { slab.root() }, &cued_noun),
+            "Complex nouns should be equal after jam/cue roundtrip"
+        );
+    }
+
+    #[test]
+    fn test_indirect_atoms() {
+        let mut slab = NounSlab::new();
+        let large_number = u64::MAX as u128 + 1;
+        let large_number_bytes = Bytes::from(large_number.to_le_bytes().to_vec());
+        let indirect_atom = Atom::from_bytes(&mut slab, &large_number_bytes);
+        let noun_with_indirect = T(&mut slab, &[D(1), indirect_atom.as_noun(), D(2)]);
+        println!("noun_with_indirect: {:?}", noun_with_indirect);
+        slab.set_root(noun_with_indirect);
+
+        let jammed = slab.jam();
+        let mut cued_slab = NounSlab::new();
+        let cued_noun = cued_slab.cue_into(jammed).expect("Cue should succeed");
+        println!("cued_noun: {:?}", cued_noun);
+
+        assert!(
+            slab_noun_equality(&noun_with_indirect, &cued_noun),
+            "Nouns with indirect atoms should be equal after jam/cue roundtrip"
+        );
+    }
+
+    #[test]
+    fn test_tas_macro() {
+        let mut slab = NounSlab::new();
+        let tas_noun = T(
+            &mut slab,
+            &[D(tas!(b"foo")), D(tas!(b"bar")), D(tas!(b"baz"))],
+        );
+        slab.set_root(tas_noun);
+
+        let jammed = slab.jam();
+        let mut cued_slab = NounSlab::new();
+        let cued_noun = cued_slab.cue_into(jammed).expect("Cue should succeed");
+
+        assert!(
+            slab_noun_equality(unsafe { slab.root() }, &cued_noun),
+            "Nouns with tas! macros should be equal after jam/cue roundtrip"
+        );
+    }
+
+    #[test]
+    #[cfg_attr(miri, ignore)]
+    fn test_cue_from_file() {
+        use bytes::Bytes;
+        use std::fs::File;
+        use std::io::Read;
+        use std::path::Path;
+
+        // Path to the test file
+        // For Bazel builds, we use the test-jams directory from the environment
+        #[cfg(feature = "bazel_build")]
+        let file_path = match std::env::var("TEST_JAMS_DIR") {
+            Ok(dir) => format!("{}/cue-test.jam", dir),
+            Err(_) => String::from("test-jams/cue-test.jam"),
+        };
+
+        // For Cargo builds, we use the regular path
+        #[cfg(not(feature = "bazel_build"))]
+        let file_path = "test-jams/cue-test.jam";
+
+        // Check if the file exists
+        if !Path::new(&file_path).exists() {
+            println!("Test file not found at {}, skipping test", file_path);
+            return; // Skip the test if the file doesn't exist
+        }
+
+        // Read the jammed data from the file
+        let mut file = File::open(file_path).expect("Failed to open file");
+        let mut jammed_data = Vec::new();
+        file.read_to_end(&mut jammed_data)
+            .expect("Failed to read file");
+        let jammed = Bytes::from(jammed_data);
+
+        // Create a new NounSlab and attempt to cue the data
+        let mut slab = NounSlab::new();
+        let result = slab.cue_into(jammed);
+
+        // Assert that cue_into does not return an error
+        assert!(
+            result.is_ok(),
+            "cue_into returned an error: {:?}",
+            result.err()
+        );
+    }
+
+    #[test]
+    fn test_cyclic_structure() {
+        let mut slab = NounSlab::new();
+
+        // Create a jammed representation of a cyclic structure
+        // [0 *] where * refers back to the entire cell, i.e. 0b11110001
+        let mut jammed = BitVec::<u8, Lsb0>::new();
+        jammed.extend_from_bitslice(bits![u8, Lsb0; 1, 1, 1]); //Backref to the entire structure
+        jammed.extend_from_bitslice(bits![u8, Lsb0; 1, 0 ,0]); // Atom 0
+        jammed.extend_from_bitslice(bits![u8, Lsb0; 0, 1]); // Cell
+
+        let jammed_bytes = Bytes::from(jammed.into_vec());
+
+        let result = slab.cue_into(jammed_bytes);
+        assert!(
+            result.is_err(),
+            "Expected error due to cyclic structure, but cue_into completed successfully"
+        );
+        if let Err(e) = result {
+            println!("Error type: {:?}", e);
+            assert!(
+                matches!(e, CueError::BadBackref),
+                "Expected CueError::BadBackref, but got a different error"
+            );
+        }
+    }
+
+    #[test]
+    fn test_cue_simple_cell() {
+        let mut slab = NounSlab::new();
+
+        // Create a jammed representation of [1 0] by hand
+        let mut jammed = BitVec::<u8, Lsb0>::new();
+        jammed.extend_from_bitslice(bits![u8, Lsb0; 1, 0, 0, 0, 1, 1, 0, 1]); // 0b10110001
+
+        let jammed_bytes = Bytes::from(jammed.into_vec());
+
+        let result = slab.cue_into(jammed_bytes);
+        assert!(result.is_ok(), "cue_into should succeed");
+        if let Ok(cued_noun) = result {
+            let expected_noun = T(&mut slab, &[D(1), D(0)]);
+            assert!(
+                slab_noun_equality(&cued_noun, &expected_noun),
+                "Cued noun should equal [1 0]"
+            );
+        }
+    }
+
+    #[test]
+    fn test_cell_construction_for_noun_slab() {
+        let mut slab = NounSlab::new();
+        let (cell, cell_mem_ptr) = unsafe { Cell::new_raw_mut(&mut slab) };
+        unsafe { assert!(cell_mem_ptr as *const CellMemory == cell.to_raw_pointer()) };
+    }
+
+    #[test]
+    fn test_noun_slab_copy_into() {
+        let mut slab = NounSlab::new();
+        let test_noun = T(&mut slab, &[D(5), D(23)]);
+        slab.set_root(test_noun);
+        let mut copy_slab = NounSlab::new();
+        copy_slab.copy_into(test_noun);
+    }
+
+    // Fails in Miri
+    // #[test]
+    // fn test_alloc_cell_for_noun_slab_uninit() {
+    //     let mut slab = NounSlab::new();
+    //     let cell_ptr = unsafe { slab.alloc_cell() };
+    //     let cell: Cell = unsafe { Cell::from_raw_pointer(cell_ptr) };
+    //     unsafe { assert_eq!(cell.head().as_raw(), 0) };
+    // }
+
+    #[test]
+    fn test_alloc_cell_for_noun_slab_set_value() {
+        let mut slab = NounSlab::new();
+        let mut i = 0;
+        while i < 100 {
+            let cell_ptr = unsafe { slab.alloc_cell() };
+            let cell_memory = CellMemory {
+                metadata: 0,
+                head: D(i),
+                tail: D(i + 1),
+            };
+            unsafe { (*cell_ptr) = cell_memory };
+            i += 1;
+            println!("allocation_start: {:?}", slab.allocation_start);
+        }
+        // let cell_ptr = unsafe { slab.alloc_cell() };
+        // // Set the cell_ptr to a value
+        // let cell_memory = CellMemory { metadata: 0, head: D(5), tail: D(23) };
+        // unsafe { (*cell_ptr) = cell_memory };
+        // let cell: Cell = unsafe { Cell::from_raw_pointer(cell_ptr) };
+        // unsafe { assert_eq!(cell.head().as_raw(), 5) };
+    }
+
+    #[test]
+    fn test_nounslab_modify() {
+        let mut slab = NounSlab::new();
+        slab.modify(|root| vec![D(0), D(tas!(b"bind")), root]);
+        let mut test_slab = NounSlab::new();
+        slab_noun_equality(
+            &slab.root,
+            &T(&mut test_slab, &[D(0), D(tas!(b"bind")), D(0)]),
+        );
+        // let peek_res = unsafe { bind_slab.root_owned() };
+        // let bind_noun = T(&mut bind_slab, &[D(pid), D(tas!(b"bind")), peek_res]);
+    }
+    // // This test _should_ fail under Miri
+    // #[test]
+    // #[should_panic(expected = "error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory")]
+    // fn test_raw_alloc() {
+    //     let layout = Layout::array::<u64>(512).unwrap_or_else(|| panic!("Panicked at {}:{} (git sha: {:?})", file!(), line!(), option_env!("GIT_SHA")));
+    //     let slab = unsafe { NounSlab::raw_alloc(layout) };
+    //     assert!(!slab.is_null());
+    //     // cast doesn't hide it from Miri
+    //     let new_slab_u64 = slab as *mut u64;
+    //     let _huh = unsafe { *new_slab_u64 };
+    //     unsafe { std::alloc::dealloc(slab, layout) };
+    // }
+}

+ 291 - 0
crates/nockapp/crown/src/observability.rs

@@ -0,0 +1,291 @@
+pub fn init_tracing() -> Result<impl tracing::Subscriber, opentelemetry::trace::TraceError> {
+    use opentelemetry::trace::TracerProvider;
+    use opentelemetry_otlp::WithExportConfig;
+    use opentelemetry_sdk::trace::Sampler;
+    use tracing_subscriber::layer::SubscriberExt;
+
+    // Datadog agent OTLP endpoint configuration
+    let dd_agent_host = std::env::var("DD_AGENT_HOST").unwrap_or("localhost".to_owned());
+    let dd_trace_port = std::env::var("DD_OTLP_GRPC_PORT").unwrap_or("4317".to_owned());
+    let endpoint = format!("http://{}:{}", dd_agent_host, dd_trace_port);
+
+    eprintln!("Datadog APM endpoint (OTLP gRPC): {}", endpoint);
+
+    // Service information
+    let service_name = std::env::var("DD_SERVICE").unwrap_or("crown".to_owned());
+    let service_version = std::env::var("DD_VERSION").unwrap_or("0.1.0".to_owned());
+    let environment = std::env::var("DD_ENV").unwrap_or("development".to_owned());
+
+    // Resource attributes that Datadog requires
+    let resource = opentelemetry_sdk::Resource::new(vec![
+        opentelemetry::KeyValue::new("service.name", service_name.clone()),
+        opentelemetry::KeyValue::new("service.version", service_version),
+        opentelemetry::KeyValue::new("deployment.environment", environment),
+    ]);
+
+    // Create OTLP exporter configured for Datadog
+    let otlp_exporter = opentelemetry_otlp::SpanExporter::builder()
+        .with_tonic() // use gRPC
+        .with_endpoint(endpoint)
+        .with_timeout(std::time::Duration::from_secs(30))
+        .build()
+        .unwrap_or_else(|_| {
+            panic!(
+                "Panicked at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+
+    // Configure sampling rate - 0.1 means sample ~10% of traces
+    let sampling_ratio = std::env::var("OTEL_TRACES_SAMPLE_RATE")
+        .ok()
+        .and_then(|v| v.parse::<f64>().ok())
+        .unwrap_or(1.0);
+
+    let provider = opentelemetry_sdk::trace::TracerProvider::builder()
+        .with_batch_exporter(otlp_exporter, opentelemetry_sdk::runtime::Tokio)
+        .with_resource(resource)
+        // Add the probability sampler here
+        .with_sampler(Sampler::TraceIdRatioBased(sampling_ratio))
+        .build();
+
+    let use_ansi = std::env::var("DD_ENV").is_err();
+    let tracer = provider.tracer(service_name);
+    let telemetry = tracing_opentelemetry::layer().with_tracer(tracer);
+    let fmt_layer = tracing_subscriber::fmt::layer()
+        .with_target(true)
+        .with_thread_ids(true)
+        .with_line_number(true)
+        // .with_span_events(tracing_subscriber::fmt::format::FmtSpan::CLOSE)
+        .with_ansi(use_ansi);
+
+    let subscriber = tracing_subscriber::Registry::default()
+        .with(tracing_subscriber::EnvFilter::from_default_env())
+        .with(fmt_layer)
+        .with(telemetry);
+    Ok(subscriber)
+}
+
+// pub fn init_tracing() -> Result<impl tracing::Subscriber, opentelemetry::trace::TraceError> {
+//     use opentelemetry::trace::TracerProvider;
+//     use opentelemetry_otlp::WithExportConfig;
+//     use opentelemetry_sdk::trace::{Sampler, SamplingResult, ShouldSample};
+//     use tracing_subscriber::layer::SubscriberExt;
+//     use opentelemetry::{Context, Key, KeyValue};
+//     use opentelemetry_sdk::trace::TraceState;
+//     use std::sync::Arc;
+
+//     // Create a custom sampler that handles ERROR level spans differently
+//     #[derive(Debug)]
+//     struct ErrorAwareSampler {
+//         base_sampler: Sampler,
+//         error_level_key: Key,
+//     }
+
+//     impl ShouldSample for ErrorAwareSampler {
+//         fn should_sample(
+//             &self,
+//             parent_context: Option<&Context>,
+//             trace_id: opentelemetry::trace::TraceId,
+//             name: &str,
+//             span_kind: &opentelemetry::trace::SpanKind,
+//             attributes: &[KeyValue],
+//             links: &[opentelemetry::trace::Link],
+//         ) -> SamplingResult {
+//             // Check if this span has the ERROR level attribute
+//             for attr in attributes {
+//                 if attr.key == self.error_level_key && attr.value.as_str() == Some("ERROR") {
+//                     // Always sample error spans
+//                     return SamplingResult {
+//                         decision: opentelemetry::trace::SamplingDecision::RecordAndSample,
+//                         attributes: Vec::new(),
+//                         trace_state: TraceState::default(),
+//                     };
+//                 }
+//             }
+
+//             // For non-error spans, use the base sampler
+//             self.base_sampler.should_sample(
+//                 parent_context,
+//                 trace_id,
+//                 name,
+//                 span_kind,
+//                 attributes,
+//                 links,
+//             )
+//         }
+//     }
+
+//     let endpoint = std::env::var(JAEGER_ENDPOINT_ENV).unwrap_or("http://localhost:4317".to_owned());
+//     eprintln!("OTLP gRPC endpoint: {}", endpoint);
+//     let otlp_exporter = opentelemetry_otlp::SpanExporter::builder()
+//         .with_tonic()
+//         .with_endpoint(endpoint)
+//         .build()
+//         .unwrap();
+
+//     // Configure sampling ratio - 0.1 means sample ~10% of traces
+//     let sampling_ratio = std::env::var("OTEL_TRACES_SAMPLE_RATE")
+//         .ok()
+//         .and_then(|v| v.parse::<f64>().ok())
+//         .unwrap_or(0.1);
+
+//     // Create our custom error-aware sampler
+//     let error_aware_sampler = ErrorAwareSampler {
+//         base_sampler: Sampler::TraceIdRatioBased(sampling_ratio),
+//         error_level_key: Key::from_static_str("log.level"),
+//     };
+
+//     let provider = opentelemetry_sdk::trace::TracerProvider::builder()
+//         .with_batch_exporter(otlp_exporter, opentelemetry_sdk::runtime::Tokio)
+//         .with_sampler(error_aware_sampler)
+//         .build();
+
+//     // Add level to spans so sampler can use it
+//     let env_filter = tracing_subscriber::EnvFilter::from_default_env();
+//     let fmt_layer = tracing_subscriber::fmt::layer()
+//         .with_target(true)
+//         .with_thread_ids(true)
+//         .with_line_number(true);
+
+//     let tracer = provider.tracer("crown");
+//     let telemetry = tracing_opentelemetry::layer()
+//         .with_tracer(tracer)
+//         // This ensures log level gets propagated to span attributes
+//         .with_tracked_inactivity(true);
+
+//     let subscriber = tracing_subscriber::Registry::default()
+//         .with(env_filter)
+//         .with(fmt_layer)
+//         .with(telemetry);
+//     Ok(subscriber)
+// }
+
+// pub fn init_tracing() -> Result<impl tracing::Subscriber, opentelemetry::trace::TraceError> {
+//     use opentelemetry::trace::TracerProvider;
+//     use opentelemetry_otlp::WithExportConfig;
+//     use opentelemetry_sdk::trace::{Sampler, SamplingResult, ShouldSample};
+//     use tracing_subscriber::layer::SubscriberExt;
+//     use opentelemetry::{Context, Key, KeyValue};
+//     use opentelemetry_sdk::trace::TraceState;
+//     use std::sync::Arc;
+
+//     // Create a custom sampler that handles ERROR level spans differently
+//     #[derive(Debug)]
+//     struct ErrorAwareSampler {
+//         base_sampler: Sampler,
+//         error_level_key: Key,
+//     }
+
+//     impl ShouldSample for ErrorAwareSampler {
+//         fn should_sample(
+//             &self,
+//             parent_context: Option<&Context>,
+//             trace_id: opentelemetry::trace::TraceId,
+//             name: &str,
+//             span_kind: &opentelemetry::trace::SpanKind,
+//             attributes: &[KeyValue],
+//             links: &[opentelemetry::trace::Link],
+//         ) -> SamplingResult {
+//             // Check if this span has the ERROR level attribute
+//             for attr in attributes {
+//                 if attr.key == self.error_level_key && attr.value.as_str() == Some("ERROR") {
+//                     // Always sample error spans
+//                     return SamplingResult {
+//                         decision: opentelemetry::trace::SamplingDecision::RecordAndSample,
+//                         attributes: Vec::new(),
+//                         trace_state: TraceState::default(),
+//                     };
+//                 }
+//             }
+
+//             // For non-error spans, use the base sampler
+//             self.base_sampler.should_sample(
+//                 parent_context,
+//                 trace_id,
+//                 name,
+//                 span_kind,
+//                 attributes,
+//                 links,
+//             )
+//         }
+//     }
+
+//     let endpoint = std::env::var(JAEGER_ENDPOINT_ENV).unwrap_or("http://localhost:4317".to_owned());
+//     eprintln!("OTLP gRPC endpoint: {}", endpoint);
+//     let otlp_exporter = opentelemetry_otlp::SpanExporter::builder()
+//         .with_tonic()
+//         .with_endpoint(endpoint)
+//         .build()
+//         .unwrap();
+
+//     // Configure sampling ratio - 0.1 means sample ~10% of traces
+//     let sampling_ratio = std::env::var("OTEL_TRACES_SAMPLE_RATE")
+//         .ok()
+//         .and_then(|v| v.parse::<f64>().ok())
+//         .unwrap_or(0.1);
+
+//     // Create our custom error-aware sampler
+//     let error_aware_sampler = ErrorAwareSampler {
+//         base_sampler: Sampler::TraceIdRatioBased(sampling_ratio),
+//         error_level_key: Key::from_static_str("log.level"),
+//     };
+
+//     let provider = opentelemetry_sdk::trace::TracerProvider::builder()
+//         .with_batch_exporter(otlp_exporter, opentelemetry_sdk::runtime::Tokio)
+//         .with_sampler(error_aware_sampler)
+//         .build();
+
+//     // Add level to spans so sampler can use it
+//     let env_filter = tracing_subscriber::EnvFilter::from_default_env();
+//     let fmt_layer = tracing_subscriber::fmt::layer()
+//         .with_target(true)
+//         .with_thread_ids(true)
+//         .with_line_number(true);
+
+//     let tracer = provider.tracer("crown");
+//     let telemetry = tracing_opentelemetry::layer()
+//         .with_tracer(tracer)
+//         // This ensures log level gets propagated to span attributes
+//         .with_tracked_inactivity(true);
+
+//     let subscriber = tracing_subscriber::Registry::default()
+//         .with(env_filter)
+//         .with(fmt_layer)
+//         .with(telemetry);
+//     Ok(subscriber)
+// }
+
+// pub fn init_tracing() -> Result<impl tracing::Subscriber, opentelemetry::trace::TraceError> {
+//     use opentelemetry::trace::TracerProvider;
+//     use opentelemetry_otlp::WithExportConfig;
+//     use tracing_subscriber::layer::SubscriberExt;
+
+//     let endpoint = std::env::var(JAEGER_ENDPOINT_ENV).unwrap_or("http://localhost:4317".to_owned());
+//     eprintln!("OTLP gRPC endpoint: {}", endpoint);
+//     let otlp_exporter = opentelemetry_otlp::SpanExporter::builder()
+//         .with_tonic()
+//         .with_endpoint(endpoint)
+//         .build()
+//         .unwrap();
+
+//     let provider = opentelemetry_sdk::trace::TracerProvider::builder()
+//         .with_batch_exporter(otlp_exporter, opentelemetry_sdk::runtime::Tokio)
+//         .build();
+
+//     let tracer = provider.tracer("crown");
+//     let telemetry = tracing_opentelemetry::layer().with_tracer(tracer);
+//     let fmt_layer = tracing_subscriber::fmt::layer()
+//         .with_target(true)
+//         // .with_span_events(tracing_subscriber::fmt::format::FmtSpan::ENTER)
+//         .with_thread_ids(true)
+//         .with_line_number(true);
+
+//     let subscriber = tracing_subscriber::Registry::default()
+//         .with(tracing_subscriber::EnvFilter::from_default_env())
+//         .with(fmt_layer)
+//         .with(telemetry);
+//     Ok(subscriber)
+// }

+ 261 - 0
crates/nockapp/crown/src/utils/bytes.rs

@@ -0,0 +1,261 @@
+use crate::utils::error::ConversionError;
+use crate::{Noun, Result};
+use bytes::Bytes;
+use ibig::UBig;
+use std::any;
+use sword::jets::cold::{Nounable, NounableResult};
+use sword::noun::{Atom, NounAllocator, Slots, D, T};
+
+pub trait ToBytes {
+    fn to_bytes(&self) -> Result<Vec<u8>>;
+}
+
+impl<T: ToBytes> ToBytes for Vec<T> {
+    fn to_bytes(&self) -> Result<Vec<u8>> {
+        let mut bytes = Vec::new();
+
+        for item in self.iter() {
+            let item = item.to_bytes()?;
+            bytes.extend(item);
+        }
+
+        Ok(bytes)
+    }
+}
+
+pub trait ToBytesExt: ToBytes {
+    /// size of `size`.
+    fn to_n_bytes(&self, size: usize) -> Result<Vec<u8>>
+    where
+        Self: Sized;
+    fn to_u64(&self) -> Result<u64>;
+    fn as_bytes(&self) -> Result<Bytes>;
+}
+
+impl<T> ToBytesExt for T
+where
+    T: ToBytes,
+{
+    fn to_n_bytes(&self, size: usize) -> Result<Vec<u8>>
+    where
+        Self: Sized,
+    {
+        let mut data = T::to_bytes(self)?;
+
+        if data.len() > size {
+            return Err(ConversionError::TooBig(any::type_name::<T>().to_string()))?;
+        }
+
+        data.resize(size, 0);
+        Ok(data)
+    }
+
+    fn to_u64(&self) -> Result<u64> {
+        let bytes = T::to_bytes(self)?;
+        Ok(u64::from_le_bytes(bytes.try_into().unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        })))
+    }
+    fn as_bytes(&self) -> Result<Bytes> {
+        let bytes = T::to_bytes(self)?;
+        Ok(Bytes::from(bytes))
+    }
+}
+
+impl ToBytes for u64 {
+    fn to_bytes(&self) -> Result<Vec<u8>> {
+        Ok(self.to_le_bytes().to_vec())
+    }
+}
+
+impl<T: ToBytes, const SIZE: usize> ToBytes for [T; SIZE] {
+    fn to_bytes(&self) -> Result<Vec<u8>> {
+        let mut bytes = Vec::new();
+
+        for item in self.iter() {
+            let item = item.to_bytes()?;
+            bytes.extend(item);
+        }
+
+        Ok(bytes)
+    }
+}
+
+impl ToBytes for String {
+    fn to_bytes(&self) -> Result<Vec<u8>> {
+        Ok(self.bytes().chain(std::iter::once(0)).collect())
+    }
+}
+
+impl ToBytes for [u8] {
+    fn to_bytes(&self) -> Result<Vec<u8>> {
+        let data = self.to_vec();
+        Ok(data)
+    }
+}
+
+impl ToBytes for &[u8] {
+    fn to_bytes(&self) -> Result<Vec<u8>> {
+        let data = self.to_vec();
+        Ok(data)
+    }
+}
+
+impl ToBytes for Vec<u8> {
+    fn to_bytes(&self) -> Result<Vec<u8>> {
+        Ok(self.clone())
+    }
+}
+impl ToBytes for &str {
+    fn to_bytes(&self) -> Result<Vec<u8>> {
+        let bytes = self.bytes();
+        Ok(bytes.collect::<Vec<u8>>())
+    }
+}
+
+/// Byts is a Vec of bytes in big-endian order. It is the rust
+/// representation of the $byts hoon type which consists of [wid=@ dat=@]
+/// We do not achieve noun isomorphism due to trailing zeros being
+/// implicit in the hoon implementation given the `wid` field while being
+/// explicitly stored in the rust implementation.
+#[derive(Debug, Clone)]
+pub struct Byts(pub Vec<u8>);
+
+impl Byts {
+    pub fn new(bytes: Vec<u8>) -> Self {
+        Self(bytes)
+    }
+}
+
+impl Nounable for Byts {
+    type Target = Self;
+    fn into_noun<A: NounAllocator>(self, stack: &mut A) -> Noun {
+        let big = UBig::from_be_bytes(&self.0);
+        let wid = D(self.0.len() as u64);
+        let dat = Atom::from_ubig(stack, &big).as_noun();
+        T(stack, &[wid, dat])
+    }
+    fn from_noun<A: NounAllocator>(_stack: &mut A, noun: &Noun) -> NounableResult<Self::Target> {
+        let size = noun.slot(2)?;
+        let dat = noun.slot(3)?.as_atom()?;
+
+        let wid = size.as_atom()?.as_u64()? as usize;
+        let mut res = vec![0; wid];
+
+        let bytes_be = dat.to_be_bytes();
+
+        // Iterate over the bytes in reverse order
+        // Start copying at the first non zero value encountered
+        let mut start_copying = false;
+        let mut copy_index = 0;
+        for byte in bytes_be.iter() {
+            if *byte != 0 {
+                start_copying = true;
+            }
+            if start_copying {
+                res[copy_index] = *byte;
+                copy_index += 1;
+            }
+        }
+        Ok(Byts(res))
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use ibig::ubig;
+    use sword::interpreter::Context;
+    use sword::jets;
+    use sword::jets::cold::{FromNounError, Nounable};
+    use sword::jets::util::test::{assert_noun_eq, A};
+    use sword::noun::{D, T};
+
+    use crate::utils::bytes::Byts;
+
+    fn test_byt_direct_atom(context: &mut Context, n: u64) -> Result<(), FromNounError> {
+        // Start with a byt_noun which is a direct atom and consists of zeroes
+        let byt_noun = T(&mut context.stack, &[D(n), D(0x0)]);
+        // Convert it to byt
+        let byt = super::Byts::from_noun(&mut context.stack, &byt_noun)?;
+        // Check that it is equal
+        assert_eq!(byt.0, vec![0x00; n as usize]);
+        // Convert it back to noun
+        let roundtrip = Byts::into_noun(byt, &mut context.stack);
+        // Check that the noun is as expected, we will truncate trailing zeroes when they aren't meaningful
+        let byt_noun_with_trailing_zero = T(&mut context.stack, &[D(n), D(0)]);
+        assert_noun_eq(&mut context.stack, roundtrip, byt_noun_with_trailing_zero);
+        Ok(())
+    }
+
+    #[test]
+    #[cfg_attr(miri, ignore)]
+    fn test_by_conversion_direct() -> Result<(), FromNounError> {
+        let mut context = jets::util::test::init_context();
+
+        // Start with a byt_noun which is a direct atom and a trailing zero
+        let byt_noun = T(&mut context.stack, &[D(3), D(0x8765)]);
+        // Convert it to byt
+        let byt = super::Byts::from_noun(&mut context.stack, &byt_noun)?;
+        // Check that it is equal
+        assert_eq!(byt.0, vec![0x87, 0x65, 0x00]);
+        // Convert it back to noun
+        let roundtrip = Byts::into_noun(byt, &mut context.stack);
+        let byt_noun_with_trailing_zero = T(&mut context.stack, &[D(3), D(0x876500)]);
+        // Check that the noun is as expected, we include trailing zeros when they are meaningful
+        assert_noun_eq(&mut context.stack, roundtrip, byt_noun_with_trailing_zero);
+
+        test_byt_direct_atom(&mut context, 4)?;
+        test_byt_direct_atom(&mut context, 10)?;
+        // // Start with a byt_noun which is a direct atom and consists of zeroes
+        // let byt_noun = T(&mut context.stack, &[D(10), D(0x0)]);
+        // // Convert it to byt
+        // let byt = super::Byts::from_noun(&mut context.stack, &byt_noun)?;
+        // // Check that it is equal
+        // assert_eq!(byt.0, vec![0x00; 10]);
+        // // Convert it back to noun
+        // let roundtrip = Byts::into_noun(byt, &mut context.stack);
+        // // Check that the noun is as expected, we will truncate trailing zeroes when they aren't meaningful
+        // let byt_noun_with_trailing_zero = T(&mut context.stack, &[D(10), D(0)]);
+        // assert_noun_eq(&mut context.stack, roundtrip, byt_noun_with_trailing_zero);
+
+        Ok(())
+    }
+
+    #[test]
+    //  APOLOGIA: ibig/ubig ManuallyDrops Vec, we are aware, we plan on purging it
+    #[cfg_attr(miri, ignore)]
+    fn test_byt_conversion_indirect() -> Result<(), FromNounError> {
+        let mut context = jets::util::test::init_context();
+
+        // Start with a byt_noun is an indirect atom but fits in a u64
+        let byt = Byts(vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0]);
+        // Convert it to noun
+        let byt_noun = byt.clone().into_noun(&mut context.stack);
+        // Check that the noun is as expected
+        let expected_byt_dat = A(&mut context.stack, &ubig!(0x123456789ABCDEF0));
+        let expected_byt_noun = T(&mut context.stack, &[D(8), expected_byt_dat]);
+        assert_noun_eq(&mut context.stack, byt_noun, expected_byt_noun);
+        // Convert it back to a byt and check if it matches
+        let byt_roundtrip = Byts::from_noun(&mut context.stack, &byt_noun)?;
+        assert_eq!(byt.0, byt_roundtrip.0);
+
+        // Start with a byt_noun is an indirect atom which does not fit in a u64
+        let byt = Byts(vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x00]);
+        // Convert it to noun
+        let byt_noun = byt.clone().into_noun(&mut context.stack);
+        let expected_byt_dat = A(&mut context.stack, &ubig!(0x123456789ABCDEF000));
+        let expected_byt_noun = T(&mut context.stack, &[D(9), expected_byt_dat]);
+        // Check that the noun is as expected
+        assert_noun_eq(&mut context.stack, byt_noun, expected_byt_noun);
+        // Convert it back to a byt
+        let byt_roundtrip = Byts::from_noun(&mut context.stack, &byt_noun)?;
+        // Check that it is the same
+        assert_eq!(byt.0, byt_roundtrip.0);
+        Ok(())
+    }
+}

+ 156 - 0
crates/nockapp/crown/src/utils/error.rs

@@ -0,0 +1,156 @@
+use thiserror::Error;
+
+#[derive(Debug, Error)]
+pub enum ExternalError {
+    #[error("unknown error: {0}")]
+    UnknownError(anyhow::Error),
+    #[error("conversion error: {0}")]
+    ConversionError(String),
+    // Add other common error variants as needed
+}
+
+#[derive(Debug, Error)]
+pub enum CrownError<T = ExternalError> {
+    #[error("external")]
+    External(T),
+    #[error("mutex error")]
+    MutexError,
+    #[error("invalid kernel input")]
+    InvalidKernelInput,
+    #[error("unknown effect")]
+    UnknownEffect,
+    #[error("io error: {0}")]
+    IOError(#[from] std::io::Error),
+    #[error("Crown NounError: {0}")]
+    Noun(#[from] NounError),
+    #[error("{0}")]
+    InterpreterError(#[from] SwordError),
+    #[error("kernel error")]
+    KernelError(Option<sword::noun::Noun>),
+    #[error("{0}")]
+    Utf8FromError(#[from] std::string::FromUtf8Error),
+    #[error("{0}")]
+    Utf8Error(#[from] std::str::Utf8Error),
+    #[error("newt error")]
+    NewtError,
+    #[error("newt")]
+    Newt(#[from] anyhow::Error),
+    #[error("boot error")]
+    BootError,
+    #[error("Serf load error")]
+    SerfLoadError,
+    #[error("work bail")]
+    WorkBail,
+    #[error("peek bail")]
+    PeekBail,
+    #[error("work swap")]
+    WorkSwap,
+    #[error("tank error")]
+    TankError,
+    #[error("play bail")]
+    PlayBail,
+    #[error("queue error")]
+    QueueRecv(yaque::TryRecvError),
+    #[error("save error: {0}")]
+    SaveError(String),
+    #[error("try from int error: {0}")]
+    IntError(#[from] std::num::TryFromIntError),
+    #[error("join error: {0}")]
+    JoinError(#[from] tokio::task::JoinError),
+    #[error("decode error")]
+    DecodeError(#[from] bincode::error::DecodeError),
+    #[error("encode error")]
+    EncodeError(#[from] bincode::error::EncodeError),
+    #[error("state jam format error: the state jam file format is not recognized")]
+    StateJamFormatError,
+    #[error("unknown error: {0}")]
+    Unknown(String),
+    #[error("conversion error: {0}")]
+    ConversionError(#[from] ConversionError),
+    #[error("unknown error")]
+    UnknownError(anyhow::Error),
+    #[error("queue")]
+    QueueError(#[from] QueueErrorWrapper),
+    #[error("Serf MPSC error")]
+    SerfMPSCError(#[from] tokio::sync::mpsc::error::SendError<crate::kernel::form::SerfAction>),
+    #[error("oneshot channel error")]
+    OneshotChannelError(#[from] tokio::sync::oneshot::error::RecvError),
+}
+
+#[derive(Debug)]
+pub struct QueueErrorWrapper(pub yaque::TrySendError<Vec<u8>>);
+
+#[derive(Debug, Error)]
+pub struct SwordError(pub sword::interpreter::Error);
+
+impl std::fmt::Display for SwordError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "Sword Error: {:?}", self.0)
+    }
+}
+
+impl From<sword::interpreter::Error> for CrownError {
+    fn from(e: sword::interpreter::Error) -> Self {
+        CrownError::InterpreterError(SwordError(e))
+    }
+}
+
+impl From<sword::jets::JetErr> for CrownError {
+    fn from(e: sword::jets::JetErr) -> Self {
+        CrownError::InterpreterError(SwordError(sword::interpreter::Error::from(e)))
+    }
+}
+
+impl std::fmt::Display for QueueErrorWrapper {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "queue error: {}", self.0)
+    }
+}
+
+impl std::error::Error for QueueErrorWrapper {
+    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+        None
+    }
+}
+
+impl From<yaque::TrySendError<Vec<u8>>> for CrownError {
+    fn from(e: yaque::TrySendError<Vec<u8>>) -> Self {
+        CrownError::QueueError(QueueErrorWrapper(e))
+    }
+}
+
+#[derive(Debug, Error)]
+#[error("conversion error: {0}")]
+pub enum ConversionError {
+    TooBig(String),
+}
+
+#[derive(Debug, Error)]
+pub struct NounError(pub sword::noun::Error);
+
+impl std::fmt::Display for NounError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "Noun Error: {}", self.0)
+    }
+}
+
+impl From<sword::noun::Error> for CrownError {
+    fn from(e: sword::noun::Error) -> Self {
+        CrownError::Noun(NounError(e))
+    }
+}
+
+impl<T, E: Into<CrownError>> IntoCrownError<T> for core::result::Result<T, E> {
+    fn crown(self) -> core::result::Result<T, CrownError> {
+        match self {
+            Ok(val) => Ok(val),
+            Err(e) => Err(e.into()),
+        }
+    }
+}
+
+pub trait IntoCrownError<T> {
+    fn crown(self) -> core::result::Result<T, CrownError>;
+}
+
+pub type Result<V, E = CrownError<ExternalError>> = std::result::Result<V, E>;

+ 162 - 0
crates/nockapp/crown/src/utils/mod.rs

@@ -0,0 +1,162 @@
+pub mod bytes;
+pub mod error;
+pub mod scry;
+pub mod slogger;
+
+use byteorder::{LittleEndian, ReadBytesExt};
+pub use bytes::ToBytes;
+use either::Either;
+pub use error::{CrownError, Result};
+use slogger::CrownSlogger;
+use std::ptr::copy_nonoverlapping;
+use std::slice::from_raw_parts_mut;
+use std::time::{SystemTime, UNIX_EPOCH};
+use sword::hamt::Hamt;
+use sword::interpreter::{self, Context};
+use sword::jets::cold::Cold;
+use sword::jets::hot::{Hot, HotEntry};
+use sword::jets::warm::Warm;
+use sword::mem::NockStack;
+use sword::noun::{Atom, IndirectAtom, Noun, NounAllocator, D};
+use sword::serialization::jam;
+use sword::trace::TraceInfo;
+
+// urbit @da timestamp
+pub struct DA(pub u128);
+
+// ~1970.1.1
+const EPOCH_DA: u128 = 170141184475152167957503069145530368000;
+// ~s1
+const S1: u128 = 18446744073709551616;
+
+pub const NOCK_STACK_1KB: usize = 1 << 7;
+
+// nock stack size
+pub const NOCK_STACK_SIZE: usize = (NOCK_STACK_1KB << 10 << 10) * 8; // 2GB
+
+/**
+ *   ::  +from-unix: unix seconds to @da
+ *   ::
+ *   ++  from-unix
+ *     |=  timestamp=@ud
+ *     ^-  @da
+ *     %+  add  ~1970.1.1
+ *     (mul timestamp ~s1)
+ *   ::  +from-unix-ms: unix milliseconds to @da
+ *   ::
+ *   ++  from-unix-ms
+ *     |=  timestamp=@ud
+ *     ^-  @da
+ *     %+  add  ~1970.1.1
+ *     (div (mul ~s1 timestamp) 1.000)
+*/
+pub fn unix_ms_to_da(unix_ms: u128) -> DA {
+    DA(EPOCH_DA + ((S1 * unix_ms) / 1000))
+}
+
+pub fn da_to_unix_ms(da: DA) -> u128 {
+    ((da.0 - EPOCH_DA) * 1000) / S1
+}
+
+pub fn current_da() -> DA {
+    let start = SystemTime::now();
+    let since_the_epoch: u128 = start
+        .duration_since(UNIX_EPOCH)
+        .expect("Time went backwards")
+        .as_millis();
+    unix_ms_to_da(since_the_epoch)
+}
+
+pub fn current_epoch_ms() -> u128 {
+    let start = SystemTime::now();
+    start
+        .duration_since(UNIX_EPOCH)
+        .expect("Time went backwards")
+        .as_millis()
+}
+
+pub fn make_tas<A: NounAllocator>(allocator: &mut A, tas: &str) -> Atom {
+    let tas_bytes: &[u8] = tas.as_bytes();
+    unsafe {
+        let mut tas_atom =
+            IndirectAtom::new_raw_bytes(allocator, tas_bytes.len(), tas_bytes.as_ptr());
+        tas_atom.normalize_as_atom()
+    }
+}
+
+// serialize a noun for writing over a socket or a file descriptor
+pub fn serialize_noun(stack: &mut NockStack, noun: Noun) -> Result<Vec<u8>> {
+    let atom = jam(stack, noun);
+    let size = atom.size() << 3;
+
+    let buf = unsafe { from_raw_parts_mut(stack.struct_alloc::<u8>(size + 5), size + 5) };
+    buf[0] = 0u8;
+    buf[1] = size as u8;
+    buf[2] = (size >> 8) as u8;
+    buf[3] = (size >> 16) as u8;
+    buf[4] = (size >> 24) as u8;
+
+    match atom.as_either() {
+        Either::Left(direct) => unsafe {
+            copy_nonoverlapping(
+                &direct.data() as *const u64 as *const u8,
+                buf.as_mut_ptr().add(5),
+                size,
+            );
+        },
+        Either::Right(indirect) => unsafe {
+            copy_nonoverlapping(
+                indirect.data_pointer() as *const u8,
+                buf.as_mut_ptr().add(5),
+                size,
+            );
+        },
+    };
+    Ok(buf.to_vec())
+}
+
+pub fn compute_timer_time(time: Noun) -> Result<u64> {
+    let time_atom = time.as_atom()?;
+    let mut time_bytes: &[u8] = time_atom.as_ne_bytes();
+    let timer_time: u128 = da_to_unix_ms(DA(ReadBytesExt::read_u128::<LittleEndian>(
+        &mut time_bytes,
+    )?));
+    let now: u128 = current_epoch_ms();
+    let timer_ms: u64 = if now >= timer_time {
+        1
+    } else {
+        (timer_time - now).try_into()?
+    };
+    Ok(timer_ms)
+}
+
+/// Creates a new Nock interpreter context with all necessary components
+///
+/// This context is used to execute Nock code and interpret Hoon-generated data.
+/// It includes:
+/// - A memory stack for Nock operations
+/// - Hot, warm, and cold jet caches for optimized operations
+/// - A hash array mapped trie (HAMT) for caching
+/// - A slogger for logging
+pub fn create_context(
+    mut stack: NockStack,
+    hot_state: &[HotEntry],
+    mut cold: Cold,
+    trace_info: Option<TraceInfo>,
+) -> Context {
+    let cache = Hamt::<Noun>::new(&mut stack);
+    let hot = Hot::init(&mut stack, hot_state);
+    let warm = Warm::init(&mut stack, &mut cold, &hot);
+    let slogger = Box::pin(CrownSlogger {});
+
+    interpreter::Context {
+        stack,
+        slogger,
+        cold,
+        warm,
+        hot,
+        cache,
+        scry_stack: D(0),
+        trace_info,
+    }
+}

+ 50 - 0
crates/nockapp/crown/src/utils/scry.rs

@@ -0,0 +1,50 @@
+use either::{Left, Right};
+use sword::noun::Noun;
+
+pub enum ScryResult {
+    BadPath,    // ~
+    Nothing,    // [~ ~]
+    Some(Noun), // [~ ~ foo]
+    Invalid,    // anything that isn't one of the above
+}
+
+impl From<&Noun> for ScryResult {
+    fn from(noun: &Noun) -> ScryResult {
+        match noun.as_either_atom_cell() {
+            Left(atom) => {
+                let Ok(direct) = atom.as_direct() else {
+                    return ScryResult::Invalid;
+                };
+                if direct.data() == 0 {
+                    return ScryResult::BadPath;
+                }
+            }
+            Right(cell) => {
+                let Ok(head) = cell.head().as_direct() else {
+                    return ScryResult::Invalid;
+                };
+                if head.data() == 0 {
+                    match cell.tail().as_either_atom_cell() {
+                        Left(atom) => {
+                            let Ok(direct) = atom.as_direct() else {
+                                return ScryResult::Invalid;
+                            };
+                            if direct.data() == 0 {
+                                return ScryResult::Nothing;
+                            }
+                        }
+                        Right(tail) => {
+                            let Ok(tail_head) = tail.head().as_direct() else {
+                                return ScryResult::Invalid;
+                            };
+                            if tail_head.data() == 0 {
+                                return ScryResult::Some(tail.tail());
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        ScryResult::Invalid
+    }
+}

+ 180 - 0
crates/nockapp/crown/src/utils/slogger.rs

@@ -0,0 +1,180 @@
+use crate::{CrownError, Result};
+use assert_no_alloc::permit_alloc;
+use either::Either::*;
+use std::io::{stderr, Write};
+use sword::interpreter::Slogger;
+use sword::jets::list::util::lent;
+use sword::mem::NockStack;
+use sword::noun::{Atom, DirectAtom, IndirectAtom, Noun, Slots};
+use sword_macros::tas;
+use tracing::{debug, error, info, trace, warn};
+
+pub struct CrownSlogger;
+
+impl Slogger for CrownSlogger {
+    fn slog(&mut self, stack: &mut NockStack, pri: u64, tank: Noun) {
+        permit_alloc(|| {
+            let mut buffer = Vec::new();
+            match slog_tank(stack, tank, &mut buffer) {
+                Ok(_) => {
+                    let message = String::from_utf8_lossy(&buffer)
+                        .trim_matches('\0')
+                        .replace('\n', " ")
+                        .to_string();
+                    if !message.is_empty() {
+                        if cfg!(feature = "slog-tracing") {
+                            match pri {
+                                0 => info!(target: "slogger", "{}", message),
+                                1 => warn!(target: "slogger", "{}", message),
+                                2 => debug!(target: "slogger", "{}", message),
+                                3 => trace!(target: "slogger", "{}", message),
+                                _ => info!(target: "slogger", "{}", message),
+                            }
+                        } else {
+                            let _ = writeln!(stderr(), "{}", message);
+                        }
+                    }
+                }
+                Err(e) => {
+                    let err_msg = format!("Failed to slog tank: {}", e);
+                    if cfg!(feature = "slog-tracing") {
+                        error!(target: "slogger", "{}", err_msg);
+                    } else {
+                        let _ = writeln!(stderr(), "{}", err_msg);
+                    }
+                }
+            }
+        });
+    }
+
+    fn flog(&mut self, _stack: &mut NockStack, cord: Noun) {
+        let cord_atom = cord.as_atom().unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+        permit_alloc(|| {
+            let mut buffer = Vec::new();
+            match slog_cord(cord_atom, &mut buffer) {
+                Ok(_) => {
+                    let message = String::from_utf8_lossy(&buffer)
+                        .trim_matches('\0')
+                        .to_string();
+                    if !message.is_empty() {
+                        if cfg!(feature = "slog-tracing") {
+                            info!(target: "slogger", "{}", message);
+                        } else {
+                            let _ = writeln!(stderr(), "{}", message);
+                        }
+                    }
+                }
+                Err(e) => {
+                    let err_msg = format!("Failed to flog cord: {}", e);
+                    if cfg!(feature = "slog-tracing") {
+                        error!(target: "slogger", "{}", err_msg);
+                    } else {
+                        let _ = writeln!(stderr(), "{}", err_msg);
+                    }
+                }
+            }
+        });
+    }
+}
+
+fn slog_cord<W: Write>(cord: Atom, out: &mut W) -> Result<()> {
+    out.write_all(cord.as_ne_bytes())?;
+    Ok(())
+}
+
+fn slog_tape<W: Write>(stack: &mut NockStack, tape: Noun, out: &mut W) -> Result<()> {
+    let cord = crip(stack, tape)?;
+    slog_cord(cord, out)
+}
+
+// XX TODO: pre-crip all tapes
+fn slog_palm<W: Write>(stack: &mut NockStack, palm: Noun, out: &mut W) -> Result<()> {
+    let ds = palm.slot(6)?;
+    let fore1 = ds.slot(6)?;
+    let fore2 = ds.slot(14)?;
+    slog_tape(stack, fore1, out)?;
+    slog_tape(stack, fore2, out)?;
+    let mid = ds.slot(2)?;
+    let end = ds.slot(15)?;
+    let mut tanks = palm.slot(7)?;
+    loop {
+        if let Ok(tanks_it) = tanks.as_cell() {
+            slog_tank(stack, tanks_it.head(), out)?;
+            tanks = tanks_it.tail();
+            if tanks.is_cell() {
+                slog_tape(stack, mid, out)?;
+            }
+        } else {
+            break slog_tape(stack, end, out);
+        }
+    }
+}
+
+// XX todo: pre-crip all tapes
+fn slog_rose<W: Write>(stack: &mut NockStack, rose: Noun, out: &mut W) -> Result<()> {
+    let ds = rose.slot(6)?;
+    let fore = ds.slot(6)?;
+    slog_tape(stack, fore, out)?;
+    let mid = ds.slot(2)?;
+    let end = ds.slot(7)?;
+
+    let mut tanks = rose.slot(7)?;
+
+    loop {
+        if let Ok(tanks_it) = tanks.as_cell() {
+            slog_tank(stack, tanks_it.head(), out)?;
+            tanks = tanks_it.tail();
+            if tanks.is_cell() {
+                slog_tape(stack, mid, out)?;
+            }
+        } else {
+            break slog_tape(stack, end, out);
+        }
+    }
+}
+
+fn slog_tank<W: Write>(stack: &mut NockStack, tank: Noun, out: &mut W) -> Result<()> {
+    match tank.as_either_atom_cell() {
+        Left(cord) => slog_cord(cord, out),
+        Right(cell) => {
+            let tag = cell.head().as_direct()?;
+            match tag.data() {
+                tas!(b"leaf") => slog_tape(stack, cell.tail(), out),
+                tas!(b"palm") => slog_palm(stack, tank, out),
+                tas!(b"rose") => slog_rose(stack, tank, out),
+                _ => Err(CrownError::Unknown("Bad tank".to_string())),
+            }
+        }
+    }
+}
+
+fn crip(stack: &mut NockStack, mut tape: Noun) -> Result<Atom> {
+    let l = lent(tape)?;
+    if l == 0 {
+        return Ok(unsafe { DirectAtom::new_unchecked(0).as_atom() });
+    }
+    let (mut indirect, buf) = unsafe { IndirectAtom::new_raw_mut_bytes(stack, l) };
+
+    let mut idx = 0;
+    loop {
+        if let Ok(tape_it) = tape.as_cell() {
+            let tape_byte = tape_it.head().as_direct()?;
+            tape = tape_it.tail();
+            if tape_byte.data() >= 256 {
+                break Err(CrownError::Unknown("Bad tape".to_string()));
+            } else {
+                buf[idx] = tape_byte.data().to_le_bytes()[0];
+                idx += 1;
+            }
+        } else {
+            break Ok(unsafe { indirect.normalize_as_atom() });
+        }
+    }
+}

BIN
crates/nockapp/crown/test-jams/cue-test.jam


BIN
crates/nockapp/crown/test-jams/test-ker.jam


+ 126 - 0
crates/nockapp/crown/tests/integration.rs

@@ -0,0 +1,126 @@
+use tracing::info;
+
+use crown::nockapp::test::setup_nockapp;
+use crown::nockapp::wire::{SystemWire, Wire};
+use crown::noun::slab::NounSlab;
+use crown::NockApp;
+
+use sword::noun::{Noun, Slots, D};
+use sword_macros::tas;
+
+#[tracing::instrument(skip(nockapp))]
+fn run_once(nockapp: &mut NockApp, i: u64) {
+    info!("before poke construction");
+    let poke = D(tas!(b"inc")).into();
+    info!("Poke constructed");
+    let wire = SystemWire.to_wire();
+    info!("Wire constructed");
+    let _ = nockapp.poke_sync(wire, poke).unwrap_or_else(|err| {
+        panic!(
+            "Panicked with {err:?} at {}:{} (git sha: {:?})",
+            file!(),
+            line!(),
+            option_env!("GIT_SHA")
+        )
+    });
+    info!("after poke_sync");
+    let peek: NounSlab = [D(tas!(b"state")), D(0)].into();
+    // res should be [~ ~ %0 val]
+    let res = nockapp.peek_sync(peek);
+    info!("after peek_sync");
+    let res = res.unwrap_or_else(|err| {
+        panic!(
+            "Panicked with {err:?} at {}:{} (git sha: {:?})",
+            file!(),
+            line!(),
+            option_env!("GIT_SHA")
+        )
+    });
+    let root = unsafe { res.root() };
+    let val: Noun = root.slot(15).unwrap_or_else(|err| {
+        panic!(
+            "Panicked with {err:?} at {}:{} (git sha: {:?})",
+            file!(),
+            line!(),
+            option_env!("GIT_SHA")
+        )
+    });
+    unsafe {
+        assert!(val.raw_equals(&D(i)), "Expected {} but got {:?}", i, val);
+    }
+    info!("after raw_equals");
+}
+
+// This is just an experimental test to exercise the tracing
+// To run this test:
+// OTEL_SERVICE_NAME="nockapp_test" RUST_LOG="debug" OTEL_EXPORTER_JAEGER_ENDPOINT=http://localhost:4317 cargo nextest run test_looped_sync_peek_and_poke --nocapture --run-ignored all
+#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
+#[ignore]
+async fn test_looped_sync_peek_and_poke() {
+    use crown::observability::*;
+    let subscriber = init_tracing().unwrap_or_else(|err| {
+        panic!(
+            "Panicked with {err:?} at {}:{} (git sha: {:?})",
+            file!(),
+            line!(),
+            option_env!("GIT_SHA")
+        )
+    });
+    eprintln!("Use docker compose up to start prometheus and jaeger");
+    eprintln!("Prometheus dashboard: http://localhost:9090/");
+    eprintln!("Jaeger dashboard: http://localhost:16686/");
+    let (_temp, mut nockapp) = setup_nockapp("test-ker.jam").await;
+    tracing::subscriber::with_default(subscriber, || {
+        tracing::info!("Starting run_forever");
+        for i in 1.. {
+            info!("before run_once");
+            run_once(&mut nockapp, i);
+            info!("after run_once");
+        }
+    });
+}
+
+#[tokio::test]
+#[cfg_attr(miri, ignore)]
+async fn test_sync_peek_and_poke() {
+    let (_temp, mut nockapp) = setup_nockapp("test-ker.jam").await;
+    tokio::task::spawn_blocking(move || {
+        for i in 1..4 {
+            let poke = D(tas!(b"inc")).into();
+            let wire = SystemWire.to_wire();
+            let _ = nockapp.poke_sync(wire, poke).unwrap_or_else(|err| {
+                panic!(
+                    "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            });
+            let peek: NounSlab = [D(tas!(b"state")), D(0)].into();
+            // res should be [~ ~ %0 val]
+            let res = nockapp.peek_sync(peek);
+            let res = res.unwrap_or_else(|err| {
+                panic!(
+                    "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            });
+            let root = unsafe { res.root() };
+            let val: Noun = root.slot(15).unwrap_or_else(|err| {
+                panic!(
+                    "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            });
+            unsafe {
+                assert!(val.raw_equals(&D(i)));
+            }
+        }
+    })
+    .await
+    .expect("Synchronous test thread failed");
+}

+ 72 - 0
crates/nockapp/nested-workspace.toml

@@ -0,0 +1,72 @@
+[workspace]
+members = ["crown", "apps/choo", "apps/http-app", "apps/test-app"]
+resolver = "2"
+
+[workspace.package]
+version = "0.1.0"
+edition = "2021"
+
+[workspace.dependencies]
+sword = { git = "https://github.com/zorp-corp/sword.git", rev = "bcb2e7fba0e24d45d4887fae77389d7c5e86b507" }
+sword_macros = { git = "https://github.com/zorp-corp/sword.git", rev = "bcb2e7fba0e24d45d4887fae77389d7c5e86b507" }
+assert_no_alloc = { git = "https://github.com/zorp-corp/sword.git", rev = "bcb2e7fba0e24d45d4887fae77389d7c5e86b507" }
+
+# Uncomment for local dev of sword and comment out above
+# assert_no_alloc.path = "../sword/rust/assert_no_alloc"
+# sword.path = "../sword/rust/sword"
+# sword_macros.path = "../sword/rust/sword_macros"
+
+# External dependencies
+anyhow = "1.0"
+async-trait = "0.1"
+axum = "0.8.1"
+bincode = "2.0.0-rc.3"
+bitvec = "1.0.1"
+blake3 = { version = "1.5.1", features = ["serde"] }
+byteorder = "1.5.0"
+bytes = "1.5.0"
+clap = "4.4.4"
+console-subscriber = "0.4.1"
+either = "1.9.0"
+futures = "0.3.31"
+getrandom = "0.3.1"
+intmap = "3.1.0"
+metrics = "0.24.1"
+metrics-exporter-prometheus = "0.16.2"
+opentelemetry = { version = "0.27.1", features = [
+    "trace",
+    "logs",
+    "metrics",
+    "internal-logs",
+] }
+opentelemetry-otlp = { version = "0.27.0", features = [
+    "tonic",
+    "http-proto",
+    "reqwest-client",
+] }
+opentelemetry_sdk = { version = "0.27.1", features = ["rt-tokio"] }
+rand = "0.8.5"
+serde = "1.0.195"
+tempfile = "3.3"
+termimad = "0.31.0"
+tracing = "0.1.40"
+tracing-opentelemetry = { version = "0.28.0", features = ["metrics"] }
+tracing-subscriber = { version = "0.3.18", features = [
+    "ansi",
+    "env-filter",
+    "registry",
+] }
+tracing-test = "0.2.5"
+thiserror = "1.0"
+tokio = { version = "1.32", features = [
+    "rt",
+    "rt-multi-thread",
+    "net",
+    "macros",
+    "io-util",
+    "fs",
+    "signal",
+] }
+tokio-util = "0.7.12"
+tonic = "0.12.3"
+yaque = "0.6.6"

+ 4 - 0
crates/nockapp/rustfmt.toml

@@ -0,0 +1,4 @@
+array_width = 95
+# use_small_heuristics = "Max"
+short_array_element_width_threshold = 100
+imports_granularity = "Module"

+ 21 - 0
crates/nockchain-bitcoin-sync/Cargo.toml

@@ -0,0 +1,21 @@
+[package]
+name = "nockchain-bitcoin-sync"
+version.workspace = true
+edition.workspace = true
+
+[dependencies]
+zkvm-jetpack.workspace = true
+
+crown = { workspace = true }
+sword = { workspace = true }
+sword_macros = { workspace = true }
+
+bitcoincore-rpc.workspace = true
+ibig = { workspace = true }
+tokio = { workspace = true, features = [
+    "rt",
+    "macros",
+    "rt-multi-thread",
+    "sync",
+] }
+tracing = { workspace = true }

+ 406 - 0
crates/nockchain-bitcoin-sync/src/lib.rs

@@ -0,0 +1,406 @@
+use std::error::Error;
+use std::str::FromStr;
+use std::sync::Arc;
+
+use bitcoincore_rpc::bitcoin::BlockHash;
+use bitcoincore_rpc::bitcoincore_rpc_json::{BlockRef, GetBlockResult};
+use bitcoincore_rpc::{Auth, Client, RpcApi};
+use crown::nockapp::driver::{make_driver, IODriverFn};
+use crown::nockapp::wire::{SystemWire, Wire};
+use crown::noun::slab::NounSlab;
+use crown::{AtomExt, Bytes, ToBytes};
+use ibig::ops::DivRem;
+use ibig::{ubig, UBig};
+use sword::noun::{Atom, Noun, NounAllocator, D, T};
+use sword_macros::tas;
+use tokio::sync::oneshot::channel;
+use tokio::sync::RwLock;
+use tracing::{debug, info, instrument};
+use zkvm_jetpack::form::math::base::PRIME;
+
+/** Hash used for test genesis block */
+const TEST_GENESIS_BLOCK_HASH: &str =
+    "00000000e6c3c75c18bdb06cc39d616d636fca0fc967c29ebf8225ddf7f2fe48";
+const TEST_GENESIS_BLOCK_HEIGHT: u64 = 2048;
+
+const TEST_GENESIS_SEAL_MSG: &str = "8UZkpNdGKTPGafrbkGZHwUeeWMu7gLzpBsPuffVkDydnSB4NFRXwZsB";
+
+/// Helper function to get the test block used for fake genesis blocks
+fn get_test_block() -> BlockRef {
+    BlockRef {
+        hash: BlockHash::from_str(TEST_GENESIS_BLOCK_HASH).unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        }),
+        height: TEST_GENESIS_BLOCK_HEIGHT,
+    }
+}
+
+/// Convert a GetBlockResult to a BlockRef
+fn get_block_ref_from_result(block: &GetBlockResult) -> BlockRef {
+    BlockRef {
+        hash: block.hash,
+        height: block.height as u64,
+    }
+}
+
+/// Represents the type of node in the network
+#[derive(Debug)]
+pub enum GenesisNodeType {
+    /// A node that will attempt to mine the genesis block
+    Leader,
+    /// A node that will wait to see a genesis block but not mine it
+    Watcher,
+}
+
+/// Connection information for the Bitcoin watcher
+#[derive(Debug)]
+pub struct BitcoinRPCConnection {
+    /// The URL of the Bitcoin RPC node
+    pub url: String,
+    /// Authentication credentials for the Bitcoin RPC node
+    pub auth: Auth,
+    /// The desired block height to watch for
+    pub wanted_height: u64,
+}
+
+impl BitcoinRPCConnection {
+    /// Create a new BitcoinRPCConnection
+    pub fn new(url: String, auth: Auth, wanted_height: u64) -> Self {
+        BitcoinRPCConnection {
+            url,
+            auth,
+            wanted_height,
+        }
+    }
+}
+
+#[instrument]
+pub fn bitcoin_watcher_driver(
+    connection: Option<BitcoinRPCConnection>,
+    node_type: GenesisNodeType,
+    message: String,
+    init_signal_tx: Option<tokio::sync::oneshot::Sender<()>>,
+) -> IODriverFn {
+    make_driver(|handle| async move {
+        debug!(
+            "Starting bitcoin_watcher_driver with node_type: {:?}",
+            node_type
+        );
+        if let Some(conn) = connection {
+            debug!("Using Bitcoin RPC connection to: {}", conn.url);
+            let watcher = BitcoinWatcher::new(conn).await.unwrap_or_else(|err| {
+                panic!(
+                    "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            });
+            debug!("BitcoinWatcher initialized successfully");
+
+            match node_type {
+                GenesisNodeType::Leader => {
+                    debug!("Node type is Miner, watching for target bitcoin block...");
+                    let block = watcher.watch().await.unwrap_or_else(|err| {
+                        panic!(
+                            "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                            file!(),
+                            line!(),
+                            option_env!("GIT_SHA")
+                        )
+                    });
+                    debug!(
+                        "Target bitcoin block found: hash={}, height={}",
+                        block.hash, block.height
+                    );
+
+                    let mut poke_slab = NounSlab::new();
+                    let block_height_noun =
+                        Atom::new(&mut poke_slab, TEST_GENESIS_BLOCK_HEIGHT).as_noun();
+                    let seal_byts =
+                        Bytes::from(TEST_GENESIS_SEAL_MSG.to_bytes().expect("blah 242"));
+                    let seal_noun = Atom::from_bytes(&mut poke_slab, &seal_byts).as_noun();
+                    let set_genesis_seal_byts = Bytes::from(b"set-genesis-seal".to_vec());
+                    let set_genesis_seal =
+                        Atom::from_bytes(&mut poke_slab, &set_genesis_seal_byts).as_noun();
+                    let poke_noun = T(
+                        &mut poke_slab,
+                        &[D(tas!(b"command")), set_genesis_seal, block_height_noun, seal_noun],
+                    );
+                    poke_slab.set_root(poke_noun);
+
+                    let wire = SystemWire.to_wire();
+                    debug!("Setting genesis seal");
+                    handle.poke(wire, poke_slab).await?;
+                    debug!("Genesis seal set successfully");
+
+                    let mut poke_slab = NounSlab::new();
+                    let template = bitcoin_block_to_genesis_template(
+                        &mut poke_slab,
+                        get_block_ref_from_result(&block),
+                        &message,
+                    );
+                    let poke_noun = T(
+                        &mut poke_slab,
+                        &[D(tas!(b"command")), D(tas!(b"genesis")), template],
+                    );
+                    poke_slab.set_root(poke_noun);
+
+                    let wire = SystemWire.to_wire();
+                    debug!("Setting genesis template");
+                    handle.poke(wire, poke_slab).await?;
+                    debug!("Genesis template set successfully");
+
+                    // Signal that initialization is complete
+                    if let Some(tx) = init_signal_tx {
+                        let _ = tx.send(());
+                        info!("Bitcoin watcher driver initialization complete signal sent");
+                    }
+                }
+                GenesisNodeType::Watcher => {
+                    debug!("Node type is Node, watching for bitcoin block for genesis...");
+
+                    let mut poke_slab = NounSlab::new();
+                    let block_height_noun =
+                        Atom::new(&mut poke_slab, TEST_GENESIS_BLOCK_HEIGHT).as_noun();
+                    let seal_byts =
+                        Bytes::from(TEST_GENESIS_SEAL_MSG.to_bytes().expect("blah 242"));
+                    let seal_noun = Atom::from_bytes(&mut poke_slab, &seal_byts).as_noun();
+                    let set_genesis_seal_byts = Bytes::from(b"set-genesis-seal".to_vec());
+                    let set_genesis_seal =
+                        Atom::from_bytes(&mut poke_slab, &set_genesis_seal_byts).as_noun();
+                    let poke_noun = T(
+                        &mut poke_slab,
+                        &[D(tas!(b"command")), set_genesis_seal, block_height_noun, seal_noun],
+                    );
+                    poke_slab.set_root(poke_noun);
+
+                    let wire = SystemWire.to_wire();
+                    debug!("Setting genesis seal");
+                    handle.poke(wire, poke_slab).await?;
+                    debug!("Genesis seal set successfully");
+
+                    let block = watcher
+                        .watch()
+                        .await
+                        .expect("Failed to watch for bitcoin block for genesis");
+                    debug!(
+                        "Bitcoin block for genesis found: hash={}, height={}",
+                        block.hash, block.height
+                    );
+                    let mut poke_slab = NounSlab::new();
+
+                    // Send a %btc-data command with just the block hash
+                    let hash_tuple = block_hash_to_belts(&mut poke_slab, &block.hash);
+                    let poke_noun = T(
+                        &mut poke_slab,
+                        &[D(tas!(b"command")), D(tas!(b"btc-data")), hash_tuple],
+                    );
+                    poke_slab.set_root(poke_noun);
+
+                    debug!("Sending btc-data command");
+                    let wire = SystemWire.to_wire();
+                    handle.poke(wire, poke_slab).await?;
+                    debug!("btc-data command sent successfully");
+                    // Signal that initialization is complete
+                    if let Some(tx) = init_signal_tx {
+                        let _ = tx.send(());
+                        info!("Bitcoin watcher driver initialization complete signal sent");
+                    }
+                }
+            }
+        } else {
+            debug!("No Bitcoin RPC connection provided, using test genesis block");
+            let wire = SystemWire.to_wire();
+            match node_type {
+                GenesisNodeType::Leader => {
+                    debug!("Creating test genesis block for leader node");
+                    let poke_slab = make_test_genesis_block(&message);
+                    handle.poke(wire, poke_slab).await?;
+                    debug!("test genesis block template sent successfully");
+                    // Signal that initialization is complete
+                    if let Some(tx) = init_signal_tx {
+                        let _ = tx.send(());
+                        info!("Bitcoin watcher driver initialization complete signal sent");
+                    }
+                }
+                GenesisNodeType::Watcher => {
+                    debug!("Send %btc-data command with test genesis block hash for watcher node");
+                    // For nodes, send a %btc-data command with the hash from make_test_genesis_block
+                    let mut poke_slab = NounSlab::new();
+                    let test_block = get_test_block();
+                    debug!(
+                        "Using test block: hash={}, height={}",
+                        test_block.hash, test_block.height
+                    );
+
+                    // Send a %btc-data command with just the block hash
+                    let hash_tuple = block_hash_to_belts(&mut poke_slab, &test_block.hash);
+                    let poke_noun = T(
+                        &mut poke_slab,
+                        &[D(tas!(b"command")), D(tas!(b"btc-data")), hash_tuple],
+                    );
+                    poke_slab.set_root(poke_noun);
+
+                    handle.poke(wire, poke_slab).await?;
+                    debug!("btc-data command for fake genesis block sent successfully");
+                    // Signal that initialization is complete
+                    if let Some(tx) = init_signal_tx {
+                        let _ = tx.send(());
+                        info!("Bitcoin watcher driver initialization complete signal sent");
+                    }
+                }
+            }
+        }
+        debug!("bitcoin_watcher_driver completed successfully");
+        Ok(())
+    })
+}
+
+fn make_test_genesis_block(message: &String) -> NounSlab {
+    // we use bitcoin block 2048 for testing
+    let test_block = get_test_block();
+    let mut poke_slab = NounSlab::new();
+
+    let template = bitcoin_block_to_genesis_template(&mut poke_slab, test_block, message);
+    let poke_noun = T(
+        &mut poke_slab,
+        &[D(tas!(b"command")), D(tas!(b"genesis")), template],
+    );
+    poke_slab.set_root(poke_noun);
+
+    poke_slab
+}
+
+fn bitcoin_block_to_genesis_template<A: NounAllocator>(
+    allocator: &mut A,
+    block: BlockRef,
+    message: &String,
+) -> Noun {
+    let hash_tuple = block_hash_to_belts(allocator, &block.hash);
+    let block_height_noun = Atom::new(allocator, block.height).as_noun();
+    let msg_byts = Bytes::from(message.to_bytes().expect("blah 242"));
+    let message_noun = Atom::from_bytes(allocator, &msg_byts).as_noun();
+    T(allocator, &[hash_tuple, block_height_noun, message_noun])
+}
+
+fn block_hash_to_belts<A: NounAllocator>(allocator: &mut A, hash: &BlockHash) -> Noun {
+    let hash_ubig = UBig::from_le_bytes(hash.as_ref());
+
+    let (hash_ubig_1, digit_0) = hash_ubig.div_rem(PRIME);
+    let (hash_ubig_2, digit_1) = hash_ubig_1.div_rem(PRIME);
+    let (hash_ubig_3, digit_2) = hash_ubig_2.div_rem(PRIME);
+    let (hash_ubig_4, digit_3) = hash_ubig_3.div_rem(PRIME);
+    let (hash_ubig_5, digit_4) = hash_ubig_4.div_rem(PRIME);
+    let (hash_ubig_6, digit_5) = hash_ubig_5.div_rem(PRIME);
+    let (hash_ubig_7, digit_6) = hash_ubig_6.div_rem(PRIME);
+    let (hash_ubig_8, digit_7) = hash_ubig_7.div_rem(PRIME);
+    assert!(hash_ubig_8 == ubig!(0));
+
+    let mut tuple_elements = Vec::new();
+
+    for digit in [digit_0, digit_1, digit_2, digit_3, digit_4, digit_5, digit_6, digit_7] {
+        tuple_elements.push(Atom::new(allocator, digit).as_noun());
+    }
+
+    T(allocator, &tuple_elements[..])
+}
+
+#[derive(Debug)]
+pub struct BitcoinWatcher {
+    wanted_height: u64,
+    client: Arc<RwLock<Client>>,
+}
+
+impl BitcoinWatcher {
+    pub async fn new(connection: BitcoinRPCConnection) -> Result<BitcoinWatcher, Box<dyn Error>> {
+        debug!(
+            "Creating new BitcoinWatcher with wanted_height: {}",
+            connection.wanted_height
+        );
+        let (tx, rx) = channel();
+        tokio::task::spawn_blocking(move || {
+            let new_result = Client::new(connection.url.as_ref(), connection.auth).map(|c| {
+                let client = Arc::new(RwLock::new(c));
+                BitcoinWatcher {
+                    wanted_height: connection.wanted_height,
+                    client,
+                }
+            });
+            tx.send(new_result).unwrap_or_else(|err| {
+                panic!(
+                    "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            });
+        })
+        .await?;
+        Ok(rx.await??)
+    }
+
+    #[allow(clippy::comparison_chain)]
+    pub async fn watch(&self) -> Result<GetBlockResult, Box<dyn Error>> {
+        let wanted_height = self.wanted_height;
+        debug!("Starting watch() for block at height: {}", wanted_height);
+
+        loop {
+            debug!("Attempting to get block hash at height: {}", wanted_height);
+            let client = self.client.clone().read_owned().await;
+
+            // Try to get the block hash at the desired height
+            let block_hash_result =
+                tokio::task::spawn_blocking(move || (*client).get_block_hash(wanted_height))
+                    .await?;
+
+            match block_hash_result {
+                Ok(block_hash) => {
+                    debug!(
+                        "Block hash found at height {}: {}",
+                        wanted_height, block_hash
+                    );
+                    // Block exists, get the full block info
+                    let client = self.client.clone().read_owned().await;
+                    let block_info =
+                        tokio::task::spawn_blocking(move || (*client).get_block_info(&block_hash))
+                            .await??;
+
+                    debug!(
+                        "Block info retrieved: hash={}, height={}, confirmations={}",
+                        block_info.hash, block_info.height, block_info.confirmations
+                    );
+
+                    // Check if the block has enough confirmations
+                    if block_info.confirmations >= 3 {
+                        debug!(
+                            "Block has sufficient confirmations ({}), returning",
+                            block_info.confirmations
+                        );
+                        return Ok(block_info);
+                    } else {
+                        // Not enough confirmations yet, wait and try again
+                        debug!(
+                            "Block at height {} has {} confirmations, waiting for at least 3...",
+                            wanted_height, block_info.confirmations
+                        );
+                        tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
+                    }
+                }
+                Err(e) => {
+                    // Block doesn't exist yet, wait and try again
+                    debug!(
+                        "Block at height {} not found yet, waiting... Error: {}",
+                        wanted_height, e
+                    );
+                    tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
+                }
+            }
+        }
+    }
+}

+ 23 - 0
crates/nockchain-bitcoin-sync/src/main.rs

@@ -0,0 +1,23 @@
+use std::env::args;
+use std::error::Error;
+
+use bitcoincore_rpc::Auth;
+use nockchain_bitcoin_sync::{BitcoinRPCConnection, BitcoinWatcher};
+
+#[tokio::main]
+async fn main() -> Result<(), Box<dyn Error>> {
+    let block: u64 = args().collect::<Vec<_>>()[1].parse()?;
+    let connection = BitcoinRPCConnection::new(
+        "http://127.0.0.1:8332".to_string(),
+        /*
+        Auth::CookieFile(PathBuf::from(
+            "cookiefile",
+        )),*/
+        Auth::None,
+        block,
+    );
+    let watcher = BitcoinWatcher::new(connection).await?;
+    let block_ref = watcher.watch().await?;
+    println!("Block {} at height {}", block_ref.hash, block_ref.height);
+    Ok(())
+}

+ 37 - 0
crates/nockchain-libp2p-io/Cargo.toml

@@ -0,0 +1,37 @@
+[package]
+name = "nockchain-libp2p-io"
+version.workspace = true
+edition.workspace = true
+
+[dependencies]
+crown = { workspace = true }
+sword = { workspace = true }
+sword_macros = { workspace = true }
+
+bytes = { workspace = true }
+bs58 = { workspace = true }
+either = { workspace = true }
+equix.workspace = true
+futures = { workspace = true }
+gnort = { workspace = true }
+hickory-resolver = { workspace = true }
+hickory-proto = { workspace = true }
+ibig = { workspace = true }
+libp2p = { workspace = true, features = [
+    "ping",
+    "kad",
+    "identify",
+    "quic",
+    "tls",
+    "dns",
+    "tokio",
+    "macros",
+    "request-response",
+    "memory-connection-limits",
+    "cbor",
+] }
+serde = { workspace = true, features = ["alloc", "derive", "serde_derive"] }
+serde_bytes = { workspace = true, features = ["alloc"] }
+tokio = { workspace = true, features = ["full"] }
+tracing = { workspace = true }
+void = { workspace = true }

+ 5 - 0
crates/nockchain-libp2p-io/src/lib.rs

@@ -0,0 +1,5 @@
+pub mod metrics;
+pub mod nc;
+pub mod p2p;
+pub mod p2p_util;
+pub mod tip5_util;

+ 25 - 0
crates/nockchain-libp2p-io/src/metrics.rs

@@ -0,0 +1,25 @@
+use gnort::*;
+
+metrics_struct![
+    NockchainP2PMetrics,
+    (gossip_acked, "nockchain-libp2p-io.gossip_acked", Count),
+    (gossip_nacked, "nockchain-libp2p-io.gossip_nacked", Count),
+    (gossip_erred, "nockchain-libp2p-io.gossip_erred", Count),
+    (gossip_dropped, "nockchain-libp2p-io.gossip_dropped", Count),
+    (requests_peeked_some, "nockchain-libp2p-io.requests_peeked_some", Count),
+    (requests_peeked_none, "nockchain-libp2p-io.requests_peeked_none", Count),
+    (requests_erred, "nockchain-libp2p-io.requests_erred", Count),
+    (requests_dropped, "nockchain-libp2p-io.requests_dropped", Count),
+    (responses_acked, "nockchain-libp2p-io.responses_acked", Count),
+    (responses_nacked, "nockchain-libp2p-io.responses_nacked", Count),
+    (responses_erred, "nockchain-libp2p-io.responses_erred", Count),
+    (responses_dropped, "nockchain-libp2p-io.responses_dropped", Count),
+    (block_request_cache_hits, "nockchain-libp2p-io.block_request_cache_hits", Count),
+    (tx_request_cache_hits, "nockchain-libp2p-io.tx_request_cache_hits", Count),
+    (block_seen_cache_hits, "nockchain-libp2p-io.block_seen_cache_hits", Count),
+    (tx_seen_cache_hits, "nockchain-libp2p-io.tx_seen_cache_hits", Count),
+    (block_request_cache_misses, "nockchain-libp2p-io.block_request_cache_misses", Count),
+    (tx_request_cache_misses, "nockchain-libp2p-io.tx_request_cache_misses", Count),
+    (block_seen_cache_misses, "nockchain-libp2p-io.block_seen_cache_misses", Count),
+    (tx_seen_cache_misses, "nockchain-libp2p-io.tx_seen_cache_misses", Count)
+];

+ 1995 - 0
crates/nockchain-libp2p-io/src/nc.rs

@@ -0,0 +1,1995 @@
+use std::collections::HashMap;
+use std::mem::size_of;
+use std::str::FromStr;
+use std::sync::Arc;
+
+use bytes::Bytes;
+use crown::nockapp::driver::{IODriverFn, NockAppHandle, PokeResult};
+use crown::nockapp::wire::{Wire, WireRepr};
+use crown::nockapp::NockAppError;
+use crown::noun::slab::NounSlab;
+use crown::utils::make_tas;
+use crown::utils::scry::*;
+use crown::{AtomExt, NounExt};
+use either::{Either, Left, Right};
+use futures::{Future, StreamExt};
+use libp2p::identify::Event::Received;
+use libp2p::request_response::Event::*;
+use libp2p::request_response::Message::*;
+use libp2p::request_response::{self};
+use libp2p::swarm::SwarmEvent;
+use libp2p::{PeerId, Swarm};
+use serde_bytes::ByteBuf;
+use sword::noun::{Atom, Noun, D, T};
+use sword_macros::tas;
+use tokio::sync::{mpsc, Mutex};
+use tokio::task::{AbortHandle, JoinError, JoinSet};
+use tracing::{debug, error, info, instrument, trace, warn};
+
+use crate::metrics::NockchainP2PMetrics;
+use crate::p2p::*;
+use crate::p2p_util::{MessageTracker, PeerIdExt};
+use crate::tip5_util::tip5_hash_to_base58;
+
+//TODO This wire is a placeholder for now. The libp2p driver is entangled with the other types of nockchain pokes
+//for historical reasons, and should be disentangled in the future.
+pub enum NockchainWire {
+    Local,
+}
+
+impl Wire for NockchainWire {
+    const VERSION: u64 = 1;
+    const SOURCE: &'static str = "nc";
+}
+
+pub enum Libp2pWire {
+    Gossip(PeerId),
+    Response(PeerId),
+}
+
+impl Libp2pWire {
+    fn verb(&self) -> &'static str {
+        match self {
+            Libp2pWire::Gossip(_) => "gossip",
+            Libp2pWire::Response(_) => "response",
+        }
+    }
+
+    fn peer_id(&self) -> &PeerId {
+        match self {
+            Libp2pWire::Gossip(peer_id) => peer_id,
+            Libp2pWire::Response(peer_id) => peer_id,
+        }
+    }
+}
+
+impl Wire for Libp2pWire {
+    const VERSION: u64 = 1;
+    const SOURCE: &'static str = "libp2p";
+
+    fn to_wire(&self) -> WireRepr {
+        let tags = vec![self.verb().into(), "peer-id".into(), self.peer_id().to_base58().into()];
+        WireRepr::new(Libp2pWire::SOURCE, Libp2pWire::VERSION, tags)
+    }
+}
+
+enum EffectType {
+    Gossip,
+    Request,
+    LiarPeer,
+    LiarBlockId,
+    Track,
+    Seen,
+    Unknown,
+}
+
+impl EffectType {
+    fn from_noun_slab(noun_slab: &NounSlab) -> Self {
+        let Ok(effect_cell) = (unsafe { noun_slab.root().as_cell() }) else {
+            return EffectType::Unknown;
+        };
+
+        let head = effect_cell.head();
+        let Ok(atom) = head.as_atom() else {
+            return EffectType::Unknown;
+        };
+        let bytes = atom
+            .to_bytes_until_nul()
+            .expect("failed to strip null bytes");
+
+        match bytes.as_slice() {
+            b"gossip" => EffectType::Gossip,
+            b"request" => EffectType::Request,
+            b"liar-peer" => EffectType::LiarPeer,
+            b"liar-block-id" => EffectType::LiarBlockId,
+            b"track" => EffectType::Track,
+            b"seen" => EffectType::Seen,
+            _ => EffectType::Unknown,
+        }
+    }
+}
+
+struct TrackedJoinSet<T> {
+    inner: JoinSet<T>,
+    tasks: HashMap<String, AbortHandle>,
+}
+
+impl<T: 'static> TrackedJoinSet<T> {
+    fn new() -> Self {
+        Self {
+            inner: JoinSet::new(),
+            tasks: HashMap::new(),
+        }
+    }
+
+    fn spawn(&mut self, name: String, task: impl Future<Output = T> + Send + 'static)
+    where
+        T: Send + 'static,
+    {
+        let handle = self.inner.spawn(task);
+        self.tasks.insert(name, handle);
+    }
+
+    async fn join_next(&mut self) -> Option<Result<T, JoinError>> {
+        let result = self.inner.join_next().await;
+        if result.is_some() {
+            // Remove the completed task from our tracking
+            self.tasks.retain(|_, v| !v.is_finished());
+        }
+        result
+    }
+
+    // Keep this around for debugging
+    #[allow(dead_code)]
+    fn get_running_tasks(&self) -> Vec<String> {
+        self.tasks.keys().cloned().collect()
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct MiningKeyConfig {
+    pub share: u64,
+    pub m: u64,
+    pub keys: Vec<String>,
+}
+
+impl FromStr for MiningKeyConfig {
+    type Err = String;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        // Expected format: "share,m:key1,key2,key3"
+        let parts: Vec<&str> = s.split(':').collect();
+        if parts.len() != 2 {
+            return Err("Invalid format. Expected 'share,m:key1,key2,key3'".to_string());
+        }
+
+        let share_m: Vec<&str> = parts[0].split(',').collect();
+        if share_m.len() != 2 {
+            return Err("Invalid share,m format".to_string());
+        }
+
+        let share = share_m[0].parse::<u64>().map_err(|e| e.to_string())?;
+        let m = share_m[1].parse::<u64>().map_err(|e| e.to_string())?;
+        let keys: Vec<String> = parts[1].split(',').map(String::from).collect();
+
+        Ok(MiningKeyConfig { share, m, keys })
+    }
+}
+
+#[instrument(skip(swarm, equix_builder))]
+pub fn make_libp2p_driver(
+    mut swarm: Swarm<NockchainBehaviour>,
+    equix_builder: equix::EquiXBuilder,
+    mining_config: Option<Vec<MiningKeyConfig>>,
+    init_complete_tx: Option<tokio::sync::oneshot::Sender<()>>,
+) -> IODriverFn {
+    Box::new(|mut handle| {
+        let metrics = NockchainP2PMetrics::register(gnort::global_metrics_registry())
+            .expect("Failed to register metrics!");
+
+        Box::pin(async move {
+            let (swarm_tx, mut swarm_rx) = mpsc::channel::<SwarmAction>(1000); // number needs to be high enough to send gossips to peers
+            let mut join_set = TrackedJoinSet::<Result<(), NockAppError>>::new();
+            let message_tracker = Arc::new(Mutex::new(MessageTracker::new()));
+
+            send_init_stark_config_poke(&handle).await?;
+
+            if let Some(configs) = mining_config {
+                if configs.len() == 1
+                    && configs[0].share == 1
+                    && configs[0].m == 1
+                    && configs[0].keys.len() == 1
+                {
+                    // Simple case - use set_mining_key
+                    set_mining_key(&handle, configs[0].keys[0].clone()).await?;
+                } else {
+                    // Advanced case - use set_mining_key_advanced
+                    set_mining_key_advanced(&handle, configs).await?;
+                }
+                enable_mining(&handle, true).await?;
+            } else {
+                enable_mining(&handle, false).await?;
+            }
+
+            if let Some(tx) = init_complete_tx {
+                let _ = tx.send(());
+                debug!("libp2p driver initialization complete signal sent");
+            }
+
+            loop {
+                tokio::select! {
+                    Ok(noun_slab) = handle.next_effect() => {
+                        let _span = tracing::trace_span!("broadcast").entered();
+                        let swarm_tx_clone = swarm_tx.clone();
+                        let equix_builder_clone = equix_builder.clone();
+                        let local_peer_id = *swarm.local_peer_id();
+                        let connected_peers: Vec<PeerId> = swarm.connected_peers().cloned().collect();
+                        let message_tracker_clone = Arc::clone(&message_tracker); // Clone the Arc, not the MessageTracker
+                        join_set.spawn("handle_effect".to_string(), async move {
+                            handle_effect(noun_slab, swarm_tx_clone, equix_builder_clone, local_peer_id, connected_peers, message_tracker_clone).await
+                        });
+                    },
+                    Some(event) = swarm.next() => {
+                        match event {
+                            SwarmEvent::NewListenAddr { address, .. } => {
+                                info!("SEvent: Listening on {address:?}");
+                            },
+                            SwarmEvent::Behaviour(NockchainEvent::Identify(Received { connection_id: _, peer_id, info })) => {
+                                trace!("SEvent: identify_received");
+                                identify_received(&mut swarm, peer_id, info)?;
+                            },
+                            SwarmEvent::ConnectionEstablished { peer_id, endpoint, .. } => {
+                                info!("SEvent: {peer_id} is new friend via: {endpoint:?}");
+                            },
+                            SwarmEvent::ConnectionClosed { peer_id, endpoint, cause, .. } => {
+                                info!("SEvent: friendship ended with {peer_id} via: {endpoint:?}. cause: {cause:?}");
+                                // Clean up the message tracker when a peer disconnects
+                                let mut tracker = message_tracker.lock().await;
+                                tracker.remove_peer(&peer_id);
+                            },
+                            SwarmEvent::IncomingConnectionError { local_addr, send_back_addr, error, .. } => {
+                               error!("SEvent: Failed incoming connection from {} to {}: {}",
+                               send_back_addr, local_addr, error);
+                            },
+                            SwarmEvent::Behaviour(NockchainEvent::RequestResponse(Message { connection_id: _, peer, message })) => {
+                                trace!("SEvent: received RequestResponse");
+                                let _span = tracing::debug_span!("SwarmEvent::Behavior(NockchainEvent::RequestResponse(…))").entered();
+                                let swarm_tx_clone = swarm_tx.clone();
+                                let mut equix_builder_clone = equix_builder.clone();
+                                let local_peer_id = *swarm.local_peer_id();
+                                // We have to dup and move a handle back into `handle` to propitiate the borrow checker
+                                let (orig_handle, request_response_handle) = handle.dup();
+                                handle = orig_handle;
+                                let metrics = metrics.clone();
+                                let message_tracker_clone = Arc::clone(&message_tracker); // Clone the Arc, not the MessageTracker
+                                join_set.spawn("handle_request_response".to_string(), async move {
+                                    handle_request_response(peer, message, swarm_tx_clone, &mut equix_builder_clone, local_peer_id, request_response_handle, metrics, message_tracker_clone).await
+                                });
+                            },
+                            SwarmEvent::OutgoingConnectionError { peer_id, error, .. } => {
+                                error!("Failed outgoing connection to {:?}: {}", peer_id, error);
+                            },
+                            SwarmEvent::IncomingConnection {
+                                local_addr,
+                                send_back_addr,
+                                connection_id,
+                                ..
+                            } => {
+                                debug!("SEvent: Incoming connection from {local_addr:?} to {send_back_addr:?} with {connection_id:?}");
+                            },
+                            SwarmEvent::Dialing { peer_id, connection_id } => {
+                                debug!("SEvent: Dialing {peer_id:?} {connection_id}");
+                            },
+                            _ => {
+                                // Handle other swarm events
+                                trace!("SEvent: other swarm event {:?}", event);
+                            }
+                        }
+                    },
+                    Some(swarm_action) = swarm_rx.recv() => {
+                        // We do this because Swarm doesn't implement Send, and so we can't pass it into the tasks
+                        // being spawned in the match cases above.
+                        match swarm_action {
+                            SwarmAction::SendRequest { peer_id, request } => {
+                                trace!("SAction: SendRequest: {peer_id}");
+                                let _ = swarm.behaviour_mut().request_response.send_request(&peer_id, request);
+                            },
+                            SwarmAction::SendResponse { channel, response } => {
+                                trace!("SAction: SendResponse");
+                                let _ = swarm.behaviour_mut().request_response.send_response(channel, response);
+                            },
+                            SwarmAction::BlockPeer { peer_id } => {
+                                warn!("SAction: Blocking peer {peer_id}");
+                                swarm.behaviour_mut().allow_block_list.block_peer(peer_id);
+                                // Disconnect the peer if they're currently connected
+                                let _ = swarm.disconnect_peer_id(peer_id);
+                            },
+                        }
+                    },
+                    Some(result) = join_set.join_next() => {
+                        if let Err(e) = result {
+                            error!("Task error: {:?}", e);
+                        }
+                    },
+                }
+            }
+        })
+    })
+}
+
+#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
+/// Network struct (in serde/CBOR) for requests
+pub enum NockchainRequest {
+    /// Request a block or TX from another node, carry PoW
+    Request {
+        pow: equix::SolutionByteArray,
+        nonce: u64,
+        message: ByteBuf,
+    },
+    /// Gossip a block or TX to another node
+    Gossip { message: ByteBuf },
+}
+
+impl NockchainRequest {
+    /// Make a new "request" which gossips a block or a TX
+    fn new_gossip(message: &NounSlab) -> NockchainRequest {
+        let message_bytes = ByteBuf::from(message.jam().as_ref());
+        NockchainRequest::Gossip {
+            message: message_bytes,
+        }
+    }
+
+    /// Make a new request for a block or a TX
+    fn new_request(
+        builder: &mut equix::EquiXBuilder,
+        local_peer_id: &libp2p::PeerId,
+        remote_peer_id: &libp2p::PeerId,
+        message: &NounSlab,
+    ) -> NockchainRequest {
+        let message_bytes = ByteBuf::from(message.jam().as_ref());
+        let local_peer_bytes = (*local_peer_id).to_bytes();
+        let remote_peer_bytes = (*remote_peer_id).to_bytes();
+        let mut pow_buf = Vec::with_capacity(
+            size_of::<u64>()
+                + local_peer_bytes.len()
+                + remote_peer_bytes.len()
+                + message_bytes.len(),
+        );
+        pow_buf.extend_from_slice(&[0; size_of::<u64>()][..]);
+        pow_buf.extend_from_slice(&local_peer_bytes[..]);
+        pow_buf.extend_from_slice(&remote_peer_bytes[..]);
+        pow_buf.extend_from_slice(&message_bytes[..]);
+
+        let mut nonce = 0u64;
+        let sol_bytes = loop {
+            {
+                let nonce_buf = &mut pow_buf[0..size_of::<u64>()];
+                nonce_buf.copy_from_slice(&nonce.to_le_bytes()[..]);
+            }
+            if let Ok(sols) = builder.solve(&pow_buf[..]) {
+                if !sols.is_empty() {
+                    break sols[0].to_bytes();
+                }
+            }
+            nonce += 1;
+        };
+
+        NockchainRequest::Request {
+            pow: sol_bytes,
+            nonce,
+            message: message_bytes,
+        }
+    }
+
+    /// Verify the EquiX PoW attached to a request
+    fn verify_pow(
+        &self,
+        builder: &mut equix::EquiXBuilder,
+        local_peer_id: &libp2p::PeerId,
+        remote_peer_id: &libp2p::PeerId,
+    ) -> Result<(), equix::Error> {
+        match self {
+            NockchainRequest::Request {
+                pow,
+                nonce,
+                message,
+            } => {
+                //  This looks backwards, but it's because which node is local and which is remote
+                //  is swapped between generation at the sender and verification at the receiver.
+                let local_peer_bytes = (*remote_peer_id).to_bytes();
+                let remote_peer_bytes = (*local_peer_id).to_bytes();
+                let nonce_bytes = nonce.to_le_bytes();
+                let mut pow_buf = Vec::with_capacity(
+                    size_of::<u64>()
+                        + local_peer_bytes.len()
+                        + remote_peer_bytes.len()
+                        + message.len(),
+                );
+                pow_buf.extend_from_slice(&nonce_bytes[..]);
+                pow_buf.extend_from_slice(&local_peer_bytes[..]);
+                pow_buf.extend_from_slice(&remote_peer_bytes[..]);
+                pow_buf.extend_from_slice(&message[..]);
+                builder.verify_bytes(&pow_buf[..], pow)
+            }
+            NockchainRequest::Gossip { message: _ } => Ok(()),
+        }
+    }
+}
+
+#[derive(Debug, serde::Serialize, serde::Deserialize)]
+/// Responses to Nockchain requests
+pub enum NockchainResponse {
+    /// The requested block or raw-tx
+    Result { message: ByteBuf },
+    /// If the request was a gossip, no actual response is needed
+    Ack,
+}
+
+impl NockchainResponse {
+    fn new_response_result(message: impl AsRef<[u8]>) -> NockchainResponse {
+        let message_bytes: &[u8] = message.as_ref();
+        let message_bytebuf = ByteBuf::from(message_bytes.to_vec());
+        NockchainResponse::Result {
+            message: message_bytebuf,
+        }
+    }
+}
+
+#[instrument(skip(handle, pubkey))]
+async fn set_mining_key(
+    handle: &NockAppHandle,
+    pubkey: String,
+) -> Result<PokeResult, NockAppError> {
+    let mut set_mining_key_slab = NounSlab::new();
+    let set_mining_key = Atom::from_value(&mut set_mining_key_slab, "set-mining-key")
+        .expect("Failed to create set-mining-key atom");
+    let pubkey_cord =
+        Atom::from_value(&mut set_mining_key_slab, pubkey).expect("Failed to create pubkey atom");
+    let set_mining_key_poke = T(
+        &mut set_mining_key_slab,
+        &[D(tas!(b"command")), set_mining_key.as_noun(), pubkey_cord.as_noun()],
+    );
+    set_mining_key_slab.set_root(set_mining_key_poke);
+
+    let wire = NockchainWire::Local;
+    handle.poke(wire.to_wire(), set_mining_key_slab).await
+}
+
+async fn set_mining_key_advanced(
+    handle: &NockAppHandle,
+    configs: Vec<MiningKeyConfig>,
+) -> Result<PokeResult, NockAppError> {
+    let mut set_mining_key_slab = NounSlab::new();
+    let set_mining_key_adv = Atom::from_value(&mut set_mining_key_slab, "set-mining-key-advanced")
+        .expect("Failed to create set-mining-key-advanced atom");
+
+    // Create the list of configs
+    let mut config_list = Vec::new();
+    for config in configs {
+        // Create the list of keys
+        let mut key_list = Vec::new();
+        for key in config.keys {
+            let key_atom =
+                Atom::from_value(&mut set_mining_key_slab, key).expect("Failed to create key atom");
+            key_list.push(key_atom.as_noun());
+        }
+        let keys_cell = T(&mut set_mining_key_slab, &key_list);
+
+        // Create the config tuple [share m keys]
+        let config_tuple = T(
+            &mut set_mining_key_slab,
+            &[D(config.share), D(config.m), keys_cell],
+        );
+        config_list.push(config_tuple);
+    }
+    let configs_cell = T(&mut set_mining_key_slab, &config_list);
+
+    let set_mining_key_poke = T(
+        &mut set_mining_key_slab,
+        &[D(tas!(b"command")), set_mining_key_adv.as_noun(), configs_cell],
+    );
+    set_mining_key_slab.set_root(set_mining_key_poke);
+
+    let wire = NockchainWire::Local;
+    handle.poke(wire.to_wire(), set_mining_key_slab).await
+}
+
+//TODO add %set-mining-key-multisig poke
+#[instrument(skip(handle))]
+async fn enable_mining(handle: &NockAppHandle, enable: bool) -> Result<PokeResult, NockAppError> {
+    let mut enable_mining_slab = NounSlab::new();
+    let enable_mining = Atom::from_value(&mut enable_mining_slab, "enable-mining")
+        .expect("Failed to create enable-mining atom");
+    let enable_mining_poke = T(
+        &mut enable_mining_slab,
+        &[D(tas!(b"command")), enable_mining.as_noun(), D(if enable { 0 } else { 1 })],
+    );
+    enable_mining_slab.set_root(enable_mining_poke);
+    let wire = NockchainWire::Local;
+    handle.poke(wire.to_wire(), enable_mining_slab).await
+}
+
+#[instrument(skip(handle))]
+async fn send_init_stark_config_poke(handle: &NockAppHandle) -> Result<PokeResult, NockAppError> {
+    let mut init_stark_config_slab = NounSlab::new();
+    let tag = make_tas(&mut init_stark_config_slab, "init-stark-config").as_noun();
+    let poke = T(
+        &mut init_stark_config_slab,
+        &[D(tas!(b"command")), tag, D(0)],
+    );
+    init_stark_config_slab.set_root(poke);
+    let wire = NockchainWire::Local;
+    handle.poke(wire.to_wire(), init_stark_config_slab).await
+}
+
+async fn handle_effect(
+    noun_slab: NounSlab,
+    swarm_tx: mpsc::Sender<SwarmAction>,
+    equix_builder: equix::EquiXBuilder,
+    local_peer_id: PeerId,
+    connected_peers: Vec<PeerId>,
+    message_tracker: Arc<Mutex<MessageTracker>>,
+) -> Result<(), NockAppError> {
+    match EffectType::from_noun_slab(&noun_slab) {
+        EffectType::Gossip => {
+            // remove the %gossip head
+            let mut tail_slab = NounSlab::new();
+            tail_slab.copy_into(unsafe { noun_slab.root().as_cell()?.tail() });
+
+            // Check if this is a heard-block gossip
+            let gossip_noun = unsafe { tail_slab.root() };
+            if let Ok(cell) = gossip_noun.as_cell() {
+                if cell.head().eq_bytes(b"heard-block") {
+                    trace!("Gossip effect for heard-block, clearing block cache");
+                    let mut tracker = message_tracker.lock().await;
+                    tracker.block_cache.clear();
+                }
+            }
+
+            let gossip_request = NockchainRequest::new_gossip(&tail_slab);
+            for peer_id in connected_peers.clone() {
+                let gossip_request_clone = gossip_request.clone();
+                swarm_tx
+                    .send(SwarmAction::SendRequest {
+                        peer_id,
+                        request: gossip_request_clone,
+                    })
+                    .await
+                    .map_err(|_e| NockAppError::OtherError)?;
+            }
+        }
+        EffectType::Request => {
+            // Extract request details to check if it's a peer-specific request
+            let request_cell = unsafe { noun_slab.root().as_cell()? };
+            let request_body = request_cell.tail().as_cell()?;
+            let request_type = request_body.head().as_direct()?;
+
+            let target_peers = if request_type.data() == tas!(b"block") {
+                let block_cell = request_body.tail().as_cell()?;
+                if block_cell.head().eq_bytes(b"elders") {
+                    // Extract peer ID from elders request
+                    let elders_cell = block_cell.tail().as_cell()?;
+                    let peer_id_atom = elders_cell.tail().as_atom()?;
+                    if let Ok(bytes) = peer_id_atom.to_bytes_until_nul() {
+                        if let Ok(peer_id) = PeerId::from_bytes(&bytes) {
+                            vec![peer_id]
+                        } else {
+                            connected_peers.clone()
+                        }
+                    } else {
+                        connected_peers.clone()
+                    }
+                } else {
+                    connected_peers.clone()
+                }
+            } else {
+                connected_peers.clone()
+            };
+
+            for peer_id in target_peers {
+                let local_peer_id_clone = local_peer_id;
+                let mut equix_builder_clone = equix_builder.clone();
+                let request = NockchainRequest::new_request(
+                    &mut equix_builder_clone, &local_peer_id_clone, &peer_id, &noun_slab,
+                );
+                swarm_tx
+                    .send(SwarmAction::SendRequest { peer_id, request })
+                    .await
+                    .map_err(|_e| NockAppError::OtherError)?;
+            }
+        }
+        EffectType::LiarPeer => {
+            let effect_cell = unsafe { noun_slab.root().as_cell()? };
+            let peer_id_atom = effect_cell.tail().as_atom().map_err(|_| {
+                NockAppError::IoError(std::io::Error::new(
+                    std::io::ErrorKind::Other,
+                    "Expected peer ID atom in liar-peer effect",
+                ))
+            })?;
+
+            let bytes = peer_id_atom
+                .to_bytes_until_nul()
+                .expect("failed to strip null bytes");
+            let peer_id_str = String::from_utf8(bytes).map_err(|_| {
+                NockAppError::IoError(std::io::Error::new(
+                    std::io::ErrorKind::Other,
+                    "Invalid UTF-8 in peer ID",
+                ))
+            })?;
+
+            let peer_id = PeerId::from_str(&peer_id_str).map_err(|_| {
+                NockAppError::IoError(std::io::Error::new(
+                    std::io::ErrorKind::Other,
+                    "Invalid peer ID format",
+                ))
+            })?;
+
+            swarm_tx
+                .send(SwarmAction::BlockPeer { peer_id })
+                .await
+                .map_err(|_| NockAppError::OtherError)?;
+        }
+        EffectType::LiarBlockId => {
+            let effect_cell = unsafe { noun_slab.root().as_cell()? };
+            let block_id = effect_cell.tail();
+
+            //add the bad block ID
+            let mut tracker = message_tracker.lock().await;
+            let peers_to_ban = tracker.process_bad_block_id(block_id)?;
+
+            // Ban each peer that sent this block
+            for peer_id in peers_to_ban {
+                swarm_tx
+                    .send(SwarmAction::BlockPeer { peer_id })
+                    .await
+                    .map_err(|_| NockAppError::OtherError)?;
+            }
+        }
+        EffectType::Track => {
+            let effect_cell = unsafe { noun_slab.root().as_cell()? };
+            let track_cell = effect_cell.tail().as_cell()?;
+            let action = track_cell.head();
+
+            if action.eq_bytes(b"add") {
+                // Handle [%track %add block-id peer-id]
+                let data_cell = track_cell.tail().as_cell()?;
+                let block_id = data_cell.head();
+                let peer_id_atom = data_cell.tail().as_atom()?;
+
+                // Convert peer_id from base58 string to PeerId
+                let Ok(peer_id) = PeerId::from_noun(peer_id_atom.as_noun()) else {
+                    return Err(NockAppError::OtherError);
+                };
+
+                // Add to message tracker
+                let mut tracker = message_tracker.lock().await;
+                tracker.track_block_id_and_peer(block_id, peer_id)?;
+            } else if action.eq_bytes(b"remove") {
+                // Handle [%track %remove block-id]
+                let block_id = track_cell.tail();
+
+                // Remove from message tracker
+                let mut tracker = message_tracker.lock().await;
+                tracker.remove_block_id(block_id)?;
+            } else {
+                return Err(NockAppError::IoError(std::io::Error::new(
+                    std::io::ErrorKind::Other,
+                    "Invalid track action",
+                )));
+            }
+        }
+        EffectType::Seen => {
+            let effect_cell = unsafe { noun_slab.root().as_cell()? };
+            let seen_cell = effect_cell.tail().as_cell()?;
+            let seen_type = seen_cell.head();
+
+            if seen_type.eq_bytes(b"block") {
+                let block_id = seen_cell.tail().as_cell()?;
+                let mut tracker = message_tracker.lock().await;
+                let block_id_str = tip5_hash_to_base58(block_id.as_noun())
+                    .expect("failed to convert block ID to base58");
+                debug!("seen block id: {:?}", &block_id_str);
+                tracker.seen_blocks.insert(block_id_str);
+            } else if seen_type.eq_bytes(b"tx") {
+                let tx_id = seen_cell.tail().as_cell()?;
+                let mut tracker = message_tracker.lock().await;
+                let tx_id_str = tip5_hash_to_base58(tx_id.as_noun())
+                    .expect("failed to convert tx ID to base58");
+                tracker.seen_txs.insert(tx_id_str);
+            }
+        }
+        EffectType::Unknown => {
+            //  This isn't unexpected - any effect that this driver doesn't handle
+            //  will hit this case.
+        }
+    }
+    Ok(())
+}
+
+// TODO: Wrap some of this up.
+#[allow(clippy::too_many_arguments)]
+async fn handle_request_response(
+    peer: PeerId,
+    message: request_response::Message<NockchainRequest, NockchainResponse>,
+    swarm_tx: mpsc::Sender<SwarmAction>,
+    equix_builder: &mut equix::EquiXBuilder,
+    local_peer_id: PeerId,
+    nockapp: NockAppHandle,
+    metrics: NockchainP2PMetrics,
+    message_tracker: Arc<Mutex<MessageTracker>>,
+) -> Result<(), NockAppError> {
+    trace!("handle_request_response peer: {peer}");
+    match message {
+        Request {
+            request, channel, ..
+        } => {
+            let Ok(()) = request.verify_pow(equix_builder, &local_peer_id, &peer) else {
+                warn!("bad libp2p powork from {peer}, blocking!");
+                swarm_tx
+                    .send(SwarmAction::BlockPeer { peer_id: peer })
+                    .await
+                    .map_err(|_| NockAppError::OtherError)?;
+                return Ok(());
+            };
+            trace!("handle_request_response: powork verified");
+            let mut request_slab = NounSlab::new();
+            match request {
+                NockchainRequest::Request {
+                    pow: _,
+                    nonce: _,
+                    message,
+                } => {
+                    trace!("handle_request_response: Request received");
+                    let message_bytes = Bytes::from(message.to_vec());
+                    let request_noun = request_slab.cue_into(message_bytes)?;
+                    let (scry_res_slab, cache_hit) = if let Ok(Some(cache_result)) = {
+                        let mut tracker = message_tracker.lock().await;
+                        tracker.check_cache(&request_noun, &metrics).await
+                    } {
+                        debug!("found cached response for request");
+                        (cache_result, true)
+                    } else {
+                        let scry_slab = request_to_scry_slab(&request_noun)?;
+                        let Some(scry_res_slab) = (match nockapp.try_peek(scry_slab).await {
+                            Ok(Some(res_slab)) => {
+                                metrics.requests_peeked_some.increment();
+                                Some(res_slab)
+                            }
+                            Ok(None) => {
+                                metrics.requests_peeked_none.increment();
+                                trace!(
+                                "No data found for incoming request from: {}, request type: {:?}",
+                                peer,
+                                request_noun.as_cell()?.tail().as_cell().map(|c| c.head())
+                            );
+                                None
+                            }
+                            Err(NockAppError::MPSCFullError(act)) => {
+                                metrics.requests_dropped.increment();
+                                trace!(
+                                    "handle_request_response: Request dropped due to backpressure"
+                                );
+                                Err(NockAppError::MPSCFullError(act))?
+                            }
+                            Err(err) => {
+                                metrics.requests_erred.increment();
+                                trace!("handle_request_response: Error getting response");
+                                Err(err)?
+                            }
+                        }) else {
+                            return Ok(());
+                        };
+                        (scry_res_slab, false)
+                    };
+
+                    let Ok(request_cell) = request_noun.as_cell() else {
+                        error!("request noun not a cell");
+                        return Err(NockAppError::OtherError);
+                    };
+                    let Ok(scry_tag) = request_cell.tail().as_cell()?.head().as_direct() else {
+                        error!("request tag axis not an atom");
+                        return Err(NockAppError::OtherError);
+                    };
+                    trace!("handle_request_response: request cell parsed");
+                    let mut res_slab = NounSlab::new();
+                    let response = match scry_tag.data() {
+                        tas!(b"block") => {
+                            trace!("handle_request_response: block tag");
+                            let scry_res = unsafe { scry_res_slab.root() };
+
+                            // Extract the request type from under %block
+                            let request_type = request_cell
+                                .tail()
+                                .as_cell()
+                                .and_then(|c| c.tail().as_cell())
+                                .map(|c| c.head().eq_bytes(b"elders"))
+                                .map_err(|_| {
+                                    NockAppError::IoError(std::io::Error::new(
+                                        std::io::ErrorKind::Other,
+                                        "invalid block request structure",
+                                    ))
+                                })?;
+
+                            // Use heard-elders for elders requests, heard-block otherwise
+                            let heard_type = if request_type {
+                                "heard-elders"
+                            } else {
+                                "heard-block"
+                            };
+
+                            match create_scry_response(scry_res, heard_type, &mut res_slab) {
+                                Left(()) => {
+                                    trace!(
+                                        "No data found for incoming block request, type: {}",
+                                        heard_type
+                                    );
+                                    return Ok(());
+                                }
+                                Right(result) => {
+                                    // cache response
+                                    if !cache_hit && heard_type == "heard-block" {
+                                        let height = request_cell
+                                            .tail()
+                                            .as_cell()?
+                                            .tail()
+                                            .as_cell()?
+                                            .tail()
+                                            .as_direct()?
+                                            .data();
+                                        let mut tracker = message_tracker.lock().await;
+                                        tracker.block_cache.insert(height, scry_res_slab.clone());
+                                        debug!("cacheing block request by height={:?}", height);
+                                    }
+                                    result?
+                                }
+                            }
+                        }
+                        tas!(b"raw-tx") => {
+                            trace!("handle_request_response: raw-tx tag");
+                            let scry_res = unsafe { scry_res_slab.root() };
+                            match create_scry_response(scry_res, "heard-tx", &mut res_slab) {
+                                Left(()) => {
+                                    trace!("No data found for incoming raw-tx request");
+                                    return Ok(());
+                                }
+                                Right(result) => {
+                                    if !cache_hit {
+                                        let tx_id = request_cell.tail().as_cell()?.tail();
+                                        let tx_id_str = tip5_hash_to_base58(tx_id)?;
+                                        let mut tracker = message_tracker.lock().await;
+                                        debug!("cacheing tx request by id={:?}", tx_id_str);
+                                        tracker.tx_cache.insert(tx_id_str, scry_res_slab.clone());
+                                    }
+                                    result?
+                                }
+                            }
+                        }
+                        tag => {
+                            error!("Unknown request tag: {:?}", tag);
+                            return Err(NockAppError::OtherError);
+                        }
+                    };
+                    swarm_tx
+                        .send(SwarmAction::SendResponse { channel, response })
+                        .await
+                        .map_err(|_| NockAppError::OtherError)?;
+                }
+                NockchainRequest::Gossip { message } => {
+                    trace!("handle_request_response: Gossip received");
+                    let message_bytes = Bytes::from(message.to_vec());
+                    let request_noun = request_slab.cue_into(message_bytes)?;
+                    trace!("handle_request_response: Gossip noun parsed");
+
+                    let head = request_noun.as_cell()?.head();
+                    if head.eq_bytes(b"heard-block") {
+                        let page = request_noun.as_cell()?.tail();
+                        let block_id = page.as_cell()?.head();
+                        let block_id_str = tip5_hash_to_base58(block_id)?;
+                        let tracker = message_tracker.lock().await;
+                        if tracker.seen_blocks.contains(&block_id_str) {
+                            debug!("Block already seen, not processing: {:?}", block_id_str);
+                            metrics.block_seen_cache_hits.increment();
+                            return Ok(());
+                        } else {
+                            debug!("block not seen, processing: {:?}", block_id_str);
+                            metrics.block_seen_cache_misses.increment();
+                        }
+                    }
+
+                    if head.eq_bytes(b"heard-tx") {
+                        let raw_tx = request_noun.as_cell()?.tail();
+                        let tx_id = raw_tx.as_cell()?.head();
+                        let tracker = message_tracker.lock().await;
+                        let tx_id_str = tip5_hash_to_base58(tx_id)?;
+                        if tracker.seen_txs.contains(&tx_id_str) {
+                            debug!("Tx already seen, not processing: {:?}", tx_id_str);
+                            metrics.tx_seen_cache_hits.increment();
+                            return Ok(());
+                        } else {
+                            debug!("tx not seen, processing: {:?}", tx_id_str);
+                            metrics.tx_seen_cache_misses.increment();
+                        }
+                    }
+
+                    let request_fact = prepend_tas(&mut request_slab, "fact", request_noun)?;
+                    request_slab.set_root(request_fact);
+                    let wire = Libp2pWire::Gossip(peer);
+
+                    match nockapp.try_poke(wire.to_wire(), request_slab).await {
+                        Ok(PokeResult::Ack) => {
+                            metrics.gossip_acked.increment();
+                        }
+                        Ok(PokeResult::Nack) => {
+                            metrics.gossip_nacked.increment();
+                            trace!("handle_request_response: gossip poke nacked");
+                            return Ok(());
+                        }
+                        Err(NockAppError::MPSCFullError(act)) => {
+                            metrics.gossip_dropped.increment();
+                            trace!(
+                                "handle_request_response: gossip poke dropped due to backpressure"
+                            );
+                            return Err(NockAppError::MPSCFullError(act));
+                        }
+                        Err(err) => {
+                            metrics.gossip_erred.increment();
+                            trace!("handle_request_response: Poke errored");
+                            return Err(err);
+                        }
+                    };
+                    trace!("handle_request_response: Poke successful");
+                    let response = NockchainResponse::Ack;
+                    swarm_tx
+                        .send(SwarmAction::SendResponse { channel, response })
+                        .await
+                        .map_err(|_| NockAppError::OtherError)?;
+                }
+            }
+        }
+        Response { response, .. } => match response {
+            NockchainResponse::Result { message } => {
+                trace!("handle_request_response: Response result received");
+                let mut response_slab = NounSlab::new();
+                let message_bytes = Bytes::from(message.to_vec());
+                let response_noun = response_slab.cue_into(message_bytes)?;
+                trace!("Received response from peer");
+
+                let response_fact = prepend_tas(&mut response_slab, "fact", response_noun)?;
+                response_slab.set_root(response_fact);
+                let wire = Libp2pWire::Response(peer);
+
+                match nockapp.try_poke(wire.to_wire(), response_slab).await {
+                    Ok(PokeResult::Ack) => {
+                        metrics.responses_acked.increment();
+                    }
+                    Ok(PokeResult::Nack) => {
+                        metrics.responses_nacked.increment();
+                        trace!("handle_request_response: Poke failed");
+                        return Ok(());
+                    }
+                    Err(NockAppError::MPSCFullError(act)) => {
+                        trace!("handle_request_response: Response dropped due to backpressure.");
+                        metrics.responses_dropped.increment();
+                        return Err(NockAppError::MPSCFullError(act));
+                    }
+                    Err(_) => {
+                        trace!("handle_request_response: Error poking with response");
+                        metrics.responses_erred.increment();
+                        trace!("Error sending poke")
+                    }
+                }
+                trace!("handle_request_response: Poke successful");
+            }
+            NockchainResponse::Ack => {
+                trace!("Received acknowledgement from peer {}", peer);
+            }
+        },
+    }
+    Ok(())
+}
+
+/// Converts a request noun into a scry path that can be used to query the Nockchain state.
+///
+/// The request noun is expected to be in the format:
+/// `[%request [type data]]` where:
+/// - `type` can be either "block" or "raw-tx"
+/// - For "block" type:
+///   - `data` can be either `[%by-height height]` or `[%elders block-id peer-id]`
+/// - For "raw-tx" type:
+///   - `data` must be `[%by-id id]`
+///
+/// # Arguments
+/// * `noun` - The request noun to convert
+///
+/// # Returns
+/// * `Ok(NounSlab)` - A noun slab containing the constructed scry path
+/// * `Err(NockAppError)` - If the request format is invalid or unknown
+///
+/// # Examples
+/// For a block by height request:
+/// [%request [%block [%by-height 123]]] -> [%heavy-n 123 0]
+/// For a block by id request:
+/// [%request [%block [%elders [1 2 3 4 5] abcDEF]]] -> [%elders base58-block-id peer-id 0]
+/// For a raw transaction request:
+/// [%request [%raw-tx [%by-id [1 2 3 4 5]]]] -> [%raw-transaction base58-tx-id 0]
+fn request_to_scry_slab(noun: &Noun) -> Result<NounSlab, NockAppError> {
+    let Ok(request) = noun.as_cell() else {
+        return Err(NockAppError::IoError(std::io::Error::new(
+            std::io::ErrorKind::Other,
+            "Unknown request - not a cell",
+        )));
+    };
+
+    let Ok(tag) = request.head().as_direct() else {
+        return Err(NockAppError::IoError(std::io::Error::new(
+            std::io::ErrorKind::Other,
+            "Unknown request - not a direct",
+        )));
+    };
+
+    if tag.data() != tas!(b"request") {
+        return Err(NockAppError::IoError(std::io::Error::new(
+            std::io::ErrorKind::Other,
+            "Unknown request - not a request",
+        )));
+    }
+
+    let mut scry_path_slab = NounSlab::new();
+    let request_body = request.tail().as_cell()?;
+
+    if request_body.head().eq_bytes(b"block") {
+        let Ok(tail_cell) = request_body.tail().as_cell() else {
+            return Err(NockAppError::IoError(std::io::Error::new(
+                std::io::ErrorKind::Other,
+                "Invalid block request",
+            )));
+        };
+
+        if tail_cell.head().eq_bytes(b"by-height") && tail_cell.tail().is_atom() {
+            let pax = T(
+                &mut scry_path_slab,
+                &[D(tas!(b"heavy-n")), tail_cell.tail(), D(0)],
+            );
+            scry_path_slab.set_root(pax);
+            info!(
+                "block by-height: {:?}",
+                sword::noun::DebugPath(&pax.as_cell()?)
+            );
+            return Ok(scry_path_slab);
+        } else if tail_cell.head().eq_bytes(b"elders") {
+            let Ok(elders_cell) = tail_cell.tail().as_cell() else {
+                return Err(NockAppError::IoError(std::io::Error::new(
+                    std::io::ErrorKind::Other,
+                    "Invalid elders request",
+                )));
+            };
+
+            let block_id_b58 = tip5_hash_to_base58(elders_cell.head())?;
+            let block_id_atom =
+                Atom::from_value(&mut scry_path_slab, block_id_b58).unwrap_or_else(|_| {
+                    panic!(
+                        "Called `expect()` at {}:{} (git sha: {})",
+                        file!(),
+                        line!(),
+                        option_env!("GIT_SHA").unwrap_or("unknown")
+                    )
+                });
+            let peer_id = elders_cell.tail();
+
+            let pax = T(
+                &mut scry_path_slab,
+                &[D(tas!(b"elders")), block_id_atom.as_noun(), peer_id, D(0)],
+            );
+            info!(
+                "block elders: {:?}",
+                sword::noun::DebugPath(&pax.as_cell()?)
+            );
+            scry_path_slab.set_root(pax);
+            return Ok(scry_path_slab);
+        }
+    } else if request_body.head().eq_bytes(b"raw-tx") {
+        let Ok(tail_cell) = request_body.tail().as_cell() else {
+            return Err(NockAppError::IoError(std::io::Error::new(
+                std::io::ErrorKind::Other,
+                "Invalid raw-tx request",
+            )));
+        };
+
+        if tail_cell.head().eq_bytes(b"by-id") {
+            let tx_id_b58 = tip5_hash_to_base58(tail_cell.tail())?;
+            let tx_id_atom =
+                Atom::from_value(&mut scry_path_slab, tx_id_b58).unwrap_or_else(|_| {
+                    panic!(
+                        "Called `expect()` at {}:{} (git sha: {})",
+                        file!(),
+                        line!(),
+                        option_env!("GIT_SHA").unwrap_or("unknown")
+                    )
+                });
+            let raw_tx_tag = make_tas(&mut scry_path_slab, "raw-transaction").as_noun();
+            let pax = T(
+                &mut scry_path_slab,
+                &[raw_tx_tag, tx_id_atom.as_noun(), D(0)],
+            );
+            info!("tx by-id: {:?}", sword::noun::DebugPath(&pax.as_cell()?));
+            scry_path_slab.set_root(pax);
+            return Ok(scry_path_slab);
+        }
+    }
+
+    // log the head of the request body
+    Err(NockAppError::IoError(std::io::Error::new(
+        std::io::ErrorKind::Other,
+        format!("Unknown request - {:?}", request_body.head()),
+    )))
+}
+
+/// Creates a response to a scry request by processing the scry result noun
+///
+/// # Arguments
+/// * `scry_res` - The noun containing the scry result to process
+/// * `heard_type` - The type of request that was heard (as a string)
+/// * `res_slab` - Mutable reference to the noun slab for storing the response
+///
+/// # Returns
+/// Either:
+/// - `Left(())` if the scry path was bad or nothing was found
+/// - `Right(Ok(NockchainResponse))` containing the successful response
+/// - `Right(Err(NockAppError))` if there was an error processing the result
+fn create_scry_response(
+    scry_res: &Noun,
+    heard_type: &str,
+    res_slab: &mut NounSlab,
+) -> Either<(), Result<NockchainResponse, NockAppError>> {
+    match ScryResult::from(scry_res) {
+        ScryResult::BadPath => {
+            warn!("Bad scry path");
+            Left(())
+        }
+        ScryResult::Nothing => {
+            trace!("Nothing found at scry path");
+            Left(())
+        }
+        ScryResult::Some(x) => {
+            if let Ok(response_noun) = prepend_tas(res_slab, heard_type, x) {
+                res_slab.set_root(response_noun);
+                Right(Ok(NockchainResponse::new_response_result(res_slab.jam())))
+            } else {
+                error!("Failed to prepend tas to response noun");
+                Right(Err(NockAppError::OtherError))
+            }
+        }
+        ScryResult::Invalid => {
+            error!("Invalid scry result");
+            Right(Err(NockAppError::OtherError))
+        }
+    }
+}
+
+/// Prepends a @tas to a Noun.
+///
+/// # Arguments
+/// * `slab` - The NounSlab containing the noun
+/// * `noun` - The Noun to prepend @tas to
+///
+/// # Returns
+/// The noun with @tas prepended
+fn prepend_tas(slab: &mut NounSlab, tas_str: &str, noun: Noun) -> Result<Noun, NockAppError> {
+    let tas_atom = Atom::from_value(slab, tas_str)?;
+    Ok(T(slab, &[tas_atom.as_noun(), noun]))
+}
+
+#[cfg(test)]
+mod tests {
+    use crown::noun::slab::NounSlab;
+    use sword::noun::{D, T};
+    use sword_macros::tas;
+
+    use super::*;
+
+    #[test]
+    #[cfg_attr(miri, ignore)] // ibig has a memory leak so miri fails this test
+    fn test_request_to_scry_slab() {
+        // Test block by-height request
+        {
+            let mut slab = NounSlab::new();
+            let height = 123u64;
+            let by_height_tas = make_tas(&mut slab, "by-height");
+            let by_height = T(&mut slab, &[by_height_tas.as_noun(), D(height)]);
+            let block_cell = T(&mut slab, &[D(tas!(b"block")), by_height]);
+            let request = T(&mut slab, &[D(tas!(b"request")), block_cell]);
+            slab.set_root(request);
+
+            let result_slab = request_to_scry_slab(unsafe { slab.root() }).unwrap_or_else(|_| {
+                panic!(
+                    "Called `expect()` at {}:{} (git sha: {})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA").unwrap_or("unknown")
+                )
+            });
+            let result = unsafe { result_slab.root() };
+
+            assert!(result.is_cell());
+            let result_cell = result.as_cell().unwrap_or_else(|_| {
+                panic!(
+                    "Called `expect()` at {}:{} (git sha: {})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA").unwrap_or("unknown")
+                )
+            });
+            assert!(result_cell.head().eq_bytes(b"heavy-n"));
+
+            // Get the tail cell and check its components
+            let tail_cell = result_cell.tail().as_cell().unwrap_or_else(|_| {
+                panic!(
+                    "Called `expect()` at {}:{} (git sha: {})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA").unwrap_or("unknown")
+                )
+            });
+            let height_atom = tail_cell.head().as_atom().unwrap_or_else(|_| {
+                panic!(
+                    "Called `expect()` at {}:{} (git sha: {})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA").unwrap_or("unknown")
+                )
+            });
+            assert_eq!(
+                height_atom.as_u64().unwrap_or_else(|err| {
+                    panic!(
+                        "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                        file!(),
+                        line!(),
+                        option_env!("GIT_SHA")
+                    )
+                }),
+                height
+            );
+            let tail_atom = tail_cell.tail().as_atom().unwrap_or_else(|_| {
+                panic!(
+                    "Called `expect()` at {}:{} (git sha: {})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA").unwrap_or("unknown")
+                )
+            });
+            assert_eq!(
+                tail_atom.as_u64().unwrap_or_else(|err| {
+                    panic!(
+                        "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                        file!(),
+                        line!(),
+                        option_env!("GIT_SHA")
+                    )
+                }),
+                0
+            );
+        }
+
+        // Test invalid request (not a cell)
+        {
+            let mut slab = NounSlab::new();
+            slab.set_root(D(123));
+            let result = request_to_scry_slab(unsafe { slab.root() });
+            assert!(result.is_err());
+        }
+
+        // Test elders request
+        {
+            let mut slab = NounSlab::new();
+            // Create a 5-tuple [1 2 3 4 5] for the block ID
+            let five_tuple = T(&mut slab, &[D(1), D(2), D(3), D(4), D(5)]);
+
+            // Create a random peer ID and store its bytes
+            let peer_id = PeerId::random();
+            let peer_id_atom =
+                Atom::from_value(&mut slab, peer_id.to_base58()).unwrap_or_else(|_| {
+                    panic!(
+                        "Called `expect()` at {}:{} (git sha: {})",
+                        file!(),
+                        line!(),
+                        option_env!("GIT_SHA").unwrap_or("unknown")
+                    )
+                });
+
+            let elders_cell = T(&mut slab, &[five_tuple, peer_id_atom.as_noun()]);
+            let elders_tas = D(tas!(b"elders"));
+            let inner_cell = T(&mut slab, &[elders_tas, elders_cell]);
+            let block_cell = T(&mut slab, &[D(tas!(b"block")), inner_cell]);
+            let request = T(&mut slab, &[D(tas!(b"request")), block_cell]);
+            slab.set_root(request);
+
+            let result_slab = request_to_scry_slab(unsafe { slab.root() }).unwrap_or_else(|_| {
+                panic!(
+                    "Called `expect()` at {}:{} (git sha: {})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA").unwrap_or("unknown")
+                )
+            });
+            let result = unsafe { result_slab.root() };
+
+            // Verify the structure: [%elders block_id_b58 peer_id 0]
+            assert!(result.is_cell());
+            let result_cell = result.as_cell().unwrap_or_else(|_| {
+                panic!(
+                    "Called `expect()` at {}:{} (git sha: {})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA").unwrap_or("unknown")
+                )
+            });
+
+            // Check %elders tag
+            assert!(result_cell.head().eq_bytes(b"elders"));
+
+            // Get the tail cell
+            let tail_cell = result_cell.tail().as_cell().unwrap_or_else(|_| {
+                panic!(
+                    "Called `expect()` at {}:{} (git sha: {})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA").unwrap_or("unknown")
+                )
+            });
+
+            // Check block ID (should be base58 encoded)
+            let block_id_atom = tail_cell.head().as_atom().unwrap_or_else(|_| {
+                panic!(
+                    "Called `expect()` at {}:{} (git sha: {})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA").unwrap_or("unknown")
+                )
+            });
+            let block_id_bytes = block_id_atom.to_bytes_until_nul().unwrap_or_else(|_| {
+                panic!(
+                    "Called `expect()` at {}:{} (git sha: {})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA").unwrap_or("unknown")
+                )
+            });
+            let block_id_str = String::from_utf8(block_id_bytes).unwrap_or_else(|_| {
+                panic!(
+                    "Called `expect()` at {}:{} (git sha: {})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA").unwrap_or("unknown")
+                )
+            });
+
+            // Get the expected base58 string
+            let expected_b58 = tip5_hash_to_base58(five_tuple).unwrap_or_else(|_| {
+                panic!(
+                    "Called `expect()` at {}:{} (git sha: {})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA").unwrap_or("unknown")
+                )
+            });
+            assert_eq!(block_id_str, expected_b58);
+
+            // Check peer ID
+            let peer_cell = tail_cell.tail().as_cell().unwrap_or_else(|_| {
+                panic!(
+                    "Called `expect()` at {}:{} (git sha: {})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA").unwrap_or("unknown")
+                )
+            });
+            let peer_id_result = peer_cell.head().as_atom().unwrap_or_else(|_| {
+                panic!(
+                    "Called `expect()` at {}:{} (git sha: {})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA").unwrap_or("unknown")
+                )
+            });
+            let peer_bytes = peer_id_result.to_bytes_until_nul().unwrap_or_else(|_| {
+                panic!(
+                    "Called `expect()` at {}:{} (git sha: {})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA").unwrap_or("unknown")
+                )
+            });
+            let peer_str = String::from_utf8(peer_bytes).unwrap_or_else(|_| {
+                panic!(
+                    "Called `expect()` at {}:{} (git sha: {})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA").unwrap_or("unknown")
+                )
+            });
+            assert_eq!(peer_str, peer_id.to_base58());
+
+            // Check final 0
+            assert_eq!(
+                peer_cell
+                    .tail()
+                    .as_direct()
+                    .unwrap_or_else(|err| {
+                        panic!(
+                            "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                            file!(),
+                            line!(),
+                            option_env!("GIT_SHA")
+                        )
+                    })
+                    .data(),
+                0
+            );
+        }
+
+        // Test invalid elders request (not a cell)
+        {
+            let mut slab = NounSlab::new();
+            let invalid_request = T(
+                &mut slab,
+                &[D(tas!(b"request")), D(tas!(b"block")), D(tas!(b"elders"))],
+            );
+            slab.set_root(invalid_request);
+
+            let result = request_to_scry_slab(unsafe { slab.root() });
+            assert!(result.is_err());
+        }
+    }
+
+    #[test]
+    #[cfg_attr(miri, ignore)] // equix uses a foreign function so miri fails this tes
+    fn test_equix_pow_verification() {
+        use equix::EquiXBuilder;
+        // Create EquiX builder - new() doesn't return Result
+        let mut builder = EquiXBuilder::new();
+
+        // Create test peer IDs
+        let local_peer_id = PeerId::random();
+        let remote_peer_id = PeerId::random();
+
+        // Create test message
+        let message = ByteBuf::from(vec![1, 2, 3, 4, 5]);
+
+        // Create valid request with correct PoW
+        let valid_request =
+            NockchainRequest::new_request(&mut builder, &local_peer_id, &remote_peer_id, &{
+                let mut slab = NounSlab::new();
+                let message_noun = Atom::from_value(&mut slab, &message[..])
+                    .expect("Failed to create message atom")
+                    .as_noun();
+                slab.set_root(message_noun);
+                slab
+            });
+
+        // Verify the valid request
+        match &valid_request {
+            NockchainRequest::Request {
+                pow,
+                nonce,
+                message: _,
+            } => {
+                // Test successful verification
+                let result = valid_request.verify_pow(
+                    &mut builder, &remote_peer_id, // Note: peers are swapped for verification
+                    &local_peer_id,
+                );
+                assert!(result.is_ok(), "Valid PoW should verify successfully");
+
+                // Test failed verification with tampered nonce
+                let tampered_request = NockchainRequest::Request {
+                    pow: *pow,
+                    nonce: nonce + 1, // Tamper with the nonce
+                    message: message.clone(),
+                };
+                let result =
+                    tampered_request.verify_pow(&mut builder, &remote_peer_id, &local_peer_id);
+                assert!(result.is_err(), "Tampered nonce should fail verification");
+
+                // Test failed verification with wrong peer order
+                let result = valid_request.verify_pow(
+                    &mut builder, &local_peer_id, // Wrong order - not swapped
+                    &remote_peer_id,
+                );
+                assert!(result.is_err(), "Wrong peer order should fail verification");
+            }
+            _ => panic!("Expected Request variant"),
+        }
+
+        // Test that gossip requests always verify successfully
+        let gossip_request = NockchainRequest::Gossip {
+            message: message.clone(),
+        };
+        let result = gossip_request.verify_pow(&mut builder, &remote_peer_id, &local_peer_id);
+        assert!(
+            result.is_ok(),
+            "Gossip requests should always verify successfully"
+        );
+    }
+
+    #[tokio::test]
+    #[cfg_attr(miri, ignore)]
+    async fn test_liar_peer_effect() {
+        use equix::EquiXBuilder;
+        use tokio::sync::mpsc;
+
+        // Create a test peer ID and convert to base58
+        let peer_id = PeerId::random();
+        let peer_id_base58 = peer_id.to_base58();
+
+        // Create the liar-peer effect noun
+        let mut effect_slab = NounSlab::new();
+        let liar_peer_atom = Atom::from_value(&mut effect_slab, "liar-peer")
+            .expect("Failed to create liar-peer atom");
+        let peer_id_atom = Atom::from_value(&mut effect_slab, peer_id_base58)
+            .expect("Failed to create peer ID atom");
+        let effect = T(
+            &mut effect_slab,
+            &[liar_peer_atom.as_noun(), peer_id_atom.as_noun()],
+        );
+        effect_slab.set_root(effect);
+
+        // Create channel to receive SwarmAction
+        let (swarm_tx, mut swarm_rx) = mpsc::channel(1);
+
+        // Call handle_effect with the liar-peer effect
+        let result = handle_effect(
+            effect_slab,
+            swarm_tx,
+            EquiXBuilder::new(),
+            PeerId::random(), // local peer ID (not relevant for this test)
+            vec![],           // connected peers (not relevant for this test)
+            Arc::new(Mutex::new(MessageTracker::new())),
+        )
+        .await;
+
+        // Verify the function succeeded
+        assert!(result.is_ok(), "handle_effect should succeed");
+
+        // Verify that a BlockPeer action was sent with the correct peer ID
+        match swarm_rx.recv().await {
+            Some(SwarmAction::BlockPeer {
+                peer_id: blocked_peer,
+            }) => {
+                assert_eq!(blocked_peer, peer_id, "Wrong peer ID was blocked");
+            }
+            other => panic!("Expected BlockPeer action, got {:?}", other),
+        }
+
+        // Verify no more actions were sent
+        assert!(swarm_rx.try_recv().is_err(), "Should only send one action");
+    }
+
+    #[tokio::test]
+    #[cfg_attr(miri, ignore)] // ibig has a memory leak so miri fails this test
+    async fn test_track_add_effect() {
+        use equix::EquiXBuilder;
+        use tokio::sync::mpsc;
+
+        // Create test peer ID
+        let peer_id = PeerId::random();
+        let peer_id_base58 = peer_id.to_base58();
+
+        // Create the track add effect noun
+        let mut effect_slab = NounSlab::new();
+        let track_atom = make_tas(&mut effect_slab, "track");
+        let add_atom = make_tas(&mut effect_slab, "add");
+
+        // Create block ID as [1 2 3 4 5]
+        let block_id_tuple = T(&mut effect_slab, &[D(1), D(2), D(3), D(4), D(5)]);
+        let peer_id_atom = Atom::from_value(&mut effect_slab, peer_id_base58)
+            .expect("Failed to create peer ID atom");
+
+        // Build the noun structure: [%track %add block-id peer-id]
+        let data_cell = T(&mut effect_slab, &[block_id_tuple, peer_id_atom.as_noun()]);
+        let add_cell = T(&mut effect_slab, &[add_atom.as_noun(), data_cell]);
+        let track_cell = T(&mut effect_slab, &[track_atom.as_noun(), add_cell]);
+        effect_slab.set_root(track_cell);
+
+        // Create message tracker and other required components
+        let message_tracker = Arc::new(Mutex::new(MessageTracker::new()));
+        let (swarm_tx, _swarm_rx) = mpsc::channel(1);
+
+        // Call handle_effect with the track add effect
+        let result = handle_effect(
+            effect_slab.clone(), // test fails if we don't clone
+            swarm_tx,
+            EquiXBuilder::new(),
+            PeerId::random(), // local peer ID (not relevant for this test)
+            vec![],           // connected peers (not relevant for this test)
+            message_tracker.clone(),
+        )
+        .await;
+
+        // Verify the function succeeded
+        assert!(result.is_ok(), "handle_effect should succeed");
+
+        // Get the expected block ID string (base58 of [1 2 3 4 5])
+        let block_id_str = tip5_hash_to_base58(block_id_tuple).unwrap_or_else(|_| {
+            panic!(
+                "Called `expect()` at {}:{} (git sha: {})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA").unwrap_or("unknown")
+            )
+        });
+
+        // Verify the message tracker state
+        let tracker = message_tracker.lock().await;
+
+        // Check block_id_to_peers mapping
+        let peers = tracker.get_peers_for_block_id(block_id_tuple);
+        assert!(
+            peers.contains(&peer_id),
+            "Peer ID should be associated with block ID"
+        );
+
+        // Check peer_to_block_ids mapping
+        let block_ids = tracker.get_block_ids_for_peer(peer_id);
+        assert!(
+            block_ids.contains(&block_id_str),
+            "Block ID should be associated with peer ID"
+        );
+    }
+
+    #[tokio::test]
+    #[cfg_attr(miri, ignore)] // ibig has a memory leak so miri fails this test
+    async fn test_track_remove_effect() {
+        use equix::EquiXBuilder;
+        use tokio::sync::mpsc;
+
+        // Create test peer ID
+        let peer_id = PeerId::random();
+
+        // Create a message tracker and add an entry that we'll later remove
+        let message_tracker = Arc::new(Mutex::new(MessageTracker::new()));
+
+        // Create block ID as [1 2 3 4 5]
+        let mut setup_slab = NounSlab::new();
+        let block_id_tuple = T(&mut setup_slab, &[D(1), D(2), D(3), D(4), D(5)]);
+
+        {
+            let mut tracker = message_tracker.lock().await;
+            tracker
+                .track_block_id_and_peer(block_id_tuple, peer_id)
+                .unwrap_or_else(|_| {
+                    panic!(
+                        "Called `expect()` at {}:{} (git sha: {})",
+                        file!(),
+                        line!(),
+                        option_env!("GIT_SHA").unwrap_or("unknown")
+                    )
+                });
+
+            // Verify it was added correctly
+            assert!(tracker.is_tracking_block_id(block_id_tuple));
+            assert!(tracker.is_tracking_peer(peer_id));
+        }
+
+        // Now create the track remove effect noun
+        let mut effect_slab = NounSlab::new();
+        let track_atom = make_tas(&mut effect_slab, "track");
+        let remove_atom = make_tas(&mut effect_slab, "remove");
+
+        // Copy the block ID tuple to the effect slab
+        let block_id_tuple_in_effect = T(&mut effect_slab, &[D(1), D(2), D(3), D(4), D(5)]);
+
+        // Build the noun structure: [%track %remove block-id]
+        let remove_cell = T(
+            &mut effect_slab,
+            &[remove_atom.as_noun(), block_id_tuple_in_effect],
+        );
+        let track_cell = T(&mut effect_slab, &[track_atom.as_noun(), remove_cell]);
+        effect_slab.set_root(track_cell);
+
+        // Create channel for SwarmAction (not used in this test)
+        let (swarm_tx, _swarm_rx) = mpsc::channel(1);
+
+        // Call handle_effect with the track remove effect
+        let result = handle_effect(
+            effect_slab,
+            swarm_tx,
+            EquiXBuilder::new(),
+            PeerId::random(), // local peer ID (not relevant for this test)
+            vec![],           // connected peers (not relevant for this test)
+            message_tracker.clone(),
+        )
+        .await;
+
+        // Verify the function succeeded
+        assert!(result.is_ok(), "handle_effect should succeed");
+
+        // Verify the message tracker state after removal
+        let tracker = message_tracker.lock().await;
+
+        // Check that the block ID was removed from block_id_to_peers
+        assert!(
+            !tracker.is_tracking_block_id(block_id_tuple),
+            "Block ID should be removed"
+        );
+
+        // Check that the peer's entry in peer_to_block_ids is also removed
+        // (since this was the only block ID associated with the peer)
+        assert!(
+            !tracker.is_tracking_peer(peer_id),
+            "Peer ID should be removed since it has no more block IDs"
+        );
+    }
+
+    #[tokio::test]
+    #[cfg_attr(miri, ignore)] // ibig has a memory leak so miri fails this test
+    async fn test_liar_block_id_effect() {
+        use equix::EquiXBuilder;
+        use tokio::sync::mpsc;
+
+        println!("Starting test_liar_block_id_effect");
+
+        // Create test peer IDs
+        let bad_peer1 = PeerId::random();
+        let bad_peer2 = PeerId::random();
+        let good_peer = PeerId::random();
+        println!(
+            "Created peer_ids: bad1={}, bad2={}, good={}",
+            bad_peer1, bad_peer2, good_peer
+        );
+
+        // Create a message tracker and add entries
+        let message_tracker = Arc::new(Mutex::new(MessageTracker::new()));
+
+        // Create block IDs
+        let mut setup_slab = NounSlab::new();
+        // Bad block ID as [1 2 3 4 5]
+        let bad_block_id = T(&mut setup_slab, &[D(1), D(2), D(3), D(4), D(5)]);
+        // Good block ID as [6 7 8 9 10]
+        let good_block_id = T(&mut setup_slab, &[D(6), D(7), D(8), D(9), D(10)]);
+        println!("Created block_ids");
+
+        {
+            let mut tracker = message_tracker.lock().await;
+            println!("Tracking block_ids and peers");
+
+            // Associate bad_peer1 with the bad block
+            tracker
+                .track_block_id_and_peer(bad_block_id, bad_peer1)
+                .unwrap_or_else(|_| {
+                    panic!(
+                        "Called `expect()` at {}:{} (git sha: {})",
+                        file!(),
+                        line!(),
+                        option_env!("GIT_SHA").unwrap_or("unknown")
+                    )
+                });
+
+            // Associate bad_peer2 with the bad block
+            tracker
+                .add_peer_if_tracking_block_id(bad_block_id, bad_peer2)
+                .unwrap_or_else(|_| {
+                    panic!(
+                        "Called `expect()` at {}:{} (git sha: {})",
+                        file!(),
+                        line!(),
+                        option_env!("GIT_SHA").unwrap_or("unknown")
+                    )
+                });
+
+            // Associate good_peer with a different block
+            tracker
+                .track_block_id_and_peer(good_block_id, good_peer)
+                .unwrap_or_else(|_| {
+                    panic!(
+                        "Called `expect()` at {}:{} (git sha: {})",
+                        file!(),
+                        line!(),
+                        option_env!("GIT_SHA").unwrap_or("unknown")
+                    )
+                });
+
+            // Verify tracking is working
+            assert!(tracker.is_tracking_block_id(bad_block_id));
+            assert!(tracker.is_tracking_block_id(good_block_id));
+            assert!(tracker.is_tracking_peer(bad_peer1));
+            assert!(tracker.is_tracking_peer(bad_peer2));
+            assert!(tracker.is_tracking_peer(good_peer));
+            println!("Verified tracking is working");
+        }
+
+        // Now create the liar-block-id effect noun for the bad block
+        let mut effect_slab = NounSlab::new();
+        let liar_block_id_atom = Atom::from_value(&mut effect_slab, "liar-block-id")
+            .expect("Failed to create liar-block-id atom");
+
+        // Copy the bad block ID tuple to the effect slab
+        let bad_block_id_in_effect = T(&mut effect_slab, &[D(1), D(2), D(3), D(4), D(5)]);
+
+        // Build the noun structure: [%liar-block-id bad-block-id]
+        let effect = T(
+            &mut effect_slab,
+            &[liar_block_id_atom.as_noun(), bad_block_id_in_effect],
+        );
+        effect_slab.set_root(effect);
+        println!("Created liar-block-id effect");
+
+        // Create channel for SwarmAction
+        let (swarm_tx, mut swarm_rx) = mpsc::channel(10); // Increased capacity for multiple actions
+        println!("Created swarm channel");
+
+        // Call handle_effect with the liar-block-id effect
+        println!("Calling handle_effect");
+        let result = handle_effect(
+            effect_slab,
+            swarm_tx,
+            EquiXBuilder::new(),
+            PeerId::random(), // local peer ID (not relevant for this test)
+            vec![],           // connected peers (not relevant for this test)
+            message_tracker.clone(),
+        )
+        .await;
+
+        println!("handle_effect result: {:?}", result);
+
+        // Verify the function succeeded
+        assert!(result.is_ok(), "handle_effect should succeed");
+        println!("Verified handle_effect succeeded");
+
+        // Collect all the block actions
+        let mut blocked_peers = Vec::new();
+        while let Ok(action) = swarm_rx.try_recv() {
+            match action {
+                SwarmAction::BlockPeer { peer_id } => {
+                    println!("Received BlockPeer action for peer: {}", peer_id);
+                    blocked_peers.push(peer_id);
+                }
+                other => {
+                    println!("Unexpected action received: {:?}", other);
+                    panic!("Expected BlockPeer action, got {:?}", other);
+                }
+            }
+        }
+
+        // Verify both bad peers were blocked
+        assert_eq!(
+            blocked_peers.len(),
+            2,
+            "Should have blocked exactly 2 peers"
+        );
+        assert!(
+            blocked_peers.contains(&bad_peer1),
+            "bad_peer1 should be blocked"
+        );
+        assert!(
+            blocked_peers.contains(&bad_peer2),
+            "bad_peer2 should be blocked"
+        );
+        assert!(
+            !blocked_peers.contains(&good_peer),
+            "good_peer should not be blocked"
+        );
+        println!("Verified correct peers were blocked");
+
+        // Verify the bad block ID was removed from the tracker
+        {
+            let tracker = message_tracker.lock().await;
+
+            // Bad block should be removed
+            assert!(
+                !tracker.is_tracking_block_id(bad_block_id),
+                "Bad block ID should be removed"
+            );
+
+            // Good block should still be tracked
+            assert!(
+                tracker.is_tracking_block_id(good_block_id),
+                "Good block ID should still be tracked"
+            );
+
+            // Bad peers should be removed
+            assert!(
+                !tracker.is_tracking_peer(bad_peer1),
+                "bad_peer1 should be removed from tracker"
+            );
+            assert!(
+                !tracker.is_tracking_peer(bad_peer2),
+                "bad_peer2 should be removed from tracker"
+            );
+
+            // Good peer should still be tracked
+            assert!(
+                tracker.is_tracking_peer(good_peer),
+                "good_peer should still be tracked"
+            );
+
+            println!("Verified tracker state is correct after processing effect");
+        }
+    }
+
+    #[tokio::test]
+    #[cfg_attr(miri, ignore)] // ibig has a memory leak so miri fails this test
+
+    async fn test_seen_block_effect() {
+        use equix::EquiXBuilder;
+        use tokio::sync::mpsc;
+
+        let mut effect_slab = NounSlab::new();
+        let block_id = T(&mut effect_slab, &[D(1), D(2), D(3), D(4), D(5)]);
+        let block_id_str = tip5_hash_to_base58(block_id).unwrap_or_else(|_| {
+            panic!(
+                "Called `expect()` at {}:{} (git sha: {})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA").unwrap_or("unknown")
+            )
+        });
+        let effect = T(
+            &mut effect_slab,
+            &[D(tas!(b"seen")), D(tas!(b"block")), block_id],
+        );
+        effect_slab.set_root(effect);
+
+        let (swarm_tx, _) = mpsc::channel(1);
+
+        let message_tracker = Arc::new(Mutex::new(MessageTracker::new()));
+        let message_tracker_clone = Arc::clone(&message_tracker); // Clone the Arc, not the MessageTracker
+        let result = handle_effect(
+            effect_slab,
+            swarm_tx,
+            EquiXBuilder::new(),
+            PeerId::random(), // local peer ID (not relevant for this test)
+            vec![],           // connected peers (not relevant for this test)
+            message_tracker_clone,
+        )
+        .await;
+
+        assert!(result.is_ok(), "handle_effect should succeed");
+
+        // Verify that the block id was added to the seen_blocks set
+        let tracker = message_tracker.lock().await;
+        let contains = tracker.seen_blocks.contains(&block_id_str);
+        assert!(contains, "Block ID should be marked as seen");
+    }
+
+    #[tokio::test]
+    #[cfg_attr(miri, ignore)] // ibig has a memory leak so miri fails this test
+    async fn test_seen_tx_effect() {
+        use equix::EquiXBuilder;
+        use tokio::sync::mpsc;
+
+        let mut effect_slab = NounSlab::new();
+        let tx_id = T(&mut effect_slab, &[D(1), D(2), D(3), D(4), D(5)]);
+        let tx_id_str = tip5_hash_to_base58(tx_id).unwrap_or_else(|_| {
+            panic!(
+                "Called `expect()` at {}:{} (git sha: {})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA").unwrap_or("unknown")
+            )
+        });
+        let effect = T(&mut effect_slab, &[D(tas!(b"seen")), D(tas!(b"tx")), tx_id]);
+
+        effect_slab.set_root(effect);
+
+        let (swarm_tx, _) = mpsc::channel(1);
+
+        let message_tracker = Arc::new(Mutex::new(MessageTracker::new()));
+        let message_tracker_clone = Arc::clone(&message_tracker); // Clone the Arc, not the MessageTracker
+        let result = handle_effect(
+            effect_slab,
+            swarm_tx,
+            EquiXBuilder::new(),
+            PeerId::random(), // local peer ID (not relevant for this test)
+            vec![],           // connected peers (not relevant for this test)
+            message_tracker_clone,
+        )
+        .await;
+
+        assert!(result.is_ok(), "handle_effect should succeed");
+
+        // Verify that the tx id was added to the seen_blocks set
+        let tracker = message_tracker.lock().await;
+        let contains = tracker.seen_txs.contains(&tx_id_str);
+        assert!(contains, "tx ID should be marked as seen");
+    }
+}

+ 266 - 0
crates/nockchain-libp2p-io/src/p2p.rs

@@ -0,0 +1,266 @@
+use std::convert::Infallible;
+use std::error::Error;
+use std::time::Duration;
+
+use crown::nockapp::NockAppError;
+use hickory_resolver::config::{ResolverConfig, ResolverOpts};
+use libp2p::identity::Keypair;
+use libp2p::multiaddr::Multiaddr;
+use libp2p::request_response::{self, cbor, ResponseChannel};
+use libp2p::swarm::behaviour::toggle::Toggle;
+use libp2p::swarm::NetworkBehaviour;
+use libp2p::{
+    allow_block_list, connection_limits, identify, kad, memory_connection_limits, ping, PeerId,
+    Swarm,
+};
+use tracing::{debug, trace};
+
+use crate::nc::*;
+
+/** How often we should run a kademlia bootstrap to keep our peer table fresh */
+pub const KADEMLIA_BOOTSTRAP_INTERVAL_SECS: Duration = Duration::from_secs(25);
+
+/** How long we should keep a peer connection alive with no traffic */
+pub const PEER_TIMEOUT_SECS: u64 = 300;
+
+/** How long should we wait before timing out the connection */
+pub const CONNECTION_TIMEOUT_SECS: u64 = PEER_TIMEOUT_SECS;
+pub const MAX_IDLE_TIMEOUT_MILLISECS: u32 = PEER_TIMEOUT_SECS as u32 * 1_000;
+pub const KEEP_ALIVE_INTERVAL_SECS: u64 = 30;
+pub const HANDSHAKE_TIMEOUT_SECS: u64 = PEER_TIMEOUT_SECS;
+
+// TODO: command-line/configure
+/** Maximum number of established *incoming* connections */
+pub const MAX_ESTABLISHED_INCOMING_CONNECTIONS: u32 = 32;
+
+// TODO: command-line/configure
+/** Maximum number of established *incoming* connections */
+pub const MAX_ESTABLISHED_OUTGOING_CONNECTIONS: u32 = 32;
+
+// TODO: command-line/configure
+/** Maximum number of established connections */
+pub const MAX_ESTABLISHED_CONNECTIONS: u32 = 64;
+
+// TODO: command-line/configure
+/** Maximum number of established connections with a single peer ID */
+pub const MAX_ESTABLISHED_CONNECTIONS_PER_PEER: u32 = 2;
+
+// TODO: command-line/configure
+/** Maximum pending incoming connections */
+pub const MAX_PENDING_INCOMING_CONNECTIONS: u32 = 16;
+
+// TODO: command-line/configure
+/** Maximum pending outcoing connections */
+pub const MAX_PENDING_OUTGOING_CONNECTIONS: u32 = 16;
+
+// ALL PROTOCOLS MUST HAVE UNIQUE VERSIONS
+pub const REQ_RES_PROTOCOL_VERSION: &str = "/nockchain-1-req-res";
+pub const KAD_PROTOCOL_VERSION: &str = "/nockchain-1-kad";
+pub const IDENTIFY_PROTOCOL_VERSION: &str = "/nockchain-1-identify";
+
+#[derive(Debug)]
+pub enum SwarmAction {
+    SendResponse {
+        channel: ResponseChannel<NockchainResponse>,
+        response: NockchainResponse,
+    },
+    SendRequest {
+        peer_id: PeerId,
+        request: NockchainRequest,
+    },
+    BlockPeer {
+        peer_id: PeerId,
+    },
+}
+
+#[derive(NetworkBehaviour)]
+#[behaviour(to_swarm = "NockchainEvent")]
+/** Composed [NetworkBehaviour] implementation for Nockchain */
+pub struct NockchainBehaviour {
+    /// Allows nodes to connect via just IP and port and exchange pubkeys
+    identify: identify::Behaviour,
+    /// Connectivity testing
+    ping: ping::Behaviour,
+    /// Peer discovery via a DHT
+    pub kad: kad::Behaviour<kad::store::MemoryStore>,
+    /// Peer banning
+    pub allow_block_list: allow_block_list::Behaviour<allow_block_list::BlockedPeers>,
+    /// Peer whitelisting
+    pub allow_peers: Toggle<allow_block_list::Behaviour<allow_block_list::AllowedPeers>>,
+    /// Connection limiting
+    connection_limits: connection_limits::Behaviour,
+    /// Memory connection limits
+    memory_connection_limits: Toggle<memory_connection_limits::Behaviour>,
+    /// Actual comms
+    pub request_response: cbor::Behaviour<NockchainRequest, NockchainResponse>,
+}
+
+impl NockchainBehaviour {
+    fn pre_new(
+        allowed: Option<allow_block_list::Behaviour<allow_block_list::AllowedPeers>>,
+        limits: connection_limits::ConnectionLimits,
+        memory_limits: Option<memory_connection_limits::Behaviour>,
+    ) -> impl FnOnce(&libp2p::identity::Keypair) -> Self {
+        |keypair: &libp2p::identity::Keypair| {
+            let peer_id = libp2p::identity::PeerId::from_public_key(&keypair.public());
+
+            let identify_config =
+                identify::Config::new(IDENTIFY_PROTOCOL_VERSION.to_string(), keypair.public())
+                    .with_interval(Duration::from_secs(15))
+                    .with_hide_listen_addrs(true); // Only send externally confirmed addresses so we don't send loopback addresses
+            let identify_behaviour = identify::Behaviour::new(identify_config);
+
+            let memory_store = kad::store::MemoryStore::new(peer_id);
+
+            let mut kad_config =
+                kad::Config::new(libp2p::StreamProtocol::new(KAD_PROTOCOL_VERSION));
+            // kademlia config methods return an &mut so we can't do this in the let binding.
+            kad_config.set_periodic_bootstrap_interval(Some(KADEMLIA_BOOTSTRAP_INTERVAL_SECS));
+            let kad_behaviour = kad::Behaviour::with_config(peer_id, memory_store, kad_config);
+
+            let request_response_config = request_response::Config::default()
+                .with_max_concurrent_streams(200)
+                .with_request_timeout(std::time::Duration::from_secs(300));
+
+            let request_response_behaviour = cbor::Behaviour::new(
+                [(
+                    libp2p::StreamProtocol::new(REQ_RES_PROTOCOL_VERSION),
+                    request_response::ProtocolSupport::Full,
+                )],
+                request_response_config,
+            );
+            let connection_limits_behaviour = connection_limits::Behaviour::new(limits);
+            let memory_connection_limits =
+                Toggle::<memory_connection_limits::Behaviour>::from(memory_limits);
+
+            let allow_peers =
+                Toggle::<allow_block_list::Behaviour<allow_block_list::AllowedPeers>>::from(
+                    allowed,
+                );
+            NockchainBehaviour {
+                ping: ping::Behaviour::default(),
+                identify: identify_behaviour,
+                kad: kad_behaviour,
+                allow_block_list: allow_block_list::Behaviour::default(),
+                allow_peers,
+                request_response: request_response_behaviour,
+                connection_limits: connection_limits_behaviour,
+                memory_connection_limits,
+            }
+        }
+    }
+}
+
+/// # Create a swarm and set it to listen
+///
+/// This function initializes a libp2p swarm with the provided keypair and binding addresses.
+/// It configures the swarm to listen on specified multiaddresses and sets up the behavior for network interactions.
+///
+/// # Arguments
+/// * `keypair` - The keypair for the node's identity
+/// * `bind` - A vector of multiaddresses specifying the network interfaces to bind to
+///
+/// # Returns
+/// A Result containing the Swarm instance or an error if any operation fails
+pub fn start_swarm(
+    keypair: Keypair,
+    bind: Vec<Multiaddr>,
+    allowed: Option<allow_block_list::Behaviour<allow_block_list::AllowedPeers>>,
+    limits: connection_limits::ConnectionLimits,
+    memory_limits: Option<memory_connection_limits::Behaviour>,
+) -> Result<Swarm<NockchainBehaviour>, Box<dyn Error>> {
+    let (resolver_config, resolver_opts) =
+        if let Ok(sys) = hickory_resolver::system_conf::read_system_conf() {
+            debug!("resolver configs and opts: {:?}", sys);
+            sys
+        } else {
+            (ResolverConfig::cloudflare(), ResolverOpts::default())
+        };
+
+    let mut swarm = libp2p::SwarmBuilder::with_existing_identity(keypair)
+        .with_tokio()
+        .with_quic_config(|mut cfg| {
+            cfg.max_idle_timeout = MAX_IDLE_TIMEOUT_MILLISECS;
+            cfg.keep_alive_interval = Duration::from_secs(KEEP_ALIVE_INTERVAL_SECS);
+            cfg.handshake_timeout = Duration::from_secs(HANDSHAKE_TIMEOUT_SECS);
+            cfg
+        })
+        .with_dns_config(resolver_config, resolver_opts)
+        .with_behaviour(NockchainBehaviour::pre_new(allowed, limits, memory_limits))?
+        .with_swarm_config(|cfg| {
+            cfg.with_idle_connection_timeout(Duration::from_secs(PEER_TIMEOUT_SECS))
+        })
+        .with_connection_timeout(std::time::Duration::from_secs(CONNECTION_TIMEOUT_SECS))
+        .build();
+
+    for bind_addr in bind {
+        swarm.listen_on(bind_addr)?;
+    }
+    Ok(swarm)
+}
+
+// TODO: We need to box identify::Event but we are on stable so no boxed patterns.
+#[derive(Debug)]
+#[allow(dead_code)]
+#[allow(clippy::large_enum_variant)]
+/** Events that can be emitted by the swarm running [NockchainBehaviour] */
+pub enum NockchainEvent {
+    /// Received or sent identify message
+    Identify(identify::Event),
+    /// Received or failed ping
+    Ping(ping::Event),
+    /// DHT state changes
+    Kad(kad::Event),
+    /// Request or response received from peer
+    RequestResponse(request_response::Event<NockchainRequest, NockchainResponse>),
+}
+
+impl From<identify::Event> for NockchainEvent {
+    fn from(event: identify::Event) -> Self {
+        Self::Identify(event)
+    }
+}
+
+impl From<ping::Event> for NockchainEvent {
+    fn from(event: ping::Event) -> Self {
+        Self::Ping(event)
+    }
+}
+
+impl From<kad::Event> for NockchainEvent {
+    fn from(event: kad::Event) -> Self {
+        Self::Kad(event)
+    }
+}
+
+impl From<Infallible> for NockchainEvent {
+    fn from(i: Infallible) -> Self {
+        match i {}
+    }
+}
+
+impl From<request_response::Event<NockchainRequest, NockchainResponse>> for NockchainEvent {
+    fn from(event: request_response::Event<NockchainRequest, NockchainResponse>) -> Self {
+        Self::RequestResponse(event)
+    }
+}
+
+///** Handler for "identify" messages */
+//#[instrument(skip(swarm))]
+pub fn identify_received(
+    swarm: &mut Swarm<NockchainBehaviour>,
+    peer_id: PeerId,
+    info: libp2p::identify::Info,
+) -> Result<(), NockAppError> {
+    swarm.add_external_address(info.observed_addr.clone());
+    let us = *swarm.local_peer_id();
+    let kad = &mut swarm.behaviour_mut().kad;
+    trace!("identify received for peer {}", peer_id);
+    trace!("Adding address {} for us: {}", info.observed_addr, us);
+    kad.add_address(&us, info.observed_addr);
+    for addr in info.listen_addrs {
+        trace!("Adding address {} for peer {}", addr, peer_id);
+        kad.add_address(&peer_id, addr);
+    }
+    Ok(())
+}

+ 589 - 0
crates/nockchain-libp2p-io/src/p2p_util.rs

@@ -0,0 +1,589 @@
+use std::collections::{BTreeMap, BTreeSet};
+use std::str::FromStr;
+
+use crown::nockapp::NockAppError;
+use crown::noun::slab::NounSlab;
+use crown::{AtomExt, NounExt};
+use libp2p::PeerId;
+use sword::noun::Noun;
+use sword_macros::tas;
+use tracing::debug;
+
+use crate::metrics::NockchainP2PMetrics;
+use crate::tip5_util::tip5_hash_to_base58;
+
+pub trait PeerIdExt {
+    fn from_noun(noun: Noun) -> Result<PeerId, NockAppError>;
+}
+
+impl PeerIdExt for PeerId {
+    fn from_noun(noun: Noun) -> Result<PeerId, NockAppError> {
+        let peer_id_bytes = noun.as_atom()?.to_bytes_until_nul()?;
+        let peer_id_str = String::from_utf8(peer_id_bytes)?;
+        PeerId::from_str(&peer_id_str).map_err(|_| NockAppError::OtherError)
+    }
+}
+
+/// This struct is used to track which peers sent us which block IDs.
+/// `block_id_to_peers` is the one we really care about, since it's what we use
+/// to figure out which peers to ban when we get a %liar-block-id effect.
+/// But when we are removing peers, we don't want to have to iterate over
+/// every block ID and check if the peer is in the set. So we also maintain
+/// a `peer_to_block_ids` map.
+pub struct MessageTracker {
+    block_id_to_peers: BTreeMap<String, BTreeSet<PeerId>>,
+    peer_to_block_ids: BTreeMap<PeerId, BTreeSet<String>>,
+    pub seen_blocks: BTreeSet<String>,
+    pub seen_txs: BTreeSet<String>,
+    pub block_cache: BTreeMap<u64, NounSlab>,
+    pub tx_cache: BTreeMap<String, NounSlab>,
+}
+
+impl Default for MessageTracker {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl MessageTracker {
+    pub fn new() -> Self {
+        Self {
+            block_id_to_peers: BTreeMap::new(),
+            peer_to_block_ids: BTreeMap::new(),
+            seen_blocks: BTreeSet::new(),
+            seen_txs: BTreeSet::new(),
+            block_cache: BTreeMap::new(),
+            tx_cache: BTreeMap::new(),
+        }
+    }
+
+    fn track_block_id_str_and_peer(&mut self, block_id_str: String, peer_id: PeerId) {
+        self.block_id_to_peers
+            .entry(block_id_str.clone())
+            .or_default()
+            .insert(peer_id);
+
+        self.peer_to_block_ids
+            .entry(peer_id)
+            .or_default()
+            .insert(block_id_str);
+    }
+
+    fn remove_block_id_str(&mut self, block_id: &str) {
+        let Some(peers) = self.block_id_to_peers.remove(block_id) else {
+            return;
+        };
+
+        for peer_id in peers {
+            let Some(block_ids) = self.peer_to_block_ids.get_mut(&peer_id) else {
+                continue;
+            };
+
+            block_ids.remove(block_id);
+            if block_ids.is_empty() {
+                self.peer_to_block_ids.remove(&peer_id);
+            }
+        }
+    }
+
+    /// Removes a peer from the tracker.
+    /// done if a peer disconnects or is banned.
+    pub fn remove_peer(&mut self, peer_id: &PeerId) {
+        let Some(block_ids) = self.peer_to_block_ids.remove(peer_id) else {
+            return;
+        };
+
+        for block_id in block_ids {
+            let Some(peers) = self.block_id_to_peers.get_mut(&block_id) else {
+                continue;
+            };
+
+            peers.remove(peer_id);
+            if peers.is_empty() {
+                self.block_id_to_peers.remove(&block_id);
+            }
+        }
+    }
+
+    /// Adds a block ID and peer to the tracker.
+    /// implements [%track %add block-id peer-id] effect
+    pub fn track_block_id_and_peer(
+        &mut self,
+        block_id: Noun,
+        peer_id: PeerId,
+    ) -> Result<(), NockAppError> {
+        let block_id_str = tip5_hash_to_base58(block_id)?;
+        self.track_block_id_str_and_peer(block_id_str, peer_id);
+        Ok(())
+    }
+
+    /// Adds a peer to an existing block ID. Returns true if the block ID exists and the peer was added,
+    /// false if the block ID doesn't exist in the tracker.
+    pub fn add_peer_if_tracking_block_id(
+        &mut self,
+        block_id: Noun,
+        peer_id: PeerId,
+    ) -> Result<bool, NockAppError> {
+        let block_id_str = tip5_hash_to_base58(block_id)?;
+
+        if self.block_id_to_peers.contains_key(&block_id_str) {
+            self.track_block_id_str_and_peer(block_id_str, peer_id);
+            Ok(true)
+        } else {
+            Ok(false)
+        }
+    }
+
+    /// Removes a block ID from the tracker.
+    /// implements [%track %remove block-id] effect
+    pub fn remove_block_id(&mut self, block_id: Noun) -> Result<(), NockAppError> {
+        let block_id_str = tip5_hash_to_base58(block_id)?;
+        self.remove_block_id_str(&block_id_str);
+        Ok(())
+    }
+
+    /// Returns a list of peers that have sent us a given block ID.
+    pub fn get_peers_for_block_id(&self, block_id: Noun) -> Vec<PeerId> {
+        let Ok(block_id_str) = tip5_hash_to_base58(block_id) else {
+            panic!("Invalid block ID");
+        };
+        self.block_id_to_peers
+            .get(&block_id_str)
+            .map(|peers| peers.iter().cloned().collect::<Vec<_>>())
+            .unwrap_or_default()
+    }
+
+    /// Returns a list of block IDs that a given peer has sent us.
+    pub fn get_block_ids_for_peer(&self, peer_id: PeerId) -> Vec<String> {
+        self.peer_to_block_ids
+            .get(&peer_id)
+            .map(|block_ids| block_ids.iter().cloned().collect::<Vec<_>>())
+            .unwrap_or_default()
+    }
+
+    /// Returns true if we are tracking a given block ID.
+    pub fn is_tracking_block_id(&self, block_id: Noun) -> bool {
+        let Ok(block_id_str) = tip5_hash_to_base58(block_id) else {
+            return false;
+        };
+        self.block_id_to_peers.contains_key(&block_id_str)
+    }
+
+    pub fn is_tracking_peer(&self, peer_id: PeerId) -> bool {
+        self.peer_to_block_ids.contains_key(&peer_id)
+    }
+
+    //  Removes the block id from the MessageTracker maps and returns all the
+    //  peers who had sent us that block.
+    pub fn process_bad_block_id(&mut self, block_id: Noun) -> Result<Vec<PeerId>, NockAppError> {
+        let block_id_str = tip5_hash_to_base58(block_id)?;
+        let peers_to_ban = self
+            .block_id_to_peers
+            .get(&block_id_str)
+            .map(|peers| peers.iter().cloned().collect::<Vec<_>>())
+            .unwrap_or_default();
+
+        // Remove each peer that sent us this bad block
+        for peer in &peers_to_ban {
+            self.remove_peer(peer);
+        }
+
+        self.remove_block_id(block_id)?;
+
+        Ok(peers_to_ban)
+    }
+
+    pub async fn check_cache(
+        &mut self,
+        request: &Noun,
+        metrics: &NockchainP2PMetrics,
+    ) -> Result<Option<NounSlab>, NockAppError> {
+        let tag = request.as_cell()?.head().as_direct()?.data();
+        if tag != tas!(b"request") {
+            return Ok(None);
+        }
+
+        let request_body = request.as_cell()?.tail().as_cell()?;
+        if request_body.head().eq_bytes(b"block") {
+            let tail = request_body.tail();
+            let kind = tail.as_cell()?.head();
+            if !kind.eq_bytes(b"by-height") {
+                return Ok(None);
+            }
+            let height = tail.as_cell()?.tail().as_direct()?.data();
+            if let Some(cached_block) = self.block_cache.get(&height) {
+                debug!("found cached block request by height={:?}", height);
+                metrics.block_request_cache_hits.increment();
+                Ok(Some(cached_block.clone()))
+            } else {
+                debug!("didn't find cached block request by height={:?}", height);
+                metrics.block_request_cache_misses.increment();
+                Ok(None)
+            }
+        } else if request_body.head().eq_bytes(b"raw-tx") {
+            let tail = request_body.tail();
+            let kind = tail.as_cell()?.head();
+            if !kind.eq_bytes(b"by-id") {
+                return Ok(None);
+            }
+            let tx_id = tail.as_cell()?.tail();
+            let tx_id_str = tip5_hash_to_base58(tx_id)?;
+            if let Some(cached_tx) = self.tx_cache.get(&tx_id_str) {
+                debug!("found cached tx request by id={:?}", tx_id_str);
+                metrics.tx_request_cache_hits.increment();
+                return Ok(Some(cached_tx.clone()));
+            } else {
+                debug!("didn't find cached tx request by id={:?}", tx_id_str);
+                metrics.tx_request_cache_misses.increment();
+                return Ok(None);
+            }
+        } else {
+            return Ok(None);
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crown::noun::slab::NounSlab;
+    use sword::noun::{D, T};
+
+    use super::*;
+
+    #[test]
+    #[cfg_attr(miri, ignore)] // ibig has a memory leak so miri fails this test
+    fn test_message_tracker_basic() {
+        let mut tracker = MessageTracker::new();
+        let peer_id = PeerId::random();
+
+        // Create a block ID as [1 2 3 4 5]
+        let mut slab = NounSlab::new();
+        let block_id_tuple = T(&mut slab, &[D(1), D(2), D(3), D(4), D(5)]);
+
+        // Add the block ID
+        tracker
+            .track_block_id_and_peer(block_id_tuple, peer_id)
+            .unwrap_or_else(|_| {
+                panic!(
+                    "Called `expect()` at {}:{} (git sha: {})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA").unwrap_or("unknown")
+                )
+            });
+
+        // Get the block ID string
+        let block_id_str = tip5_hash_to_base58(block_id_tuple).unwrap_or_else(|_| {
+            panic!(
+                "Called `expect()` at {}:{} (git sha: {})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA").unwrap_or("unknown")
+            )
+        });
+
+        // Verify it was added correctly
+        assert!(tracker.block_id_to_peers.contains_key(&block_id_str));
+        assert!(tracker.peer_to_block_ids.contains_key(&peer_id));
+
+        // Remove the block ID
+        tracker.remove_block_id(block_id_tuple).unwrap_or_else(|_| {
+            panic!(
+                "Called `expect()` at {}:{} (git sha: {})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA").unwrap_or("unknown")
+            )
+        });
+
+        // Verify it was removed
+        assert!(!tracker.block_id_to_peers.contains_key(&block_id_str));
+        assert!(!tracker.peer_to_block_ids.contains_key(&peer_id));
+    }
+
+    #[test]
+    #[cfg_attr(miri, ignore)] // ibig has a memory leak so miri fails this test
+    fn test_bad_block_id() {
+        let mut tracker = MessageTracker::new();
+        let peer_id = PeerId::random();
+
+        // Create a block ID
+        let mut slab = NounSlab::new();
+        let block_id_tuple = T(&mut slab, &[D(1), D(2), D(3), D(4), D(5)]);
+
+        // Track the block ID
+        tracker
+            .track_block_id_and_peer(block_id_tuple, peer_id)
+            .unwrap_or_else(|_| {
+                panic!(
+                    "Called `expect()` at {}:{} (git sha: {})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA").unwrap_or("unknown")
+                )
+            });
+
+        // Mark it as bad
+        let peers_to_ban = tracker
+            .process_bad_block_id(block_id_tuple)
+            .unwrap_or_else(|_| {
+                panic!(
+                    "Called `expect()` at {}:{} (git sha: {})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA").unwrap_or("unknown")
+                )
+            });
+
+        // Verify the peer is returned for banning
+        assert_eq!(peers_to_ban.len(), 1);
+        assert_eq!(peers_to_ban[0], peer_id);
+    }
+
+    #[test]
+    fn test_peer_id_base58_roundtrip() {
+        use sword::noun::Atom;
+        // Generate a random PeerId
+        let original_peer_id = PeerId::random();
+        let base58_str = original_peer_id.to_base58();
+        println!("Original base58: {}", base58_str);
+
+        // Create a NounSlab and store the base58 string as an Atom
+        let mut slab = NounSlab::new();
+        let peer_id_atom = Atom::from_value(&mut slab, base58_str.as_bytes())
+            .expect("Failed to create peer ID atom");
+
+        // Use the from_noun method to convert back to PeerId
+        let recovered_peer_id = PeerId::from_noun(peer_id_atom.as_noun()).unwrap_or_else(|_| {
+            panic!(
+                "Called `expect()` at {}:{} (git sha: {})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA").unwrap_or("unknown")
+            )
+        });
+
+        // Verify round trip
+        assert_eq!(original_peer_id, recovered_peer_id);
+    }
+
+    #[test]
+    #[cfg_attr(miri, ignore)] // ibig has a memory leak so miri fails this test
+    fn test_add_peer_if_tracking_block_id() {
+        let mut tracker = MessageTracker::new();
+        let peer_id1 = PeerId::random();
+        let peer_id2 = PeerId::random();
+
+        // Create a block ID
+        let mut slab = NounSlab::new();
+        let block_id_tuple = T(&mut slab, &[D(1), D(2), D(3), D(4), D(5)]);
+
+        // First, try to add a peer to a non-existent block ID
+        let result = tracker
+            .add_peer_if_tracking_block_id(block_id_tuple, peer_id1)
+            .unwrap_or_else(|_| {
+                panic!(
+                    "Called `expect()` at {}:{} (git sha: {})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA").unwrap_or("unknown")
+                )
+            });
+        assert!(!result); // Should return false since block ID doesn't exist
+
+        // Now track the block ID with peer1
+        tracker
+            .track_block_id_and_peer(block_id_tuple, peer_id1)
+            .unwrap_or_else(|_| {
+                panic!(
+                    "Called `expect()` at {}:{} (git sha: {})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA").unwrap_or("unknown")
+                )
+            });
+
+        // Add peer2 to the existing block ID
+        let result = tracker
+            .add_peer_if_tracking_block_id(block_id_tuple, peer_id2)
+            .unwrap_or_else(|_| {
+                panic!(
+                    "Called `expect()` at {}:{} (git sha: {})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA").unwrap_or("unknown")
+                )
+            });
+        assert!(result); // Should return true since block ID exists
+
+        // Verify both peers are associated with the block ID
+        let peers = tracker.get_peers_for_block_id(block_id_tuple);
+        assert_eq!(peers.len(), 2);
+        assert!(peers.contains(&peer_id1));
+        assert!(peers.contains(&peer_id2));
+    }
+
+    #[test]
+    #[cfg_attr(miri, ignore)] // ibig has a memory leak so miri fails this test
+    fn test_add_peer_if_tracking_block_id_then_remove() {
+        let mut tracker = MessageTracker::new();
+        let peer_id1 = PeerId::random();
+        let peer_id2 = PeerId::random();
+
+        // Create a block ID
+        let mut slab = NounSlab::new();
+        let block_id_tuple = T(&mut slab, &[D(1), D(2), D(3), D(4), D(5)]);
+        let block_id_str = tip5_hash_to_base58(block_id_tuple).unwrap_or_else(|_| {
+            panic!(
+                "Called `expect()` at {}:{} (git sha: {})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA").unwrap_or("unknown")
+            )
+        });
+
+        // Track the block ID with peer1
+        tracker
+            .track_block_id_and_peer(block_id_tuple, peer_id1)
+            .unwrap_or_else(|_| {
+                panic!(
+                    "Called `expect()` at {}:{} (git sha: {})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA").unwrap_or("unknown")
+                )
+            });
+
+        // Add peer2 to the existing block ID
+        let result = tracker
+            .add_peer_if_tracking_block_id(block_id_tuple, peer_id2)
+            .unwrap_or_else(|_| {
+                panic!(
+                    "Called `expect()` at {}:{} (git sha: {})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA").unwrap_or("unknown")
+                )
+            });
+        assert!(result); // Should return true since block ID exists
+
+        // Verify both peers are associated with the block ID
+        let peers = tracker.get_peers_for_block_id(block_id_tuple);
+        assert_eq!(peers.len(), 2);
+        assert!(peers.contains(&peer_id1));
+        assert!(peers.contains(&peer_id2));
+
+        // Now remove the block ID
+        tracker.remove_block_id(block_id_tuple).unwrap_or_else(|_| {
+            panic!(
+                "Called `expect()` at {}:{} (git sha: {})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA").unwrap_or("unknown")
+            )
+        });
+
+        // Verify the block ID is no longer tracked
+        let peers_after_removal = tracker.get_peers_for_block_id(block_id_tuple);
+        assert_eq!(peers_after_removal.len(), 0);
+
+        // Verify the block ID is removed from block_id_to_peers
+        assert!(!tracker.block_id_to_peers.contains_key(&block_id_str));
+
+        // Verify the peers either don't exist in the map anymore or don't have this block ID
+        // For peer_id1
+        if let Some(block_ids) = tracker.peer_to_block_ids.get(&peer_id1) {
+            assert!(!block_ids.contains(&block_id_str));
+        }
+        // For peer_id2
+        if let Some(block_ids) = tracker.peer_to_block_ids.get(&peer_id2) {
+            assert!(!block_ids.contains(&block_id_str));
+        }
+    }
+
+    #[test]
+    #[cfg_attr(miri, ignore)] // ibig has a memory leak so miri fails this test
+    fn test_process_bad_block_id_removes_peers() {
+        let mut tracker = MessageTracker::new();
+        let peer_id1 = PeerId::random();
+        let peer_id2 = PeerId::random();
+
+        // Create a block ID
+        let mut slab = NounSlab::new();
+        let block_id_tuple = T(&mut slab, &[D(1), D(2), D(3), D(4), D(5)]);
+
+        // Create another block ID that both peers will share
+        let other_block_id = T(&mut slab, &[D(6), D(7), D(8), D(9), D(10)]);
+
+        // Track both block IDs with both peers
+        tracker
+            .track_block_id_and_peer(block_id_tuple, peer_id1)
+            .unwrap_or_else(|_| {
+                panic!(
+                    "Called `expect()` at {}:{} (git sha: {})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA").unwrap_or("unknown")
+                )
+            });
+        tracker
+            .add_peer_if_tracking_block_id(block_id_tuple, peer_id2)
+            .unwrap_or_else(|_| {
+                panic!(
+                    "Called `expect()` at {}:{} (git sha: {})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA").unwrap_or("unknown")
+                )
+            });
+        tracker
+            .track_block_id_and_peer(other_block_id, peer_id1)
+            .unwrap_or_else(|_| {
+                panic!(
+                    "Called `expect()` at {}:{} (git sha: {})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA").unwrap_or("unknown")
+                )
+            });
+        tracker
+            .add_peer_if_tracking_block_id(other_block_id, peer_id2)
+            .unwrap_or_else(|_| {
+                panic!(
+                    "Called `expect()` at {}:{} (git sha: {})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA").unwrap_or("unknown")
+                )
+            });
+
+        // Verify both peers are tracked
+        assert!(tracker.is_tracking_peer(peer_id1));
+        assert!(tracker.is_tracking_peer(peer_id2));
+
+        // Process the bad block ID
+        let banned_peers = tracker
+            .process_bad_block_id(block_id_tuple)
+            .unwrap_or_else(|_| {
+                panic!(
+                    "Called `expect()` at {}:{} (git sha: {})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA").unwrap_or("unknown")
+                )
+            });
+
+        // Verify both peers were returned for banning
+        assert_eq!(banned_peers.len(), 2);
+        assert!(banned_peers.contains(&peer_id1));
+        assert!(banned_peers.contains(&peer_id2));
+
+        // Verify both peers are no longer tracked
+        assert!(!tracker.is_tracking_peer(peer_id1));
+        assert!(!tracker.is_tracking_peer(peer_id2));
+
+        // Verify the other block ID is also no longer tracked
+        // (since we removed the peers entirely)
+        assert!(!tracker.is_tracking_block_id(other_block_id));
+    }
+}

+ 120 - 0
crates/nockchain-libp2p-io/src/tip5_util.rs

@@ -0,0 +1,120 @@
+use bs58;
+use crown::nockapp::NockAppError;
+use ibig::{ubig, UBig};
+use sword::noun::Noun;
+//TODO all this stuff would be useful as jets, which mostly just requires
+//using the Atom::as_ubig with the NockStack instead of ibig's heap version
+// which we use to avoid having a NockStack sitting around.
+
+// Goldilocks prime
+const P: u64 = 0xffffffff00000001;
+
+/// Tries to convert a Noun to a Base58 string by extracting a 5-tuple, converting it to a decimal, and then to Base58.
+///
+/// # Arguments
+/// * `noun` - The Noun to convert, expected to be a 5-tuple tip5 hash
+///
+/// # Returns
+/// The Noun as a Base58 string
+pub fn tip5_hash_to_base58(noun: Noun) -> Result<String, NockAppError> {
+    let tuple = extract_5_tuple(noun)?;
+    let decimal_value = base_p_to_decimal(tuple)?;
+    let base58_string = ubig_to_base58(decimal_value);
+
+    Ok(base58_string)
+}
+
+pub fn base_p_to_decimal(hash: Vec<Noun>) -> Result<UBig, NockAppError> {
+    let prime_ubig = UBig::from(P);
+    let mut result = ubig!(0);
+
+    for (i, noun) in hash.iter().enumerate() {
+        // Convert Noun to Atom and then to UBig
+        let atom = noun.as_atom()?.as_u64()?;
+
+        // Add the value * P^i to the result
+        result += UBig::from(atom) * prime_ubig.pow(i);
+    }
+
+    Ok(result)
+}
+
+/// Converts a UBig to a Base58 string.
+pub fn ubig_to_base58(value: UBig) -> String {
+    let bytes = value.to_be_bytes();
+    bs58::encode(bytes).into_string()
+}
+
+/// Extracts a 5-tuple from a cell, returning the elements as a Vec
+pub fn extract_5_tuple(tuple_cell: Noun) -> Result<Vec<Noun>, NockAppError> {
+    let mut elements = Vec::with_capacity(5);
+    let mut current = tuple_cell;
+
+    // Extract the first 4 elements
+    for _ in 0..4 {
+        let cell = current.as_cell()?;
+        let head = cell.head();
+        // Verify that the element is an atom
+        head.as_atom()?;
+        elements.push(head);
+        current = cell.tail();
+    }
+
+    // The 5th element is the final item
+    // Verify that the last element is an atom
+    current.as_atom()?;
+    elements.push(current);
+
+    Ok(elements)
+}
+
+#[cfg(test)]
+mod tests {
+    use crown::noun::slab::NounSlab;
+    use sword::noun::{D, T};
+
+    use super::*;
+
+    #[test]
+    #[cfg_attr(miri, ignore)] // ibig has a memory leak so miri fails this test
+    fn test_tip5_hash_to_base58() {
+        use sword::noun::Atom;
+        // Create a NounSlab to use as an allocator
+        let mut slab = NounSlab::new();
+
+        // Test case 1: Simple tuple [1 2 3 4 5]
+        let tuple1 = T(&mut slab, &[D(1), D(2), D(3), D(4), D(5)]);
+        let expected1 = "2V9arU36gvtaofWmNowewoj9u7gbNA2qsJZEQ3WPky5mQ";
+        let result1 = tip5_hash_to_base58(tuple1).unwrap_or_else(|_| {
+            panic!(
+                "Called `expect()` at {}:{} (git sha: {})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA").unwrap_or("unknown")
+            )
+        });
+        assert_eq!(result1, expected1);
+
+        // Test case 2: Complex values
+        let a1 = Atom::new(&mut slab, 0x6ef99e5f3447ffda);
+        let a2 = Atom::new(&mut slab, 0xdf94122d1a98ec99);
+        let a3 = Atom::new(&mut slab, 0xcbf1918337a0e197);
+        let a4 = Atom::new(&mut slab, 0x6cda1112891244ce);
+        let a5 = Atom::new(&mut slab, 0x6e420b8a615508d4);
+
+        let tuple2 = T(
+            &mut slab,
+            &[a1.as_noun(), a2.as_noun(), a3.as_noun(), a4.as_noun(), a5.as_noun()],
+        );
+        let expected2 = "6UkUko9WTwwR6VVRXwPQpUy5pswdvNtoyHspY5n9nLVnBxzAgEyMwPR";
+        let result2 = tip5_hash_to_base58(tuple2).unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+        assert_eq!(result2, expected2);
+    }
+}

+ 49 - 0
crates/nockchain/Cargo.toml

@@ -0,0 +1,49 @@
+[package]
+name = "nockchain"
+build = "build.rs"
+publish = false
+version.workspace = true
+edition.workspace = true
+
+[dependencies]
+choo.workspace = true
+crown.workspace = true
+kernels = { workspace = true, features = ["dumb"] }
+nockchain-bitcoin-sync.workspace = true
+sword.workspace = true
+sword_macros.workspace = true
+
+bitcoincore-rpc.workspace = true
+bs58.workspace = true
+clap.workspace = true
+equix.workspace = true
+libp2p = { workspace = true, features = [
+    "ping",
+    "kad",
+    "identify",
+    "quic",
+    "tls",
+    "dns",
+    "tokio",
+    "macros",
+    "request-response",
+    "cbor",
+] }
+nockchain-libp2p-io.workspace = true
+tempfile = { workspace = true }
+termcolor.workspace = true
+tokio = { workspace = true, features = ["full"] }
+tracing.workspace = true
+tracing-test.workspace = true
+
+zkvm-jetpack.workspace = true
+
+[build-dependencies]
+vergen = { workspace = true, features = [
+    "build",
+    "cargo",
+    "git",
+    "gitcl",
+    "rustc",
+    "si",
+] }

+ 14 - 0
crates/nockchain/build.rs

@@ -0,0 +1,14 @@
+fn main() {
+    // List of Bazel built-in stamping variables to embed
+    let bazel_vars = [
+        "BUILD_EMBED_LABEL", "BUILD_HOST", "BUILD_USER", "BUILD_TIMESTAMP",
+        "FORMATTED_DATE",
+        // You can add more built-in variables or your own STABLE_ variables as needed
+    ];
+
+    // Set cargo:rustc-env for each variable that exists
+    for var in bazel_vars {
+        let value = std::env::var(var).unwrap_or_else(|_| "unknown".to_string());
+        println!("cargo:rustc-env={var}={value}");
+    }
+}

+ 231 - 0
crates/nockchain/src/colors.rs

@@ -0,0 +1,231 @@
+// use env_logger::fmt::Color as LogColor;
+// use std::collections::hash_map::DefaultHasher;
+// use std::hash::{Hash, Hasher};
+use std::io::Write;
+
+use termcolor::{ColorSpec, StandardStream, WriteColor};
+
+// pub fn log_format(
+//     record: &log::Record,
+//     buf: &mut env_logger::fmt::Formatter,
+// ) -> Result<(), std::io::Error> {
+//     use std::io::Write;
+//     let target = record.target();
+//     let target = target;
+//     let module = target.split("::").nth(1).unwrap_or(target);
+
+//     let (level_color, level_char) = crate::colors::level_colors(record.level());
+//     let mut module_style = buf.style();
+//     let module_color = module_color(module);
+//     module_style
+//         .set_color(module_color)
+//         .set_dimmed(true)
+//         .set_bold(true);
+
+//     let mut level_style = buf.style();
+//     level_style.set_color(level_color).set_intense(true);
+//     writeln!(
+//         buf,
+//         "[{}{}] {}",
+//         level_style.value(level_char),
+//         module_style.value(module),
+//         record.args()
+//     )
+// }
+
+// pub fn level_colors(level: log::Level) -> (LogColor, char) {
+//     match level {
+//         log::Level::Error => (LogColor::Red, '!'),
+//         log::Level::Warn => (LogColor::Yellow, '?'),
+//         log::Level::Info => (LogColor::Green, '^'),
+//         log::Level::Debug => (LogColor::Blue, '>'),
+//         log::Level::Trace => (LogColor::White, '<'),
+//     }
+// }
+
+// pub fn module_color(module: &str) -> LogColor {
+//     let mut hasher = DefaultHasher::new();
+//     module.hash(&mut hasher);
+//     let hash = hasher.finish();
+//     match hash % 6 {
+//         0 => LogColor::Rgb(128, 0, 0),     // Dark Red
+//         1 => LogColor::Rgb(255, 0, 0),     // Bright Red
+//         2 => LogColor::Rgb(255, 69, 0),    // Red-Orange
+//         3 => LogColor::Rgb(255, 165, 0),   // Orange
+//         4 => LogColor::Rgb(255, 215, 0),   // Gold
+//         5 => LogColor::Rgb(255, 255, 255), // White
+//         _ => unreachable!(),
+//     }
+// }
+
+pub(crate) fn print_banner(stdout: &mut StandardStream, banner: &str) {
+    let colors = [
+        (128, 0, 0),  // Dark Red
+        (255, 0, 0),  // Bright Red
+        (255, 69, 0), // Red-Orange
+    ];
+
+    let lines: Vec<&str> = banner.lines().collect();
+    let color_step = (colors.len() - 1) as f32 / (lines.len() - 1) as f32;
+
+    for (i, line) in lines.iter().enumerate() {
+        let color_index = (i as f32 * color_step) as usize;
+        let (r1, g1, b1) = colors[color_index];
+        let (r2, g2, b2) = colors[(color_index + 1).min(colors.len() - 1)];
+
+        for (j, c) in line.chars().enumerate() {
+            let t = j as f32 / line.len() as f32;
+            let r = (r1 as f32 * (1.0 - t) + r2 as f32 * t) as u8;
+            let g = (g1 as f32 * (1.0 - t) + g2 as f32 * t) as u8;
+            let b = (b1 as f32 * (1.0 - t) + b2 as f32 * t) as u8;
+
+            let mut color_spec = ColorSpec::new();
+            color_spec
+                .set_fg(Some(termcolor::Color::Rgb(r, g, b)))
+                .set_bold(true);
+            stdout.set_color(&color_spec).unwrap_or_else(|err| {
+                panic!(
+                    "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            });
+            write!(stdout, "{}", c).unwrap_or_else(|err| {
+                panic!(
+                    "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            });
+        }
+        writeln!(stdout).unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+    }
+    stdout.reset().unwrap_or_else(|err| {
+        panic!(
+            "Panicked with {err:?} at {}:{} (git sha: {:?})",
+            file!(),
+            line!(),
+            option_env!("GIT_SHA")
+        )
+    });
+}
+
+pub(crate) fn print_version_info(stdout: &mut StandardStream, info: &[(&str, &str)]) {
+    let mut color_spec = ColorSpec::new();
+    color_spec
+        .set_fg(Some(termcolor::Color::Rgb(255, 69, 0)))
+        .set_bold(true);
+    stdout.set_color(&color_spec).unwrap_or_else(|err| {
+        panic!(
+            "Panicked with {err:?} at {}:{} (git sha: {:?})",
+            file!(),
+            line!(),
+            option_env!("GIT_SHA")
+        )
+    });
+    writeln!(stdout, "Nockchain Version Info:").unwrap_or_else(|err| {
+        panic!(
+            "Panicked with {err:?} at {}:{} (git sha: {:?})",
+            file!(),
+            line!(),
+            option_env!("GIT_SHA")
+        )
+    });
+    stdout.reset().unwrap_or_else(|err| {
+        panic!(
+            "Panicked with {err:?} at {}:{} (git sha: {:?})",
+            file!(),
+            line!(),
+            option_env!("GIT_SHA")
+        )
+    });
+
+    for (i, (label, value)) in info.iter().enumerate() {
+        let t = i as f32 / (info.len() - 1) as f32;
+        let r = (128.0 * (1.0 - t) + 255.0 * t) as u8;
+        let g = (0.0 * (1.0 - t) + 69.0 * t) as u8;
+        let b = 0;
+
+        let mut label_spec = ColorSpec::new();
+        label_spec
+            .set_fg(Some(termcolor::Color::Rgb(r, g, b)))
+            .set_bold(true);
+        stdout.set_color(&label_spec).unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+        write!(stdout, "{}: ", label).unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+
+        let mut value_spec = ColorSpec::new();
+        value_spec
+            .set_fg(Some(termcolor::Color::Rgb(255, 255, 255)))
+            .set_bold(false);
+        stdout.set_color(&value_spec).unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+        writeln!(stdout, "{}", value).unwrap_or_else(|err| {
+            panic!(
+                "Panicked with {err:?} at {}:{} (git sha: {:?})",
+                file!(),
+                line!(),
+                option_env!("GIT_SHA")
+            )
+        });
+    }
+
+    let separator = "════════════════════════════════════════════════════════";
+    let mut separator_spec = ColorSpec::new();
+    separator_spec
+        .set_fg(Some(termcolor::Color::Rgb(255, 69, 0)))
+        .set_bold(true);
+    stdout.set_color(&separator_spec).unwrap_or_else(|err| {
+        panic!(
+            "Panicked with {err:?} at {}:{} (git sha: {:?})",
+            file!(),
+            line!(),
+            option_env!("GIT_SHA")
+        )
+    });
+    writeln!(stdout, "{}", separator).unwrap_or_else(|err| {
+        panic!(
+            "Panicked with {err:?} at {}:{} (git sha: {:?})",
+            file!(),
+            line!(),
+            option_env!("GIT_SHA")
+        )
+    });
+
+    stdout.reset().unwrap_or_else(|err| {
+        panic!(
+            "Panicked with {err:?} at {}:{} (git sha: {:?})",
+            file!(),
+            line!(),
+            option_env!("GIT_SHA")
+        )
+    });
+}

+ 635 - 0
crates/nockchain/src/lib.rs

@@ -0,0 +1,635 @@
+use std::error::Error;
+use std::fs;
+use std::path::Path;
+
+use clap::{arg, command, ArgAction, Parser};
+use crown::kernel::boot;
+use crown::nockapp::NockApp;
+use libp2p::identity::Keypair;
+use libp2p::multiaddr::Multiaddr;
+use libp2p::{allow_block_list, connection_limits, memory_connection_limits, PeerId};
+use nockchain_bitcoin_sync::{bitcoin_watcher_driver, BitcoinRPCConnection, GenesisNodeType};
+use nockchain_libp2p_io::p2p::{
+    MAX_ESTABLISHED_CONNECTIONS, MAX_ESTABLISHED_CONNECTIONS_PER_PEER,
+    MAX_ESTABLISHED_INCOMING_CONNECTIONS, MAX_ESTABLISHED_OUTGOING_CONNECTIONS,
+    MAX_PENDING_INCOMING_CONNECTIONS, MAX_PENDING_OUTGOING_CONNECTIONS,
+};
+use termcolor::{ColorChoice, StandardStream};
+use tokio::net::UnixListener;
+pub mod colors;
+use std::path::PathBuf;
+
+use clap::value_parser;
+use colors::*;
+use crown::noun::slab::NounSlab;
+use nockchain_libp2p_io::nc::MiningKeyConfig;
+use sword::jets::hot::HotEntry;
+use sword::noun::{D, T};
+use sword_macros::tas;
+use tracing::{debug, info, instrument};
+
+/// Module for handling driver initialization signals
+pub mod driver_init {
+    use crown::nockapp::driver::{make_driver, IODriverFn, PokeResult};
+    use crown::nockapp::wire::{SystemWire, Wire};
+    use crown::noun::slab::NounSlab;
+    use sword::noun::{D, T};
+    use sword_macros::tas;
+    use tokio::sync::oneshot;
+    use tracing::{debug, error, info};
+
+    /// A collection of initialization signals for drivers
+    #[derive(Default)]
+    pub struct DriverInitSignals {
+        /// Sender for the born signal
+        pub born_tx: Option<oneshot::Sender<()>>,
+        /// Receiver for the born signal
+        pub born_rx: Option<oneshot::Receiver<()>>,
+        /// Map of driver names to their initialization signal senders
+        pub driver_signals: std::collections::HashMap<String, oneshot::Receiver<()>>,
+    }
+
+    impl DriverInitSignals {
+        /// Create a new DriverInitSignals instance
+        pub fn new() -> Self {
+            let (born_tx, born_rx) = oneshot::channel();
+            Self {
+                born_tx: Some(born_tx),
+                born_rx: Some(born_rx),
+                driver_signals: std::collections::HashMap::new(),
+            }
+        }
+
+        /// Register a driver with an initialization signal
+        pub fn register_driver(&mut self, name: &str) -> oneshot::Sender<()> {
+            let (tx, rx) = oneshot::channel();
+            self.driver_signals.insert(name.to_string(), rx);
+            tx
+        }
+
+        /// Get the initialization signal sender for a driver
+        pub fn get_signal_sender(&self, name: &str) -> Option<&oneshot::Receiver<()>> {
+            self.driver_signals.get(name)
+        }
+
+        /// Create a task that waits for all registered drivers to initialize
+        pub fn create_born_task(&mut self) -> tokio::task::JoinHandle<()> {
+            let born_tx = self.born_tx.take().expect("Born signal already used");
+            let driver_signals = std::mem::take(&mut self.driver_signals);
+
+            tokio::spawn(async move {
+                // Wait for all registered drivers to initialize concurrently
+                let mut join_set = tokio::task::JoinSet::new();
+                for (name, rx) in driver_signals {
+                    let name = name.clone();
+                    join_set.spawn(async move {
+                        let _ = rx.await;
+                        info!("driver '{}' initialized", name);
+                    });
+                }
+
+                // Wait for all tasks to complete
+                while let Some(result) = join_set.join_next().await {
+                    result.expect("Task panicked");
+                }
+
+                // Send the born poke signal
+                let _ = born_tx.send(());
+                info!("all drivers initialized, born poke sent");
+            })
+        }
+
+        /// Create the born driver that waits for the born signal
+        pub fn create_born_driver(&mut self) -> IODriverFn {
+            let born_rx = self.born_rx.take().expect("born signal already used");
+
+            make_driver(move |handle| {
+                Box::pin(async move {
+                    // Wait for the born signal
+                    let _ = born_rx.await;
+
+                    // Send the born poke
+                    let mut born_slab = NounSlab::new();
+                    let born = T(
+                        &mut born_slab,
+                        &[D(tas!(b"command")), D(tas!(b"born")), D(0)],
+                    );
+                    born_slab.set_root(born);
+                    let wire = SystemWire.to_wire();
+                    let result = handle.poke(wire, born_slab).await?;
+
+                    match result {
+                        PokeResult::Ack => debug!("born poke acknowledged"),
+                        PokeResult::Nack => error!("Born poke nacked"),
+                    }
+
+                    Ok(())
+                })
+            })
+        }
+    }
+}
+
+// TODO: command-line/configure
+/** Path to read current node's identity from */
+pub const IDENTITY_PATH: &str = ".nockchain_identity";
+
+/** Path to read current node's peer ID from */
+pub const PEER_ID_EXTENSION: &str = ".peer_id";
+
+// TODO: command-line/configure
+/** Extension for peer ID files */
+const PEER_ID_FILE_EXTENSION: &str = "peerid";
+
+// Libp2p multiaddrs don't support const construction, so we have to put strings literals and parse them at startup
+/** Backbone nodes for our testnet */
+const TESTNET_BACKBONE_NODES: &[&str] = &[];
+
+// Libp2p multiaddrs don't support const construction, so we have to put strings literals and parse them at startup
+// TODO: feature flag testnet/realnet
+/** Backbone nodes for our realnet */
+#[allow(dead_code)]
+const REALNET_BACKBONE_NODES: &[&str] = &[];
+
+/** List of backbone nodes baked into the executable: we'll try to join these unconditionally */
+const BACKBONE_NODES: &[&str] = TESTNET_BACKBONE_NODES;
+
+/** How often we should affirmatively ask other nodes for their heaviest chain */
+const CHAIN_INTERVAL_SECS: u64 = 20;
+
+/// The height of the bitcoin block that we want to sync our genesis block to
+/// Currently, this is the height of an existing block for testing. It will be
+/// switched to a future block for launch.
+const GENESIS_HEIGHT: u64 = 892723;
+
+/// Command line arguments
+#[derive(Parser, Debug, Clone)]
+#[command(name = "nockchain")]
+pub struct NockchainCli {
+    #[command(flatten)]
+    pub crown_cli: crown::kernel::boot::Cli,
+    #[arg(
+        long,
+        help = "npc socket path",
+        default_value = ".socket/nockchain_npc.sock"
+    )]
+    pub npc_socket: String,
+    #[arg(long, help = "Mine in-kernel", default_value = "false")]
+    pub mine: bool,
+    #[arg(
+        long,
+        help = "Pubkey to mine to (mutually exclusive with --mining-key-adv)"
+    )]
+    pub mining_pubkey: Option<String>,
+    #[arg(
+        long,
+        help = "Advanced mining key configuration (mutually exclusive with --mining-pubkey). Format: share,m:key1,key2,key3",
+        value_parser = value_parser!(MiningKeyConfig),
+        num_args = 1..,
+        value_delimiter = ',',
+    )]
+    pub mining_key_adv: Option<Vec<MiningKeyConfig>>,
+    #[arg(long, help = "Watch for genesis block", default_value = "false")]
+    pub genesis_watcher: bool,
+    #[arg(long, help = "Mine genesis block", default_value = "false")]
+    pub genesis_leader: bool,
+    #[arg(long, help = "use fake genesis block", default_value = "false")]
+    pub fakenet: bool,
+    #[arg(long, help = "Genesis block message", default_value = "Hail Zorp")]
+    pub genesis_message: String,
+    #[arg(
+        long,
+        help = "URL for Bitcoin Core RPC",
+        default_value = "http://100.98.183.39:8332"
+    )]
+    pub btc_node_url: String,
+    #[arg(long, help = "Username for Bitcoin Core RPC")]
+    pub btc_username: Option<String>,
+    #[arg(long, help = "Password for Bitcoin Core RPC")]
+    pub btc_password: Option<String>,
+    #[arg(long, help = "Auth cookie path for Bitcoin Core RPC")]
+    pub btc_auth_cookie: Option<String>,
+    #[arg(long, short, help = "Initial peer", action = ArgAction::Append)]
+    pub peer: Vec<String>,
+    #[arg(long, help = "Allowed peer IDs file")]
+    pub allowed_peers_path: Option<String>,
+    #[arg(long, help = "Don't dial default peers")]
+    pub no_default_peers: bool,
+    #[arg(long, help = "Bind address", action = ArgAction::Append)]
+    pub bind: Vec<String>,
+    #[arg(
+        long,
+        help = "Generate a new peer ID, discarding the existing one",
+        default_value = "false"
+    )]
+    pub new_peer_id: bool,
+    #[arg(long, help = "Maximum established incoming connections")]
+    pub max_established_incoming: Option<u32>,
+    #[arg(long, help = "Maximum established outgoing connections")]
+    pub max_established_outgoing: Option<u32>,
+    #[arg(long, help = "Maximum pending incoming connections")]
+    pub max_pending_incoming: Option<u32>,
+    #[arg(long, help = "Maximum pending outgoing connections")]
+    pub max_pending_outgoing: Option<u32>,
+    #[arg(long, help = "Maximum established connections")]
+    pub max_established: Option<u32>,
+    #[arg(long, help = "Maximum established connections per peer")]
+    pub max_established_per_peer: Option<u32>,
+    #[arg(long, help = "Maximum system memory percentage for connection limits")]
+    pub max_system_memory_fraction: Option<f64>,
+    #[arg(long, help = "Maximum process memory for connection limits (bytes)")]
+    pub max_system_memory_bytes: Option<usize>,
+}
+
+impl NockchainCli {
+    pub fn validate(&self) -> Result<(), String> {
+        if self.mining_pubkey.is_some() && self.mining_key_adv.is_some() {
+            return Err(
+                "Cannot specify both mining_pubkey and mining_key_adv at the same time".to_string(),
+            );
+        }
+
+        if self.genesis_leader && self.genesis_watcher {
+            return Err(
+                "Cannot specify both genesis_leader and genesis_watcher at the same time"
+                    .to_string(),
+            );
+        }
+
+        if !self.fakenet && (self.genesis_watcher || self.genesis_leader) {
+            if self.btc_node_url.is_empty() {
+                return Err(
+                    "Must specify --btc-node-url when using genesis_watcher or genesis_leader"
+                        .to_string(),
+                );
+            }
+            if self.btc_auth_cookie.is_none() {
+                if self.btc_username.is_none() && self.btc_password.is_none() {
+                    return Err("Must specify either --btc-username or --btc-password when using genesis_watcher or genesis_leader on livenet".to_string());
+                }
+            }
+        }
+
+        Ok(())
+    }
+
+    /// Helper function to create a BitcoinRPCConnection from CLI arguments
+    fn create_bitcoin_connection(&self) -> BitcoinRPCConnection {
+        let url = self.btc_node_url.clone();
+        let height = GENESIS_HEIGHT;
+        let auth = if let Some(username) = self.btc_username.clone() {
+            let password = self.btc_password.clone().unwrap_or_else(|| {
+                panic!(
+                    "Panicked at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            });
+            bitcoincore_rpc::Auth::UserPass(username, password)
+        } else {
+            let cookie_path_str = self.btc_auth_cookie.clone().unwrap_or_else(|| {
+                panic!(
+                    "Panicked at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            });
+            let cookie_path = PathBuf::from(cookie_path_str);
+            bitcoincore_rpc::Auth::CookieFile(cookie_path)
+        };
+        BitcoinRPCConnection::new(url, auth, height)
+    }
+}
+
+/// # Load a keypair from a file or create a new one if the file doesn't exist
+///
+/// This function attempts to read a keypair from a specified file. If the file exists, it reads the keypair from the file.
+/// If the file does not exist, it generates a new keypair, writes it to the file, and returns it.
+///
+/// # Arguments
+/// * `keypair_path` - A reference to a Path object representing the file path where the keypair should be stored
+/// * `force_new` - If true, generate a new keypair even if one already exists
+///
+/// # Returns
+/// A Result containing the Keypair or an error if any operation fails
+pub fn gen_keypair(keypair_path: &Path) -> Result<Keypair, Box<dyn Error>> {
+    let new_keypair = libp2p::identity::Keypair::generate_ed25519();
+    let new_keypair_bytes = new_keypair.to_protobuf_encoding()?;
+    std::fs::write(keypair_path, new_keypair_bytes)?;
+    let peer_id = new_keypair.public().to_peer_id();
+    // write the peer_id encoded as base58 to a file
+    std::fs::write(
+        keypair_path.with_extension(PEER_ID_FILE_EXTENSION),
+        peer_id.to_base58(),
+    )?;
+    info!("Generated new identity as peer {peer_id}");
+    Ok(new_keypair)
+}
+
+fn load_keypair(keypair_path: &Path, force_new: bool) -> Result<Keypair, Box<dyn Error>> {
+    if keypair_path.try_exists()? && !force_new {
+        let keypair_bytes = std::fs::read(keypair_path)?;
+        let keypair = libp2p::identity::Keypair::from_protobuf_encoding(&keypair_bytes[..])?;
+        let peer_id = keypair.public().to_peer_id();
+        info!("Loaded identity as peer {peer_id}");
+        Ok(keypair)
+    } else {
+        if force_new && keypair_path.try_exists()? {
+            info!("Discarding existing peer ID and generating a new one");
+            std::fs::remove_file(keypair_path)?;
+        }
+        gen_keypair(keypair_path)
+    }
+}
+
+#[instrument(skip(kernel_jam, hot_state))]
+pub async fn init_with_kernel(
+    cli: Option<NockchainCli>,
+    kernel_jam: &[u8],
+    hot_state: &[HotEntry],
+) -> Result<NockApp, Box<dyn Error>> {
+    welcome();
+
+    if let Some(cli) = &cli {
+        cli.validate()?;
+    }
+
+    let mut nockapp = boot::setup(
+        kernel_jam,
+        cli.as_ref().map(|c| c.crown_cli.clone()),
+        hot_state,
+        "nockchain",
+        None,
+    )
+    .await?;
+
+    let keypair = {
+        let keypair_path = Path::new(IDENTITY_PATH);
+        load_keypair(
+            keypair_path,
+            cli.as_ref().map(|c| c.new_peer_id).unwrap_or(false),
+        )?
+    };
+    eprintln!(
+        "allowed_peers_path: {:?}",
+        cli.as_ref().unwrap().allowed_peers_path
+    );
+    let allowed = cli.as_ref().and_then(|c| {
+        c.allowed_peers_path.as_ref().map(|path| {
+            let contents = fs::read_to_string(path).expect("failed to read allowed peers file: {}");
+            let peer_ids: Vec<PeerId> = contents
+                .lines()
+                .map(|line| {
+                    let peer_id_bytes = bs58::decode(line)
+                        .into_vec()
+                        .expect("failed to decode peer ID bytes from base58");
+                    PeerId::from_bytes(&peer_id_bytes).expect("failed to decode peer ID from bytes")
+                })
+                .collect();
+            let mut allow_behavior =
+                allow_block_list::Behaviour::<allow_block_list::AllowedPeers>::default();
+            for peer_id in peer_ids {
+                allow_behavior.allow_peer(peer_id);
+            }
+            allow_behavior
+        })
+    });
+
+    let bind_multiaddrs = cli
+        .as_ref()
+        .map_or(vec!["/ip4/0.0.0.0/udp/0/quic-v1".parse()?], |c| {
+            c.bind
+                .clone()
+                .into_iter()
+                .map(|addr_str| addr_str.parse().expect("could not parse bind multiaddr"))
+                .collect()
+        });
+
+    let limits = connection_limits::ConnectionLimits::default()
+        .with_max_established_incoming(
+            cli.as_ref()
+                .and_then(|c| c.max_established_incoming)
+                .and(Some(MAX_ESTABLISHED_INCOMING_CONNECTIONS)),
+        )
+        .with_max_established_outgoing(
+            cli.as_ref()
+                .and_then(|c| c.max_established_outgoing)
+                .and(Some(MAX_ESTABLISHED_OUTGOING_CONNECTIONS)),
+        )
+        .with_max_pending_incoming(
+            cli.as_ref()
+                .and_then(|c| c.max_pending_incoming)
+                .and(Some(MAX_PENDING_INCOMING_CONNECTIONS)),
+        )
+        .with_max_pending_outgoing(
+            cli.as_ref()
+                .and_then(|c| c.max_pending_outgoing)
+                .and(Some(MAX_PENDING_OUTGOING_CONNECTIONS)),
+        )
+        .with_max_established(
+            cli.as_ref()
+                .and_then(|c| c.max_established)
+                .and(Some(MAX_ESTABLISHED_CONNECTIONS)),
+        )
+        .with_max_established_per_peer(
+            cli.as_ref()
+                .and_then(|c| c.max_established_per_peer)
+                .and(Some(MAX_ESTABLISHED_CONNECTIONS_PER_PEER)),
+        );
+    let memory_limits = cli.as_ref().and_then(|c| {
+        if c.max_system_memory_bytes.is_some() && c.max_system_memory_fraction.is_some() { panic!( "Must provide neither or one of --max-system-memory_bytes or --max-system-memory_percentage" )};
+        if let Some(max_bytes) = c.max_system_memory_bytes {
+            Some(memory_connection_limits::Behaviour::with_max_bytes(max_bytes))
+        } else { c.max_system_memory_fraction.map(memory_connection_limits::Behaviour::with_max_percentage) }
+    });
+    // TODO parse CLI
+    let mut swarm = nockchain_libp2p_io::p2p::start_swarm(
+        keypair, bind_multiaddrs, allowed, limits, memory_limits,
+    )?;
+
+    let backbone_peers = BACKBONE_NODES
+        .iter()
+        .map(|multiaddr_str| {
+            multiaddr_str
+                .parse()
+                .expect("could not parse multiaddr from built-in string")
+        })
+        .collect();
+
+    // Set up initial peer addresses to connect to
+    let mut peer_multiaddrs: Vec<Multiaddr> = if cli.as_ref().is_some_and(|c| c.no_default_peers) {
+        Vec::new()
+    } else {
+        backbone_peers
+    };
+
+    if let Some(c) = cli.as_ref() {
+        let v: Vec<Multiaddr> = c
+            .peer
+            .clone()
+            .into_iter()
+            .map(|multiaddr_str| {
+                multiaddr_str
+                    .parse()
+                    .expect("could not parse multiaddr from string")
+            })
+            .collect();
+        peer_multiaddrs.extend(v);
+    }
+
+    debug!("peer_multiaddrs: {:?}", peer_multiaddrs);
+
+    // Dial initial peers
+    for remote in peer_multiaddrs {
+        swarm.dial(remote.clone())?;
+        info!("Dialed initial peer {remote}");
+    }
+
+    let equix_builder = equix::EquiXBuilder::new();
+
+    // Create driver initialization signals. the idea here is that we want to wait for
+    // drivers that emit init pokes to complete before we send the born poke.
+    let mut driver_signals = driver_init::DriverInitSignals::new();
+
+    // Register drivers that need initialization signals
+    let libp2p_init_tx = driver_signals.register_driver("libp2p");
+    let watcher_init_tx = driver_signals.register_driver("bitcoin_watcher");
+
+    // Create the born task that waits for all drivers to initialize
+    let _born_task = driver_signals.create_born_task();
+
+    if cli.as_ref().map(|c| c.fakenet).unwrap_or(false) {
+        let message = cli
+            .as_ref()
+            .map(|c| c.genesis_message.clone())
+            .unwrap_or("".to_string());
+        let node_type = if cli.as_ref().map(|c| c.genesis_leader).unwrap_or(false) {
+            GenesisNodeType::Leader
+        } else {
+            GenesisNodeType::Watcher
+        };
+        let watcher_driver =
+            bitcoin_watcher_driver(None, node_type, message, Some(watcher_init_tx));
+        nockapp.add_io_driver(watcher_driver).await;
+    } else if cli
+        .as_ref()
+        .map(|c| c.genesis_watcher || c.genesis_leader)
+        .unwrap_or(false)
+    {
+        let message = cli
+            .as_ref()
+            .map(|c| c.genesis_message.clone())
+            .unwrap_or("".to_string());
+        let connection = cli.as_ref().unwrap().create_bitcoin_connection();
+        let node_type = if cli.as_ref().map(|c| c.genesis_leader).unwrap_or(false) {
+            GenesisNodeType::Leader
+        } else {
+            GenesisNodeType::Watcher
+        };
+        let watcher_driver =
+            bitcoin_watcher_driver(Some(connection), node_type, message, Some(watcher_init_tx));
+        nockapp.add_io_driver(watcher_driver).await;
+    }
+
+    let libp2p_driver = nockchain_libp2p_io::nc::make_libp2p_driver(
+        swarm,
+        equix_builder,
+        cli.as_ref().and_then(|c| {
+            if let Some(pubkey) = &c.mining_pubkey {
+                Some(vec![nockchain_libp2p_io::nc::MiningKeyConfig {
+                    share: 1,
+                    m: 1,
+                    keys: vec![pubkey.clone()],
+                }])
+            } else {
+                c.mining_key_adv.clone()
+            }
+        }),
+        Some(libp2p_init_tx),
+    );
+    nockapp.add_io_driver(libp2p_driver).await;
+
+    // Create the born driver that waits for the born signal
+    let born_driver = driver_signals.create_born_driver();
+
+    // Add the born driver to the nockapp
+    nockapp.add_io_driver(born_driver).await;
+
+    // set up socket
+    let socket_path = Path::new(
+        &cli.as_ref()
+            .unwrap_or_else(|| {
+                panic!(
+                    "Panicked at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            })
+            .npc_socket,
+    );
+    nockapp.npc_socket_path = Some(socket_path.to_path_buf());
+
+    if let Some(parent) = socket_path.parent() {
+        fs::create_dir_all(parent)?;
+    }
+    let listener = UnixListener::bind(socket_path)?;
+
+    nockapp
+        .add_io_driver(crown::npc_listener_driver(listener))
+        .await;
+
+    // set up timer
+    let mut timer_slab = NounSlab::new();
+    let timer_noun = T(
+        &mut timer_slab,
+        &[D(tas!(b"command")), D(tas!(b"timer")), D(0)],
+    );
+    timer_slab.set_root(timer_noun);
+    nockapp
+        .add_io_driver(crown::timer_driver(CHAIN_INTERVAL_SECS, timer_slab))
+        .await;
+
+    Ok(nockapp)
+}
+
+fn welcome() {
+    let mut stdout = StandardStream::stdout(ColorChoice::Auto);
+
+    let banner = "
+    _   _            _        _           _
+   | \\ | | ___   ___| | _____| |__   __ _(_)_ __
+   |  \\| |/ _ \\ / __| |/ / __| '_ \\ / _` | | '_ \\
+   | |\\  | (_) | (__|   < (__| | | | (_| | | | | |
+   |_| \\_|\\___/ \\___|_|\\_\\___|_| |_|\\__,_|_|_| |_|
+   ";
+
+    print_banner(&mut stdout, banner);
+
+    let info = [
+        ("Build label", env!("BUILD_EMBED_LABEL")),
+        ("Build host", env!("BUILD_HOST")),
+        ("Build user", env!("BUILD_USER")),
+        ("Build timestamp", env!("BUILD_TIMESTAMP")),
+        ("Build date", env!("FORMATTED_DATE")),
+        // ("Git commit", env!("BAZEL_GIT_COMMIT")),
+        // ("Build timestamp", env!("VERGEN_BUILD_TIMESTAMP")),
+        // ("Cargo debug", env!("VERGEN_CARGO_DEBUG")),
+        // ("Cargo features", env!("VERGEN_CARGO_FEATURES")),
+        // ("Cargo opt level", env!("VERGEN_CARGO_OPT_LEVEL")),
+        // ("Cargo target", env!("VERGEN_CARGO_TARGET_TRIPLE")),
+        // ("Git branch", env!("VERGEN_GIT_BRANCH")),
+        // ("Git commit date", env!("VERGEN_GIT_COMMIT_DATE")),
+        // ("Git commit author", env!("VERGEN_GIT_COMMIT_AUTHOR_NAME")),
+        // ("Git commit message", env!("VERGEN_GIT_COMMIT_MESSAGE")),
+        // ("Git commit timestamp", env!("VERGEN_GIT_COMMIT_TIMESTAMP")),
+        // ("Git commit SHA", env!("VERGEN_GIT_SHA")),
+        // ("Rustc channel", env!("VERGEN_RUSTC_CHANNEL")),
+        // ("Rustc host", env!("VERGEN_RUSTC_HOST_TRIPLE")),
+        // ("Rustc LLVM version", env!("VERGEN_RUSTC_LLVM_VERSION")),
+    ];
+
+    print_version_info(&mut stdout, &info);
+}

+ 19 - 0
crates/nockchain/src/main.rs

@@ -0,0 +1,19 @@
+use std::error::Error;
+
+use clap::Parser;
+use crown::kernel::boot;
+use kernels::dumb::KERNEL;
+use zkvm_jetpack::hot::produce_prover_hot_state;
+
+#[tokio::main]
+async fn main() -> Result<(), Box<dyn Error>> {
+    sword::check_endian();
+    let cli = nockchain::NockchainCli::parse();
+    boot::init_default_tracing(&cli.crown_cli);
+
+    let prover_hot_state = produce_prover_hot_state();
+    let nockchain =
+        nockchain::init_with_kernel(Some(cli), KERNEL, prover_hot_state.as_slice()).await?;
+    nockchain.run().await?;
+    Ok(())
+}

+ 71 - 0
crates/sword/DEVELOPERS.md

@@ -0,0 +1,71 @@
+# Developing Sword
+
+## Rust
+
+### Build
+
+To build Sword, make sure Rust is installed, then run:
+
+```bash
+cargo build
+```
+
+to build the Sword executable. This will place the built executable at `target/debug/sword` under the `rust/sword` directory.
+
+#### Pills
+
+Sword development and testing, unlike regular development and ship operation, currently requires careful control over what pill is used to launch a ship. Currently, there are several pills available in `resources/pills/`:
+- **baby.pill**: an extremely minimal Arvo-shaped core and Hoon standard library (`~wicdev-wisryt` [streamed a
+video of its development](https://youtu.be/fOVhCx1a-9A))
+- **toddler.pill**: a slightly more complex Arvo and Hoon than `baby`, which runs slow recursive operations for testing jets
+- **azimuth.pill**: a pill that processes an Azimuth snapshot
+- **full.pill**: the complete Urbit `v2.11` pill
+- **slim.pill**: a slimmed down version of the Urbit `v2.11` pill that has had every desk and agent not necessary for booting to dojo removed
+
+More information on the pills used by Sword can be found [here](https://github.com/zorp-corp/sword/blob/status/docs/pills.md).
+
+### Test
+
+The command to run the Sword suite of unit tests is:
+
+```bash
+cargo test --verbose -- --test-threads=1
+```
+
+The tests must be run with `-- --test-threads=1` because Rust does not have any way to specify test setup / teardown functions, nor does it have any way to
+specify ordered test dependencies. Therefore, the only way to ensure that tests that share resources don't clobber each other **and** that tests setup / teardown in the right order is to force all unit tests to be single-threaded.
+
+### Style
+
+Sword uses the default Rust formatting and style. The CI jobs are configured to reject any code which produces linter or style warnings. Therefore, as a final step before uploading code changes to GitHub, it's recommended to run the following commands:
+
+```bash
+cargo fmt
+cargo clippy --all-targets --no-deps -- -D warnings -A clippy::missing_safety_doc
+```
+
+This will auto-format your code and check for linter warnings.
+
+### Watch
+
+To watch rust and check for errors, run
+
+```bash
+cargo watch --clear
+```
+
+Until terminated with ctrl-c, this will rebuild Sword library on any change to the underlying source files and report any warnings and errors. It will *not* produce the executable. You must run the build command above to rebuild the executable.
+
+## Hoon
+
+The Nock analysis and lowering for Sword is written in Hoon, and lives at `hoon/codegen.` It is meant to be jammed and included in the Sword binary. (See [`src/load.rs`](rust/sword/src/load.rs) in the Rust sources for details.)
+
+If the hoon source has been synced to a desk, e.g. `sandbox`, on a fakezod, then the build generator can be invoked as:
+
+```
+.cg/jam +sandbox!cg-make
+```
+
+This will build the Hoon standard library and the Sword Nock analysis as a "trap" meant to be run by Sword. The jammed output can be found at `<fakezod-pier>/.urb/put/cg.jam`, and should be copied to the `rust/sword/bin` directory, from whence the rust build will include it in the executable.
+
+Instructions on testing the analysis in a fakezod are forthcoming.

+ 21 - 0
crates/sword/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 Zorp Corp
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 7 - 0
crates/sword/README.md

@@ -0,0 +1,7 @@
+# Sword
+
+A modern Nock runtime with automatic persistence.
+
+## Installation
+
+See the "Run" section of [DEVELOPERS.md](DEVELOPERS.md#run).

+ 46 - 0
crates/sword/docs/b-trees.md

@@ -0,0 +1,46 @@
+# B+ Tree page directory for New Mars Persistent Memory Arena
+
+## Problem
+
+We need to be able to dynamically allocate intervals of memory which will be consistently and durably persisted to disk. This requires mapping memory pages to regions of a disk-backed file. Modifying a page requires that the data first be *copied* to a new region so that the original data is not corrupted if the process exits with the new data in an inconsistent state or not fully persisted to disk.
+
+This requires a durable mapping of virtual memory page addresses to offsets in the backing file. One solution is to store a metadata table indexed by the base pointer of the new table. This is simple to implement but has some downsides: it must itself be durable, so there is some complexity in ensuring the information for later pages of the table are stored in earlier pages. The table must grow to accomodate the highest virtual memory address used, and cannot efficiently encode contiguous multi-page mappings.
+
+Further, it is unclear whether mainstream OS kernels can handle the sheer quantity of mappings necessary to keep the entire persistent memory arena always mapped. It may be necessary to limit the number of mappings, using a marking eviction strategy to unmap infrequently-used mappings, and mapping pages on demand by reference to the index. In such a case, we would want to take advantage of contiguous mappings, which are likely to contain related nouns, and map them as one mapping. 
+
+The alternative is to use a B+ tree, *indexed by page base pointers*, and storing disk offsets as values. With such a structure, the page directory is stored in the arena alongside data. There is no need to separately implement dirtying and synchronization for the page directory, it is done by the same implementation as dirtying and synchronizing data. B+ trees naturally encode intervals of keys, which provides a means to store contiguous mappings.
+
+Only tree nodes relative to a traversal must be mapped. This provides an efficient way to handle on-demand mapping of page regions. Since the structure naturally stores contiguous page mappings, such mappings can be supplied to `mmap()`, further reducing pressure on the virtual memory system.
+
+The downside of this approach is added complexity of the datastructure, and a small asymptotic penalty (constants become logarithmic dependencies) in most operations.
+
+## Implementation details
+
+The page directory maps pages of virtual memory (identified by their base pointers) to page-sized intervals (hereafter PSIs) in a file. It must be persisted in the file itself for durability of the data. Corruption of the page directory renders the data unrecoverable, and so it is subject to the same durability constraints as the actual data.
+
+Nodes of a B+ tree contain an array of N entries of values in the keyspace, and N+1 leaves or references to child nodes. 
+
+A reference to a child page must contain an offset into the file where the page is stored. Modern processors and operating systems allow, in practice, 48-bit addresses, though pointers are represented as 64-bit integers. Pages are generally 4 kilobytes or 2^12 bytes in size. So a byte-addressed offset (pointer or file offset) truncated to address the base of a page is 48-12=36 bits, or 4.5 bytes. We expand this to 5 bytes for ease of addressing and to use the extra bits as flags. Thus, a 4KB page can store 409 indices and 410 child references. This requires a maximum tree depth of 6 to store entries for every page in an approximately 85TB arena. Each leaf page entry occupies 5 bytes, for the disk offset. Node pages are mapped into the memory at the start of the interval they represent, thus the leftmost interval is advanced by 1 page for the leftmost child of a node.
+
+There are of course internal pages, but since each page shares its parent pointers with 455 others the amortized contribution is negligible. So the overhead of the page table is a fraction of a percent of the total storage space of the arena.
+
+Since a B+ tree naturally stores *intervals*, a leaf with a file offset denotes a run of pages contiguous in both virtual memory and on-disk storage. An unmapped interval is denoted by a 0 file offset, since the first two PSIs in the file are the double-buffered entry point into the file.
+
+
+Modifying a page requires that the page first be copied to a new disk location (dirtied) as would be done for a copy-on-write of a data page. This will involve altering its parent node, which will need to be dirtied, and so on up to a node that is already dirty, or to the root, which is copied over whichever of the first two PSIs on disk is not the most current.
+
+When syncing a snapshot to disk, the B+ tree is traversed (and a stack maintained), descending only into nodes for which the dirty bit is set. Dirty pages are `msync()`ed first and the dirty bits of their table entries cleared on a successful synchronization.
+
+Once dirty pages referenced by a node are synced, the node itself can be synced, and so on back up to the root node. Once the root node itself is successfully synced the snapshot can be considered durable.
+
+## Allocation
+
+Nouns which are to be stored in the PMA come from the 2stackz arena. The copier first scans the noun to determine the memory size necessary in the PMA, and then calls `pma_malloc()` with this size. The noun is then recursively copied into the allocated buffer.
+
+Allocation must find both a suitably sized region of virtual memory, and a suitably sized free region in the backing block store. The allocator should maintain an ephemeral cache of free space on the disk, which can be inferred on process start from the on-disk B+ tree. If no suitable region is available, then the file is expanded. Freeing regions at the end of the file should contract the file.
+
+**TODO** description of how allocation works, in particular less-than-page and multipage allocations.
+
+## Collection
+
+A collection of roots are stored in the PMA's metadata. Before snapshotting, these roots are supplied to a mark-and-sweep garbage collector, which frees all allocations not marked in a trace from the root. Freed pages have their associated PSIs on disk returned to the free set of PSIs.

+ 76 - 0
crates/sword/docs/codegen-bootstrap.md

@@ -0,0 +1,76 @@
+# New Mars: Codegen Bootstrapping
+
+New Mars provides an implementation of generating low-level, linear bytecode or machine code from Nock. This strategy is specified in Hoon, which is compiled to Nock. Thus, in order to generate low level code for Nock, we must execute Nock.
+
+Further, we should have a way of upgrading the Nock code for code generation without baking a machine code, bytecode, or Nock blob into the New Mars runtime.
+
+The proposed solution is to have the runtime provide a tree-walking Nock interpreter which can be used to run the Nock codegen code against itself, receiving a linearized version from which bytecode or machine code can be generated. This bytecode or machine code is then used to generate low level code for all other executed Nock, with the possible exception of interactively-proposed Nock (i.e. from the Dojo), which may still be executed by the tree-walking interpreter.
+
+The runtime will ship with Nock code for code generation. Upgrades to this Nock code can be distributed two ways: along with upgrades to the runtime, and by a scry path from Arvo. If Arvo binds the next version of this designated scry path, the result is interpreted as an upgraded Nock formula for code generation.
+
+## Bytecode
+
+The quickest path to making use of linearization is a bytecode. The current vere already uses a bytecode, but instruction execution is not the current performance bottleneck.
+
+The easiest approach to a bytecode is to make a compact and rapidly dispatchable encoding of the Nock linearizer output, which must encode the following instructions:
+
+| Instruction | Description                          | Op | Hex |
+|-------------|--------------------------------------|----|-----|
+| `imm`       | Write an immediate noun to an SSA    |  1 |   1 |
+| `mov`       | Copy one SSA to another              |  2 |   2 |
+| `inc`       | Checked increment                    |  3 |   3 |
+| `unc`       | Unchecked increment                  |  4 |   4 |
+| `con`       | Create a cell                        |  5 |   5 |
+| `hed`       | Checked head of a cell               |  6 |   6 |
+| `tal`       | Checked tail of a cell               |  7 |   7 |
+| `hud`       | Unchecked head of a cell             |  8 |   8 |
+| `tul`       | Unchecked tail of a cell             |  9 |   9 |
+| `clq`       | Branch on whether a noun is a cell   | 10 |   A |
+| `eqq`       | Branch on whether nouns are equal    | 11 |   B |
+| `brn`       | Branch on a loobean                  | 12 |   C |
+| `hop`       | Direct jump internally in an arm     | 13 |   D |
+| `cal`       | Call an arm in non-tail position     | 14 |   E |
+| `lnk`       | Evaluate an arm in non-tail position | 15 |   F |
+| `jmp`       | Call an arm in tail position         | 16 |  10 |
+| `lnt`       | Evaluate an arm in tail position     | 17 |  11 |
+| `spy`       | Execute a scry                       | 18 |  12 |
+| `hnt`       | Provide a hint                       | 19 |  13 |
+| `bom`       | Crash                                | 20 |  14 |
+
+Thus we need 5 bits to encode instructions. The linearized nock IR is registerized in SSA form, meaning we need to either maintain a dynamic mapping of SSA registers to values or perform register allocation. To avoid having to encode arbitrary-length integers for registers, we choose the latter option.
+
+Our bytecode VM will have 8 general-purpose registers, `r0` through `r7`.
+When a value must be in a single, conventional register (i.e. for returns or for the subject for dynamic evaluation), that register is `r0`. Registers are encoded with 3 bits in an instruction. A register's value holds a noun. Reading from an uninitialized register results in undefined behavior. It is therefore permissible for debugging purposes, but not mandatory, to crash on a read from an uninitialized register. 
+
+Register allocation is done using a Reverse Linear Scan algorithm. Register allocation begins at exit points of the functions. SSAs used by `don` and `lnt` are assigned to `r0` (and `r1` for the formula of LNT). A map of SSAs to register assignments is maintained, and entries are copied to implement and eliminate `mov` instructions. A stack of live registers, annotated with their SSAs, is maintained as well. When an SSA is defined, the corresponding register is removed from the stack. A stack of dead (unused) registers is also maintained, so that the most recently unused register is the one first selected for reallocation. In this way, terminal operations on an SSA will immediately re-use the VM register for their result.
+
+When there are no available dead registers, then we must contract our live register set. This is done by "spilling" a currently live register to the stack.
+We extend the SSA->register map to allow SSAs to be mapped to stack slots, and add stacks of live and dead stack slots. When there are no dead registers which may be allocated to an SSA at its use point, we select the register furthest down the live register stack, and either the stack slot at the top of the dead stack slot stack, or a fresh stack slot if all stack slots are live (or if we have not yet allocated any). We introduce a load from the selected slot to the selected register following the instruction which we are allocating for. We then update the mapping to point the corresponding SSA, no longer at the register, but at the stack slot.
+
+If we encounter a use of an SSA mapped to a stack slot, we must allocate a register at that point for that SSA and introduce a load instruction. This may entail spilling a later-used register.
+
+When we discover the definition of an SSA mapped to a stack slot, we allocate a register (which, again, may entail spilling) and introduce a `sto` operation of the SSA to that stack slot. 
+
+Internal labels are encoded as flag bits, and then 13 or 48 bit integers representing byte offsets from the end of the instruction invoking the label. If the first bit of an offset is 0, then the offset represents a fall-through to the next instruction, useful for compact encoding of conditionals. If the first bit is 1, then the next bit selects either 13 or 48 bits for the label size.
+
+External labels for arms are encoded as 45 bit integers, which should be shifted by 3 to the left and become pointers to bytecode buffers. **This requires the code table to guarantee pointer stability of bytecode buffers for arms.** Such pointers are looked up and compressed to 45 bits at bytecode generation time. **TODO** recursive arms.
+
+Calls require a list of one to seven registers. (Calls which require 0 should be replaced by an immediate instruction.) To simplify the encoding, registers from `r0` to `r6` are used in order, and `mov` instructions are inserted to match parameters to the register allocator's assignments for the initial SSAs. If a call takes more than 8 parameters, the eighth and later parameters are consed into a list and passed in `r7`.   
+
+Encoding of the begins with the 5 bit opcode, followed by the instruction's parameters encoded as described above and concatenated. The new 
+
+## Codegen todos:
+
+### Optimization: Shift some defines adjacent to uses
+
+Register pressure will be reduced WLOP if we can shift memory load instructions to as near the uses of the loaded values as possible. In particular, it does no good if we load the head or tail of a cell, only to spill it to the stack later. 
+A single use of a loaded value should always be immediately preceded by the load instruction. Multiple uses (perhaps unified by `mov`), if a spill does intervene, should replace the spill by repeated loads from the original memory location, to avoid an unnecessary memory write to the stack. See "Definition copying"
+
+This also applies to immediates. Immediates should be shifted to immediately proceed their first use. An immediate instruction of an arbitrary noun carries a reference to the noun's fixed location in (virtual) memory, and so it is no extra cost to duplicate the immediate instruction instead of spilling it.
+
+The `mov` instruction should also be shifted immediately prior to its first use. This will also ensure that it gets out of the way of the shift above.
+
+### Optimization: Definition copying
+
+If a spilled variable is a memory load or immediate, then a load instruction should not be written for the spill. Instead, the defining instruction should be copied to the locations where the spill would have been loaded from the stack slot. This would most easily be implemented by notating such SSAs with their defining instruction. For memory load instructions, this is only practical if we are still within the liverange of the register for the cell targeted by the load. Otherwise, we must allocate a register at precisely the point where we need to spill because of register pressure. Other encountered uses of the SSA, preceding the spill, may be able to extend the liverange of the cell if there are available dead registers at that point.
+

+ 62 - 0
crates/sword/docs/heap.md

@@ -0,0 +1,62 @@
+# Heaps and persistence
+
+However, there are several reasons why we may wish to copy a noun off the stack and reference it during a computation.
+Copying a large noun of the stack and maintaining a reference instead prevents repeated copying up the stack.
+Fixing a location for a noun enables pointer equality to short-circuit equality checks.
+Snapshotting and paging requires a fixed location in memory for a noun, unperturbed by computation.
+Finally, the ability to attach metadata (hints, compiled code, reference counts, etc) to nouns is extremely helpful for runtime implementation, but costly if provided for every noun.
+Instead, we can store nouns in a coarsely-allocated heap which attaches metadata to the allocation entry for each noun, rather than for each cell.
+
+## Memory arenas
+### Large objects
+To prevent repeated copying of large objects, a temporary older-generation heap may suffice.
+This is a heap used alongside a Nock stack (see [stack.md](stack.md)) to which a noun may be copied instead of copying it up to the calling frame.
+
+Each allocation entry records the location of the most senior pointer to the entry, and if that frame is popped or object freed without a copy of the pointer, the object can be freed.
+Thus, we can use a traditional alloc/free approach with a free list for allocations, while not losing automatic and predictable memory management.
+
+#### Aside: unifying equality
+The current vere (u3) implements unifying equality, meaning that when two nouns are discovered to be equal, a reference to one is replaced with a reference to the other.
+This is not required by the Nock specification, which demands only structural equality, but is an obvious and nearly costless optimization to make to structural equality.
+
+However, we must be careful not to undo invariants that our memory system depends on. For the stack, this means that when replacing a reference to a noun with a structurally equal one,
+we must take care to replace the reference to more junior (more recently pushed) stack frames with references to more senior stack frames. We must also take care not to introduce
+references from any heap back to the stack, thus any heap must be treated as more senior (and thus chosen for unifying equality) than any stack frame.
+
+### Persistence and paging
+The canonical persistence layer for any Urbit ship is the event log.
+However, exclusive reliance on the event log requires us to permit the event log to grow without bound and to replay a ship's entire history every time it is restarted.
+Fast restarts and event log pruning both rely on snapshots, which are persistent copies of the current Arvo state.
+
+Further, all data stored on an Urbit ship and accessible to it is present as nouns in the Arvo state.
+Unless we wish to limit Urbit forever to persisting only as much data as can reasonably be stored in RAM, we need some mechanism for storing large parts of the Arvo state on disk, and loading them into memory only when needed.
+
+Nouns which need to be persisted, or which require metadata, are "canonicalized."
+That is, they are assigned a memory address which is permanent for the lifetime of the noun.
+Once this is done, the noun can be persisted on disk, and the physical memory freed for other uses.
+The *virtual memory* space remains reserved for the noun at least as long as the noun is still reachable from the set of roots (see below).
+
+The persistent heap maintains a queue of which persisted nouns were recently touched, and evicts the pages for least recently used nouns (likely using `madvise()` with the `MADV_DONTNEED` advice on Linux) when the resident set size is too large.
+User-space page fault handling (using `userfaultfd` on Linux or equivalent page fault trapping on other systems) is used to detect subsequent reads in a noun's virtual memory space and page the noun back in.
+
+The persistent heap also maintains a metadata record adjacent to each noun, which allows for:
+- Reference counting, to ensure we can predictably drop nouns which are no longer referenced by roots.
+- Code caching, for nouns representing Nock formulas
+- Any other data helpful to the implementation.
+
+## Snapshotting
+Snapshotting makes use of the current arena, storing a root for each snapshot. The snapshotting process ensures all nouns reachable from the snapshot synced to disk.
+
+## Event logging
+TODO: Can we also use persistent noun storage for the event log?
+
+(TODO: strategy for maintaining use times which doesn't require faulting on every access)
+
+## Roots
+### Always
+- Any undiscarded snapshot
+- Any unpruned event log entry (if using noun persistence for event loggin)
+
+### During running computations
+- Any result or subject register
+- Any slot in a Nock stack

+ 191 - 0
crates/sword/docs/llvm.md

@@ -0,0 +1,191 @@
+# Compiling Nock to LLVM
+
+Nock formulae are nouns, which are binary trees whose leaves are natural numbers.
+The LLVM IR is a mostly sequential language comprised of operations arranged into basic blocks and functions.
+
+In order to generate LLVM IR which executes a Nock formula, we can conceptually lower Nock to NockIR.
+NockIR is a mostly-sequential language which contains a set of instructions optimized as a target for Nock.
+It abstracts memory management and is intended to run with the stack allocator described in [memory.md](memory.md).
+
+## The NockIR VM
+The machine semantically has three registers.
+Two of these registers, `sub` and `res`, contain noun values.
+The third, `pc`, contains a function pointer.
+
+- `sub` contains the subject of the current Nock computation.
+- `res` stores the result of a completed Nock computation.
+- `pc` can semantically be read to saved on the stack, and set to jump.
+
+It is important to note that we do not actually implement `pc` directly in LLVM, rather we create new functions at save points and tail call to jump.
+
+The machine also has a stack with the number of "slots" in a frame specified per-frame when it is pushed.
+Each slot can hold a noun, the first slot (slot 0) can optionally hold a function pointer.
+We reference slots in the current frame as `frame[n]` where `n` is the 0-based index of the slot.
+
+We denote the NockIR for a nock formula as `ir[x]` where `x` is a formula.
+
+NockIR provides the following instructions:
+
+| Instruction | Operation
+|-------------|----------------------------------------------------------------
+| `axe[x]`    | `res := /[x sub]`                                              
+| `cel[s]`    | `res := [frame[s] res]`                                        
+| `puh[x]`    | push a frame with x slots                                      
+| `pop`       | pop a frame                                                    
+| `put[s]`    | `frame[s] := res`                                              
+| `get[s]`    | `res := frame[s]`                                              
+| `sub`       | `sub := res`                                                   
+| `noc`       | `sub := /[2 res]; res := /[3 res]`                             
+| `sav[s]`    | `frame[s] := sub`                                              
+| `reo[s]`    | `sub := frame[s]`                                              
+| `con[x]`    | `res := x`                                                     
+| `clq`       | if res is a cell, `res := 0` else `res := 1`                   
+| `inc`       | `res := res + 1` (crash if cell)                               
+| `eqq[s]`    | if `frame[s] == res` then `res :=0` else `res := 1`            
+| `edt[x]`    | `res := #[x res sub]`                                          
+| `ext`       | `sub := [res sub]`                                             
+| `lnt`       | `pc := ir[res]`                                                
+| `lnk`       | `frame[0] := pc; pc := ir[res]`                                
+| `don`       | `pc = frame[0]`                                                
+| `br0[x,y]`  | if `res` is 0, `pc = x`, if res is 1, `pc = y`, crash otherwise
+| `spy`       | External jump to a runtime-provided function producing a noun. 
+| `hns[b]`    | Look up a static hint in the hint table, jump if entry         
+| `hnd[b]`    | Look up a dynamic hint from res in the table, jump if entry.   
+
+The translation of Nock to NockIR takes the Nock formula plus two extra input bits, both of which are 0 if unspecified:
+
+| Code generation         | Generated code
+|-------------------------|------------------------------------------------------------------------------
+| `ir[0 0 [[b c] d]]`     | `puh[1]; ir[1 1 [b c]]; put[0]; ir[0 1 d]; cel[0]; pop; don`                 
+| `ir[s 1 [[b c] d]]`     | `puh[1]; ir[1 1 [b c]]; put[0]; ir[s 1 d]; cel[0]; pop`                      
+| `ir[0 0 [0 b]]`         | `axe[b]; don`                                                                
+| `ir[s 1 [0 b]]`         | `axe[b]`                                                                     
+| `ir[0 0 [1 x]]`         | `con[x]; don`                                                                
+| `ir[s 1 [1 x]]`         | `con[x]; don`                                                                
+| `ir[0 0 [2 b c]]`       | `puh[1]; ir[1 1 c]; put[0]; ir[0 1 b]; cel[0]; pop; noc; lnt`                
+| `ir[0 1 [2 b c]]`       | `puh[2]; ir[1 1 c]; put[1]; ir[0 1 b]; cel[1]; noc; lnk; pop`                
+| `ir[1 1 [2 b c]]`       | `puh[2]; ir[1 1 c]; put[1]; ir[1 1 b]; cel[1]; sav[1]; noc; lnk; reo[1]; pop`
+| `ir[0 0 [3 b]]`         | `ir[0 1 b]; clq; don`                                                        
+| `ir[s 1 [3 b]]`         | `ir[s 1 b]; clq`                                                             
+| `ir[0 0 [4 b]]`         | `ir[0 1 b]; inc; don`                                                        
+| `ir[s 1 [4 b]]`         | `ir[s 1 b]; inc;`                                                            
+| `ir[0 0 [5 b c]]`       | `puh[1]; ir[1 1 b]; put[0]; ir[0 1 c]; eqq[0]; pop; don`                     
+| `ir[s 1 [5 b c]]`       | `puh[1]; ir[1 1 b]; put[0]; ir[s 1 c]; eqq[0]; pop`                          
+| `ir[s t [6 b c d]]`     | `ir[1 1 b]; br0[ir[s t c] ir[s t d]]`                                        
+| `ir[0 t [7 b c]]`       | `ir[0 1 b]; sub; ir[0 t c]`                                                  
+| `ir[1 1 [7 b c]]`       | `puh[1]; ir[0 1 b]; sav[0]; sub; ir[0 1 c]; reo[0]; pop`                     
+| `ir[0 t [8 b c]]`       | `ir[1 1 b]; ext; ir[0 t c]`                                                  
+| `ir[1 1 [8 b c]]`       | `puh[1]; ir[0 1 b]; sav[0]; ext; ir[0 1 c]; reo[0]; pop`                      
+| `ir[0 0 [9 b c]]`       | `ir[0 1 c]; sub; axe[b]; lnt`                                                
+| `ir[0 1 [9 b c]]`       | `puh[1]; ir[0 1 c]; sub; axe[b]; lnk; pop`                                   
+| `ir[1 1 [9 b c]]`       | `puh[2]; sav[1]; ir[0 1 c]; sub; axe[b]; lnk; reo[1]; pop`                   
+| `ir[0 0 [10 [b c] d]]`  | `puh[1]; ir[1 1 d]; put[0]; ir[0 1 c]; reo[0]; edt[b]; pop; don`             
+| `ir[0 1 [10 [b c] d]]`  | `puh[1]; ir[1 1 d]; put[0]; ir[0 1 c]; reo[0]; edt[b]; pop;`                 
+| `ir[1 1 [10 [b c] d]]`  | `puh[2]; sav[1]; ir[0 1 d]; put[0]; reo[1]; ir[0 1 c]; reo[0]; edt[b]; pop`
+| `ir[s t [11 [b c] d]]`  | `ir[1 1 b]; hnd[b]; ir[s t d]`                                               
+| `ir[s t [11 b c]]`      | `hns[b]; ir[s t c]`                                                          
+| `ir[0 0 [12 b c]]`      | `puh[1]; ir[1 1 b]; put[0]; ir[0 1 c]; cel[0]; spy; pop; don`  
+| `ir[s 1 [12 b c]]       | `puh[1]; ir[1 1 b]; put[0]; ir[s 1 c]; cel[0]; spy; pop`              
+
+## From NockIR to LLVM IR
+
+NockIR is not intended to be generated separately. Rather, each NockIR instruction is implemented as a builder for some sequence of LLVM IR.
+
+The registers for the memory allocator (the stack and frame pointers) and the VM (the subject and result) are implemented in the LLVM IR by making each basic block an LLVM function. Within a function, each mutation to a register results in a new SSA register, and the previous registers are not used after the new assignment.
+Branching is accomplished by means of a conditional tail cail to basic blocks which contain static, unconditional jumps to the common suffix of the branch, or `don` instructions otherwise.
+
+## Calling convention for basic blocks
+
+We employ the `cc 10` convention, supplied for use by the Glasgow Haskell Compiler, as it matches our own needs.
+This ensures no registers are spilled to the (LLVM) stack, that parameters are passed in registers, and that tail calls are always tail-call-optimized, i.e. compiled to jumps.
+
+Each function representing a basic block, takes the current stack pointer, current frame pointer, current subject, and current value of the result register as arguments.
+
+Instructions which need to be implemented as functions, such as `axe` or `pop`, take these 4 arguments, plus a function pointer to tail-call to continue the basic block.
+
+## Calls and returns
+
+The NockIR provides the `lnt` and `lnk` instructions for tail and non-tail calls, respectively.
+The `lnt` instruction is relatively the simpler: it invokes the runtime system to generate or fetch cached code for the noun in the `res` register, then executes a dynamic jump to this code.
+The `lnk` instruction will be followed by another basic block, thus, it first saves the function pointer for that basic block in `frame[0]`; and then jumps to the code in question.
+The `don` instruction simply looks up the function pointer in `frame[0]` and jumps to it.
+Thus the outer frame for a computation installs a handler, matching the calling convention for basic blocks, which returns the result noun to the runtime system.
+
+## Instructions
+
+### `axe`
+
+The `axe` instruction looks up an axis in the subject and places it in the result register.
+It will nock crash if the axis is not valid for the noun in the subject register.
+
+### `cel`
+
+Creates a cell by allocating on the stack, with head from the given stack frame slot, and tail from the result register.
+Cell is placed in the result register.
+
+### `puh`
+
+Pushes a new frame with a given number of slots onto the stack
+
+### `pop`
+Pops a frame from the stack. This will cause a copy of stack-allocated nouns from within the current frame,
+traced from the result register as a root, so that the result register remains valid.
+
+The noun in the result register will remain the same, but since it must be copied up the stack, the memory
+representation may change
+
+### `put`
+Save the noun in the result register to a frame slot.
+
+### `get`
+Load the noun in a frame slot to the result register.
+
+### `sub`
+Set the subject register to the noun in the result register.
+
+### `noc`
+Set the subject register to the head of the cell in the result register
+
+**This operation assumes that the result register is a cell, and its behavior is undefined (e.g. not a nock crash)
+if the result register is not a cell.**
+
+### `sav`
+Save the noun in the subject register to a frame slot.
+
+### `reo`
+Load a noun from a frame slot to the subject register
+
+### `con`
+Load a noun immediate into the result register
+
+### `clq`
+If the result register is a cell, set the result register to 0, else set it to 1
+
+### `inc`
+If the result register is an atom, increment it by one. If it is a cell, nock crash
+
+### `eqq`
+Unifying equality between the noun in the given slot and the noun in the result register.
+Result register set to `0` if equal and `1` if not equal
+
+### `edt`
+Edit the noun in the subject register by replacing the subnoun at the given axis with the noun in the result register.
+Place the resulting noun in the result register (subject register is unmodified).
+
+Nock crash if axis is not valid for the noun.
+
+### `ext`
+Allocate a cell with the noun in the result register as a head, and the noun in the subject register as a tail.
+Place the cell in the subject register.
+
+### `lnt`
+Codegen and evaluate the noun in the result register with the current subject, in tail position.
+
+### `lnk`
+Codegen and evaluate the noun in the result register, returning to the instruction following `lnk`
+
+### `don`
+Return to just past the calling `lnk` instruction or the outer virtualization context.
+
+### `br1`
+Continue if the result register is 0, branch to the given label if it is 1. Nock crash otherwise

+ 13 - 0
crates/sword/docs/moving-memory.md

@@ -0,0 +1,13 @@
+# Zero-cost Memory Moves in the New Mars PMA
+
+The New Mars Persistent Memory Arena (PMA) maintains a file-backed memory arena where pages to be mutated are copied to new blocks in the file prior to mutation. Both reads and writes are memory reads and writes: data is written to memory in the standard way and is only written to disk as part of a page eviction or when `msync()` is called to ensure the page is durable.
+
+One possible use of this mechanism is to operate a "2stackz" allocation and control stack within an otherwised unused region of virtual memory within the PMA. Stack push, stack pop, and allocate operations use the lower level operations of the PMA to allocate disk regions for pages, map these regions, and store the mapping in a persistent page directory. None of these operations entails any synchronous write to disk. 
+
+The allocator design repurposed for the permanent heap area of the PMA, phkmalloc, allocates a new page or pages for allocation requests of more than one half of the size of a page. The 2stackz allocation algorithm could easily be extended either to page-align such allocations, or to note them during copying and copy them to a page-aligned boundary in the stack frame. A page-aligned object can be moved in virtual memory with no copying overhead simply by re-mapping the disk region to new pages of virtual memory, and updating the page index to correspond.
+
+Pages are unmapped when the stack is popped across their boundary, and deleted from the page directory when unmapped. Since snapshots are taken between event processing, not during, ephemeral pages for the stack will not be synchronously written to disk. In fact, they will only be placed on disk at all if the virtual memory system must eject the page, and the disk region will be repurposed for another page since the mapping will be deleted from the page directory and the disk region added to the cache of free disk pages.
+
+(An optimization is to continue to map pages when the extent of the stack requires it, but to unmap stack pages as a batch when the computation on the stack has completed.)
+
+If the 2stackz copier can take advantage of this system to move large objects in virtual memory space without needing to write or copy them to disk, or to copy them in physical memory, it will remove a great deal of the overhead associated with memory management. Large atoms could be handled without repeated copying overheads, regardless of size. Further, moving large atoms to the permanent heap would also involve only remapping disk (and indirectly, physical memory) to the proper heap location. Snapshots will then synchronize the large atom data to disk as with any other noun.

Fichier diff supprimé car celui-ci est trop grand
+ 390 - 0
crates/sword/docs/noun-rep.svg


+ 103 - 0
crates/sword/docs/persistence.md

@@ -0,0 +1,103 @@
+# New Mars persistence
+## Rationale
+
+New Mars requires a persistence layer. Nouns which are allocated in the top frame of a 2stackz memory manager can only be freed by resetting the stack. This loses all nouns. Therefore, for nouns which persist between computations, another arena is needed.
+
+These are also the nouns which should be included in a snapshot. Therefore, this arena ought to exist in memory mapped to persistent storage. This also represents a solution to the loom size limit: since memory is block-storage-backed, we can grow the Arvo state much larger than physical RAM or system swap space permit, and rely on virtual memory to make pages resident when we read them.
+
+Since this memory will never be used to directly allocate a noun, we do not need the speed of bump-allocating. We do require stable addressing along with fragmentation resistance. Therefore, we will take design cues from `malloc` implementations, and maintain information about free spaces in our memory arena. For nouns, freeing will be triggered by tracing or reference counting from a set of roots.
+
+This implies that any noun used as an input to a computation run in a stack arena must be maintained as a root until that computation completes. For instance: an Arvo core stored in persistent memory is used as input to an event computation. This core is maintained as a root in the persistent arena until the computation has completed.
+
+## Copy-on-Write File-Backed Memory Arena
+
+As the [Varnish](https://varnish-cache.org/docs/trunk/phk/notes.html) notes point out: a userspace program ought not to go to great lengths to juggle disk vs memory residency for persistent data. This is something the (terran) operating system already puts large amounts of work into. It is not necessary to work very hard to manage eviction and restoration of immutable data. At the greatest extent of effort, judicious use of `madvise` calls can reduce block storage wait times.
+
+Every page in our memory-backed arena is mapped to a region of a file. This ensures that the operating system can evict the page at will, without need for swap space. The file contains an index which is read when the process starts, describing which regions should be mapped to which virtual memory addresses. This permits us to persist data without serialization or deserialization. The file stores a memory image of the persistent arena.
+
+What is more difficult is to ensure that data *written* to the arena is fully synchronized and that the persistent index between file and memory locations do not point to corrupted data. This is addressed with a "copy-on-write" strategy, similar but not identical to that of [LMDB](http://www.lmdb.tech/media/20120829-LinuxCon-MDB-txt.pdf).
+
+The copy-on-write strategy ensures that commmitted data is not corrupted. Since it is not possible to "atomically" write data to disk, committed data is not overwritten, but rather copied and modified. Committing then progressively ensures that this new data is durably written to disk, and that the index of disk regions to memory regions is durably updated, before it will permit the previous copy to be overwritten.
+
+### Handling writes to the arena
+
+There are several ways in which we can handle writes without overwriting, and possibly corrupting, data already committed to our store.
+
+By default, pages in the persistent memory arena are protected read-only (`PROT_READ` flag to `mmap()` or `mprotect()`). This means that an attempt to write through a pointer into such a page will trigger a protection fault, raising a SIGSEGV to our process.
+
+#### Immediate copying
+
+The most likely successful approach involves immediately copying the page to a new page, mapped to a new region of the file, and then mapping this page back over the same virtual memory space.
+
+To handle the SIGSEGV signal and resolve the fault, our handler will:
+
+- move the page to the copy-on-write set, so other threads faulting will not also attempt to copy the page.
+- `write()` the contents of the page to an unused region of the backing block storage. Note that this will not block on writing through to the disk, though it may write through if the OS decides.
+- `mmap()` the new region to the address of the original page, being sure to set `MAP_SHARED`, `PROT_READ`, and `PROT_WRITE`
+- Move the page from the copy-on-write set to the dirty set.
+
+The net effect of this is that the file region to which the read-protected page was mapped will remain unmodified, but our process continues with identical contents in a virtual memory page mapped to a new region of the file. The process continues from the faulting instruction and writes to memory. Index metadata which maps regions of the file to regions of memory remains untouched at this juncture.
+
+To facilitate restoring the persistent memory arena from durable block storage, we reserve two pages at the beginning of the file for metadata storage. This does not limit us to a page for metadata, we may commit metadata objects alongside our stored data, and reference it from a page. A metadata page contains a counter and a checksum of the page's contents. The page with a valid checksum and the more recent counter is used to load the data.
+
+To atomically commit writes since the last commit to durable storage, we simply `msync()` each dirty page. If all syncs succeed, we know that data in these pages is durably persisted to disk. We may now safely write a new metadata page to whichever metadata page is not in use. Once this too successfully `msync`s, our writes are durably committed.
+
+#### Lazy copying
+
+Another possible approach which leverages kernel support for in-memory copy-on-write, at the expense of using a boundable amount of swap, is to make writable mappings `MAP_PRIVATE`. This instructs the kernel to copy the page in the page cache on the first write. Unfortunately, this is now an anonymous mapping, meaning it is not backed by file storage.
+Thus, if the kernel elects to evict it, it will be written to swap space on disk. It is still necessary to map pages as `PROT_READ` and use a signal handler, because we must track which pages are dirtied. It is almost certainly necessary to set a configurable limit on the number of dirty pages before a snapshot will be committed, since the number of dirty pages is limited by the available RAM and swap.
+
+To commit, we select regions in the file from the free list, and `write()` each dirty page to a free region.
+We must then achieve a successful `fsync()` to know that the file has been committed.
+
+### When to commit
+
+Committing is required to know that an event is durably persisted in the log. It is also required to save a snapshot to disk.
+
+### Memory allocation
+
+Allocation of memory space for persisted nouns, and for helper structures for the runtime, follows largely the algorithm from [phkmalloc](https://papers.freebsd.org/1998/phk-malloc.files/phk-malloc-paper.pdf).
+Briefly:
+
+- Pages are tracked as free, start, continue, or partitioned
+- Small allocations seek space in a partitioned page, and only partition a page if no suitable space is found
+- Large allocations (> 1/2 a page) are allocated sufficient contiguous free pages
+
+One difference is that since all of our persistent memory is file-backed, the acquisition of new memory consists in mapping new, initially-dirty pages, backed by unused regions of block storage, instead of using the `brk` syscall.
+
+### Block region allocation
+
+As well as allocating memory to noun (fragments) which need to be copied into the persistent set, we must also allocate regions of block storage to memory pages. Fortunately this is much easier. All pages are the same size. When committing writes, we can take note of file regions which are not mapped by the new metadata, and prepopulate a free list of file regions with these regions. The list becomes usable once the commit succeeds. We simply pop regions off of the list, starting with those nearest the beginning of the file. until it is exhausted, at which point we must extend the file.
+
+It's not clear whether it would be necessary to shrink the backing file, but if so, this could initially be done manually with a separate program, or a heuristic could be applied. Perhaps when a certain ratio of free-to-used pages are run, a defragmentation step is run, followed by shrinking the file.
+
+### Snapshots
+
+After each event is processed, the working state is copied to the snapshot persistent arena to be used as input for the next event. Creating a durable snapshot consists of committing, and recording a pair of event number and noun pointer in a durable metadata record. Metadata is referenced from the root page of a commit, and includes the event number of the snapshot and a pointer to the snapshotted noun.
+
+### Event log
+
+The event log should live in a separate arena from Arvo snapshot nouns. These arenas should be of incomparable seniority: that is, references are not permitted between either of them. This precludes synchronization issues between snapshot and event log commits, and ensures that old epochs of the event log can be freed without corrupting a snapshot.
+
+This requires logic in the copier to ensure that a reference is only preserved as a reference if the seniority of the source is ≤ the seniority of the destination.
+
+Epochs in the event log can be stored as linked lists in the event log persistent arena. In order to safely release effects computed from incoming events, the events must be durably written to disk.
+
+Allocations in this arena may be made by bumping a pointer within pages. Dirtying a page does not necessitate copying as long as the pointer to the most recent event can be stored consistently, and is updated *after* an event is made durable on disk. This eliminates repeated copies of a page to re-dirty it after a commit. The only time when a free block of storage must be allocated to a page is when a new page is mapped. If each epoch has its own page set, then when an epoch is discarded, pages may simply be unmapped together and their blocks re-added to the free list.
+
+The separate arena need not be maintained in an entirely separate memory region. Rather, on disk, two indexes each with two root pages (which are alternated for commits) can be maintained, and the allocator can track which pages belong to which arena, and inform the persistence subsystem of this when requesting free pages, so that the persistence subsystem knows which index should track a newly dirtied page.
+
+### Concurrency
+
+Because nouns are immutable, and because the copy-on-write approach ensures that a new, dirty page is a copy of an existing clean page prior to mapping it, readers should never see an inconsistent state of an object so long as that object is traceable from a root.
+
+It is not necessary to support multiple writers to the arena. Event (`+poke`) computation is linearized: we cannot start computing the next event until the result of the previous event is copied to the arena. Computation of scries (`+peek`) may run concurrently, but will only read from the arena unless new code generation is necessary.
+
+Thus, it should suffice to maintain a single, global writer lock for an arena.
+Contention for this lock is not likely to be high, and this will eliminate a great deal of complexity which would obtain if page dirtying and commits required more granular synchronization.
+
+## Tuning
+
+Commit frequency of the Arvo snapshot arena is a potentially tunable value, with performance implications.
+Committing more often incurs more disk IO, saving nouns that will potentially soon be discarded (Ames queues, for instance). However, under memory pressure, it makes paging more performant, as the proportion of dirty pages which must be *written* to disk to evict is smaller. Already-committed pages can simply be evicted from memory and re-read from disk when a read fault occurs. Committing less often requires writes as part of eviction, but reduces disk IO. It is desirable as long as memory pressure as low, and highly desirable for block storage where write-wearing is a concern.
+

+ 22 - 0
crates/sword/docs/pills.md

@@ -0,0 +1,22 @@
+# Making your own pill to run with Ares
+
+Ares development and testing, unlike regular development and ship operation, currently requires careful control over what pill is used to launch a ship. This document details how pills are created for the purposes of development and testing.
+
+## Example: `baby.pill`
+
+`baby.pill` is an extremely minimal Arvo-shaped core and Hoon standard library equipped with `%sham` jets needed to run it. `~wicdev-wisryt` [streamed a video](https://youtu.be/fOVhCx1a-9A) of its development. You can find the source Hoon for `baby.pill` in `resources/pills/src/baby/baby.hoon`, and the limited version of Hoon that it uses in `resources/pills/src/baby/cradle.hoon`. A pre-compiled `baby.pill` is already available at `resources/pills/baby.pill`. However, the steps to compile it yourself are documented below.
+
+1. Boot a fake `zod` using an ordinary Urbit executable (not the one you created
+to run Ares as serf)
+2. Run `|mount %base`
+3. Copy the contents of `resources/pills/src/baby/` to `/path/to/fake/zod/base/lib/`
+4. Run `|commit %base`
+5. Run `.baby/pill -build-file %/lib/baby/hoon`. This will make a file named `baby.pill` in `/path/to/fake/zod/.urb/put/`
+
+You can now use this pill to boot a ship using your Vere + Ares executable:
+
+```bash
+./ares-urbit -F dev -B /path/to/fake/zod/.urb/put/baby.pill
+```
+
+If it writes `effect` to the terminal with every keystroke, you have succeeded!

Fichier diff supprimé car celui-ci est trop grand
+ 54 - 0
crates/sword/docs/priors.bib


+ 45 - 0
crates/sword/docs/proposal/hypotheses.md

@@ -0,0 +1,45 @@
+# Hypotheses tested by New Mars
+
+## Stack-only allocation for computation
+**Hypotheses:**
+*Stack allocations, with nouns for return copied up the stack ("lexical memory management")
+is a feasible way to implement Nock. Specifically*
+- *Lexical memory management is simple to implement.*
+- *Lexical memory management provides performant allocation and collection of Nock nouns.*
+
+## Just-in-time compilation
+Intuitively, compiling Nock bytecode to machine code should provide performance wins with a highly-amortized cost.
+
+Urbit code is highly persistent. We don't dynamically load code for a short interactive session and then discard it,
+but instead load *and already compile* hoon code to Nock, then continue using that code in an event loop for a long period
+until the next OTA update of Arvo or an application.
+
+Especially since we already take the time to compile hoon to nock, it likely makes sense to compile Nock to machine code
+that can be directly invoked without interpretation overhead.
+
+**Hypothesis:**
+*Compiling Nock bytecode to machine code and caching the compilation results in much faster Nock execution,
+and the expense of compilation is acceptable given the amortization across a very high number of invocations.*
+
+## Large-noun hash table
+The major downside of the lexical memory management scheme is that large nouns allocated deep in the stack and returned from
+Arvo will be repeatedly copied to return them through multiple continuation frames. This can be ameliorated by using
+a separate arena for large nouns. The step of copying the noun up the stack tracks how much memory has been copied, and,
+at a certain threshhold, resets the stack pointer to undo the copy and instead copies the noun into the separate
+arena and returns a reference.
+
+By making this arena a hash table, we can create a store which can be copy-collected without adjusting references.
+This can also serve to deduplicate nouns in the table.
+
+The hashtable serves as a place to store non-noun objects, such as bytecode or jet dashboards, and a place to store noun
+metadata. Rather than suffering the overhead of possible metadata annotations on every cell, we can instead only
+allow metadata as the head of a hashtable entry.
+
+This hashtable also permits differential snapshotting, by storing only the hashes which are new in the table since the last
+snapshot. It also permits paging of large nouns to disk, as a hashtable entry could be marked with a path to a page file
+and paged out.
+
+**Hypotheses**:
+- *A hash referenced memory arena for large nouns resolves the major downside of lexical memory management by preventing repeated copying of large nouns.*
+- *A hash referenced memory arena provides a store for non-noun objects in the nock interpreter.*
+- *A hash referenced memory arena provides transparent incremental snapshotting and disk paging of large nouns.*

+ 60 - 0
crates/sword/docs/proposal/milestones.md

@@ -0,0 +1,60 @@
+# New Mars project milestones
+
+The two major research (rather than implementation) goals of the New Mars project are:
+
+1. The effectiveness of an on-stack bump allocator and delimited copying collector as a memory manager for Nock computational memory.
+2. An effective linearization strategy for Nock code generation.
+
+## Near Term: Memory management
+
+The goal of this set of milestones is to validate the 2stackz memory model and its implementation
+
+- run nock
+  * 2stackz allocator (implemented)
+  * tree-walking interpreter (in-progress)
+    + unifying equality
+    + tree edits
+- run non-trivial amounts of nock
+  * cue
+  * load a small pill (probably of a custom core)
+- run some compiled hoon
+  * mug caching
+  * noun hashtable
+  * jam
+  * jet dashboard
+    + optimize for simplicity / speed of implementation
+
+## Next: code generation
+
+The goal of this stage is to generate highly-linearized IR from Nock
+
+Note that a substantial part of this milestone is already accomplished, if unvalidated.
+The New Mars project began with linearization of Nock across Nock 2 and Nock 9 being a problem without theoretical or design solution.
+Subject-knowledge analysis (SKA) is the (currently theoretical) artifact which permits such linearization.
+
+- Firm up reference Hoon implementation of SKA
+- Use SKA to bytecode-compile Nock
+  - This validates SKA as a code-generation and jet-matching strategy for Nock without bogging down in the details of LLVM
+- Switch out bytecode for LLVM IR
+
+## Later: heaps and paging
+
+The goal of this stage is to permit New Mars to function fully as a Mars, saving snapshots and event logs, and paging infrequently used and/or large data out to disk.
+
+- Threshhold based eviction to a heap
+- Heap collection strategies
+- Demand paging of heap objects
+- Event logging and snapshotting using demand-paged heap objects
+- Tie disk reclamation to virtual memory reclamation
+
+## Historical progress:
+
+Project time to this point has been spent:
+
+1. Implementing the memory management strategy
+2. Attacking the code linearization problem for Nock in order to come up with a means of producing direct, monomorphized calls to generated code for procedures and to jets.
+
+The memory management implementation is complete but untested. The implementation of the tree-walking interpreter will permit us to test and harden this implementation.
+
+The subject knowledge analysis is a still theoretical result, but a result nonetheless, which convincingly and constructively establishes that linearization and monomorphization of Nock for code generation is possible in a reasonably efficient manner.
+

+ 37 - 0
crates/sword/docs/proposal/nock.txt

@@ -0,0 +1,37 @@
+## sample eval
+
+### nock 6
+```
+*[a 6 b c d]
+*[a *[[c d] 0 *[[2 3] 0 *[a 4 4 b]]]]
+*[a *[[c d] 0 *[[2 3] 0 +*[a 4 b]]]]
+*[a *[[c d] 0 *[[2 3] 0 ++*[a b]]]]
+```
+#### if *[a b] is 0
+```
+*[a *[[c d] 0 *[[2 3] 0 ++0]]]
+*[a *[[c d] 0 *[[2 3] 0 +1]]]
+*[a *[[c d] 0 *[[2 3] 0 2]]]
+*[a *[[c d] 0 2]]
+*[a c]
+```
+#### if *[a b] is 1
+```
+*[a *[[c d] 0 *[[2 3] 0 ++1]]]
+*[a *[[c d] 0 *[[2 3] 0 +2]]]
+*[a *[[c d] 0 *[[2 3] 0 3]]]
+*[a *[[c d] 0 3]]
+*[a d]
+```
+
+### nock 9
+*[a 9 b c]
+*[*[a c] 2 [0 1] 0 b]
+(d=*[a c])
+*[*[d 0 1] *[d 0 b]]
+*[d *[d 0 b]]
+
+
+
+## impl notes
+

+ 21 - 0
crates/sword/docs/proposal/notes-~2021.9.23.md

@@ -0,0 +1,21 @@
+# Notes ~2021.9.23
+## Discussion with ~rovnys-ricfer
+* Some discussion of the memory model, in particular using tag bits to identify
+  - direct noun
+  - indirect noun
+  - pointer to cell
+  - hash table reference
+* Hash table ejection strategy
+  - When the copier is returning a noun, it counts how many iterations of copying it has performed
+  - Above a tunable threshhold, an entry in the hash table is made and the noun is copied there instead.
+  - Existing pointers into the hash table are copied and re-inserted as new entries, thus maintaining an invariant
+    that a hash table entry can only reference its own memory by a direct pointer, or another hash table entry,
+    by hash reference.
+  - nouns that require metadata (jet pointers, cached bytecode) are ejected to the hashtable
+  - hashtable can also store non-noun data such as bytecode
+  - TBD: a collection strategy for the hash table.
+
+## Infrastructure channel discussion
+* Interpreter should handle nock 12 with an extra input of a scry gate stack, so it can be used to jet `+mink`
+* Also need to return crash values from the interpreter, and build traces when passing through nock 11.
+* ~master-morzod objects to calling the machine code from JIT compilation "automatic jets" and is probably right.

+ 23 - 0
crates/sword/docs/proposal/notes-~2021.9.24.md

@@ -0,0 +1,23 @@
+# Notes ~2021.9.24
+## Exploration of `+mink`
+The [`+mink`](https://github.com/urbit/urbit/blob/fa894b9690deae9e2334ccec5492ba90cb0b38f9/pkg/arvo/sys/hoon.hoon#L5978-L6106)
+arm in [`hoon.hoon`](https://github.com/urbit/urbit/blob/master/pkg/arvo/sys/hoon.hoon) is a metacircular Nock interpreter
+intended to be jetted by invoking the host Nock interpreter. In addition to the subject and formula to evaluate, `+mink`
+takes a scry gate which is used to evaluate nock 12 (scry) formulas.
+
+The jet uses `u3r_mean` to take apart the sample of the `+mink` gate and feeds the resulting nouns to 
+`u3n_nock_et`, which runs the interpreter and produces a 'toon', 
+
+Scry gates are kept in a stack because a scry gate may itself scry, and should not re-enter itself.
+
+## Exceptions in new mars
+### Suboptimal but simple way
+The simple thing to do would be to make every continuation expect a toon and on an error or block result, immediately call
+the next continuation up the stack with it, until a continuation installed e.g. by the +mink jet branched on the result.
+This is effectively writing the interpreter as a CPS/trampoline translation of +mink.
+
+### Likely more optimal way
+It should be possible to store exception contexts in the hash table: comprising stack pointers for unwinding,
+code pointer to jump to (possibly we could use setjmp/longjmp instead), and a hash reference to the next outermost
+handler. The hash reference to the current exception context would be held in a register. This structure could also hold
+the current scry gate.

+ 159 - 0
crates/sword/docs/proposal/noun-representation.md

@@ -0,0 +1,159 @@
+# Noun Representation
+
+A noun is represented as a word, which may be a pointer or reference to other memory.
+
+Semantically, nouns are either atoms or cells, where a cell is an ordered pair of nouns.
+
+A noun-word is a word sized to the machine architecture (e.g. 64 bits on x86_64, 32 bits on ARMv)
+which, possibly together with other memory to which it points, represents a noun.
+
+A noun-word with a 0 as the most significant bit is a direct atom.
+The machine word corresponds exactly to the value of the atom.
+
+A noun-word with 10 as the most significant bits is an indirect atom. By masking the 2 least significant bits,
+this noun can be converted to a (4 byte aligned) pointer, which points to an length-tagged array.
+That is, the machine word pointed to by the pointer specifies the length of the array in bytes,
+and is immediately followed in memory by a byte array containing the full indirect atom.
+
+A noun-word with 11 as the most significant bits is a pointer to a cell. By masking the 2 least significant bits,
+this noun can be converted to a (8 byte aligned) pointer, which points to a cell in memory.
+
+```
+MSB                                                          LSB                  
+|--------------------------------------------------------------|                  
+0XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX  Direct atom     
+10XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX  Indirect atom   
+11XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX  Pointer to cell 
+```
+
+## Indirect atoms
+
+An indirect atom is a pointer to a machine word, which word is directly adjacent to an array of bytes.
+The machine pointer is obtained by left-shifting by 2 bits.
+The machine word specifies the size of the atom in bytes, and the byte array stores the atom in little-endian order.
+
+## Cells
+
+A cell is a pointer to a noun-word, which is adjacent to another noun-word.
+The machine pointer is obtained by left-shifting by 2 bits.
+The pointed-to noun-word is the head of the cell, and the immediately adjacent noun-word is the tail.
+
+# Noun allocation
+The primary allocation of memory is done in the _stack arena_.
+This arena contains two stacks growing in opposite directions from opposite ends of the arena.
+The stack growing from lower memory addresses towards higher addresses is called the "north" stack, while the stack growing from higher to lower addresses is the "south" stack.
+
+Computation begins with a stack frame on the north stack. The current stack frame is delimited by two registers: the stack pointer and the frame pointer.
+A stack frame maintains the stack pointer and frame pointer for the previous stack frame as the first two words within the frame, except for the top frame
+where these words are null (`0x0`).
+
+Testing whether we are currently in the north or south stack is done by comparing the frame pointer to the stack pointer.
+If the frame pointer is greater than the stack pointer, we are in the north stack, otherwise we are in the south stack.
+
+Pushing of a new stack frame is illustrated by the following C-like pseudocode:
+
+```c
+void push() {
+  if(fp > sp)
+  {
+    /* we are on the north stack, push on the south stack by reading the end of the last
+     * south stack frame (the saved frame pointer). If it's null push at the end of the memory arena */
+    void * new_sp = *(sp + sizeof(void*));
+    if(new_sp == 0x0) {
+      new_sp = arena + arena_size - 1;
+    };
+    void * new_fp = new_sp - 2 * sizeof(void*);
+    // save old SP at new SP
+    // save old FP one word below
+    *new_sp = sp;
+    *(new_sp - sizeof(void*)) = fp;
+    sp = new_sp;
+    fp = new_fp;
+  } else {
+    /* we are on the south stack, push onto the north stack by reading the end of the last
+     * north stack frame (the saved frame pointer). */
+    void* new_sp = *(sp - sizeof(void*));
+    void* new_fp = new_sp + 2 * sizeof(void*);
+    // Save old SP at new SP
+    // Save old FP one word above
+    *new_sp = sp;
+    *(new_fp + sizeof(void*)) = fp;
+    sp = new_sp;
+    fp = new_fp;
+  }
+}
+```
+
+Nouns can be pushed onto the stack frame as follows:
+
+```c
+// TODO: check that stacks haven't grown into each other
+
+/* Allocate memory on the stack frame for some noun words.
+ * Note that the parameter is denominated in noun-words, not bytes.
+ * 
+ * This enables machine-portable code.
+void* push_noun(size_t nouns) {
+  if(fp > sp) { // north stack
+    // fp is always pointing to free space
+    base = fp;
+    fp = fp + nouns * sizeof(void*);
+    return base;
+  } else
+    fp = fp - nouns * sizeof(void*);
+    // fp is always pointing to free space
+    base = fp + 1;
+    return base;
+  }
+}
+```
+
+Allocating a cell and receiving it as a noun-word is as follows:
+
+```c
+void* push_cell(void* hed, void* tal) {
+  void* cel = push(2);
+  *(cel) = hed
+  *(cel + sizeof(void*)) = tal;
+  cel = cel >> 2;
+  cel = cel | (0b11 << (sizeof(void*) - 2));
+  return cel;
+}
+```
+
+Pushing an indirect atom is done by providing a pointer to the atom which be copied ot the stack:
+
+```c
+void* push_indirect_atom(void* tom) {
+  tom = tom << 2;
+  size_t words = *tom;
+  void* noun = push_noun(words + 1);
+  memcpy(noun, tom, (words + 1) * sizeof(void*));
+  tom = tom >> 2;
+  tom = tom | (0b10 << (sizeof(void*) - 2));
+  return tom;
+}
+```
+
+## Hash reference
+
+XXX
+
+A noun which the copier determines requires more copying than some tunable threshhold can instead be copied into the _hash arena_.
+
+The noun is hashed with a secure hash function. (TODO: which one and how is the noun serialized for hashing?).
+The hash arena consists of a bump-allocated memory arena which contains a HAMT (1) mapping hashes of nouns to pointers to those nouns,
+and a 1-D (2) tree mapping memory allocations for cells to the hashes of cells.
+
+The noun arena is collected whenever it fills by copying to a new arena.
+References are never stored as hashes. Instead, they are stored as indirect atom or cell references as above, with the following invariants:
+
+- A pointer from the stack allocation arena may point to any noun in the hash arena, including a subnoun whose hash is not explicitly stored.
+- A pointer from the hash arena may point to a subnoun in its own allocation range.
+- A pointer from the hash arena to another allocation range must point to the head of this allocation range. (3)
+- A pointer from the hash arena may not point to the stack arena.
+
+### Noun ejection
+
+### Paging and snapshotting
+

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff