Status: Draft (Relaunch saga) Scope: The scaffolding and first smoke test that prove the pivot. This document is rewritten each phase; when the next saga begins, prior phases' content moves to a dated section or is replaced.
End the relaunch saga with a repository that (a) no longer describes a
C implementation, (b) builds its own .s source through a vendored
cor24-run, and (c) passes a smoke test that exercises the full
toolchain end-to-end on the single mnemonic nop.
sw-cor24-assembler/
+-- README.md # revamped, describes the .s-based scope
+-- justfile # recipe runner
+-- .gitignore # build/, vendor bin/
+-- CLAUDE.md # updated session protocol + project rules
+-- docs/
| +-- prd.md
| +-- architecture.md
| +-- design.md # this file
| +-- plan.md
+-- src/
| +-- sw-as24.s # the self-hosted assembler (starts tiny)
+-- tests/
| +-- smoke/
| +-- nop.s # single "nop"; expected byte 0x00
+-- scripts/
| +-- vendor-fetch.sh # port of the sw-cor24-ocaml script
| +-- build.sh # assemble src/sw-as24.s with vendored cor24-run
| +-- test.sh # assemble nop.s with both tools, diff bytes
+-- vendor/
+-- .gitignore # ignores */v*/bin/*, keeps .gitkeep
+-- active.env # single-source-of-truth version pins
+-- sw-em24/<version>/
+-- version.json # manifest (repo, commit, artifact paths)
+-- bin/
+-- .gitkeep # placeholder; cor24-run lands here on fetch
The .agentrail/ directory (new saga) and .agentrail-archive/
(preserved C-era saga) are tracked but are not part of the build
surface.
Directly mirrors sw-cor24-ocaml. Every vendored tool has:
- An entry in
vendor/active.env:SW_EM24_VERSION=v0.1.0 - A manifest at
vendor/<tool>/<version>/version.jsonwith (at minimum) the upstream repo, a local sibling path, the pinned commit SHA, and a map of artifacts to copy. - A gitignored
bin/(kept present by.gitkeep) wherescripts/vendor-fetch.shdeposits the built binary. - A copy rule in
scripts/vendor-fetch.shthat locates the local sibling repo viarepo_path_local, verifies the pinned commit, and copiestarget/release/cor24-runinto the manifest'sbin/directory.
The one vendored tool for the relaunch saga is sw-em24 (upstream
sw-cor24-emulator, artifact cor24-run). The Rust cross-assembler
library (sw-cor24-x-assembler) is a transitive dependency of
cor24-run and is not vendored separately; if a later saga needs to
consult its source, it does so by path, not by copy. The user may
elect to re-vendor against a different upstream later; the version
pin (active.env) is the only thing that needs to change.
Initial set -- minimum viable for the smoke test:
| Recipe | Purpose |
|---|---|
vendor-fetch |
Materialize vendor/sw-em24/<v>/bin/cor24-run. |
vendor-check |
Verify that expected vendor artifacts are present. |
build |
Assemble src/sw-as24.s with the vendored cor24-run. |
test |
Run the smoke test: both tools assemble nop.s; diff. |
run FILE |
Assemble + execute an arbitrary .s via cor24-run. |
clean |
Remove build/. |
Design decisions:
- No
installtarget -- this project builds artifacts underbuild/and does not install anywhere. builddoes not depend onvendor-fetch; failing fast with a helpful message when the vendor binary is missing is preferable to silent fetching.testis the contract the relaunch saga closes against. It must be green by the end of this saga.
Input -- tests/smoke/nop.s:
nop
One line, no comments. Saga-1 sw-as24 deliberately does not parse comments (step 008 non-goal); header comments return in saga 2 once comment-stripping lands.
Expected behaviour:
cor24-run --assemble tests/smoke/nop.s build/ref.bin build/ref.lstemits a single byte0xFF(the COR24 encoding ofnop-- verified empirically against the Rust assembler's listing output).cor24-run --load-binary build/sw-as24.bin@0 --entry 0 -u "nop\x04" ...loads the already-assembled sw-as24 binary, feeds the .s content over UART RX (terminated with 0x04 EOT), runs until self-branch halt. sw-as24's UART TX carries the startup bannerSfollowed by the hex representation of the emitted byte:"SFF"on match,"S00"on fail.scripts/test.shparses cor24-run'sUART output:summary line, strips the one-char banner, pipes the remaining hex text throughscripts/hex2bin.sh, anddiff -qs the decodedbuild/candidate.binagainstbuild/ref.bin. Exit 0 iff they match.
sw-as24 emits its machine-code output as ASCII hex (two printable
chars per byte) rather than raw binary. Reason: cor24-run silently
filters byte 0x00 out of every UART-TX observation path -- the
per-byte log, the summary line, and even raw stdout under
--terminal. Verified by probing with lc r0, 0 vs lc r0, 1:
the 0x01 byte survives to stdout, the 0x00 byte does not.
Since nop encodes to 0xFF and future instructions will hit
0x00 too, emitting raw bytes forfeits observability.
Hex encoding sidesteps the filter (all hex digits are printable 0x30-0x46) and carries extra benefits: transport-robust over any terminal, human-debuggable, matches the classical Intel-HEX / S-record pattern for shipping binary over text channels. The design holds even if cor24-run's filter is fixed upstream later.
scripts/hex2bin.sh (a one-line wrapper around xxd -r -p) is the
host-side decoder. It pairs with sw-as24's output anywhere: the
smoke test, an FPGA flasher, a serial-console capture.
Minimum viable assembler: recognises literally one mnemonic, nop,
and emits hex for one byte. Expectations:
- UART banner
Son startup (one printable char; test.sh strips). - Three-byte UART read into r0, each byte compared against 'n',
'o', 'p' in sequence via
ceq+brf fail. - On match, emit two UART bytes
F,F(hex for0xFF= nop encoding). On mismatch, emit0,0(hex for0x00sentinel). - Halt via branch-to-self (
halt: bra halt). cor24-run detects self-branch and terminates the emulation.
Calling convention for putc and getc helpers follows the
jal r1, (r2) pattern from cor24-rs/rust-to-cor24/src/examples/
(see uart_hello.s): jal stashes the return address in r1;
the callee pushes r1 on entry, pops before jmp (r1) on exit,
leaving r1 free as scratch inside the subroutine.
Deliberately omitted (later sagas): labels in input, comments in input, multiple mnemonics, operands, directives, a symbol table, two passes, proper byte-to-hex helper (currently hard-coded for the two byte values this saga emits).
Replace the current C-oriented README with a scope statement that matches the PRD:
- One-line description: self-hosting COR24 assembler written in COR24 assembly.
- Build:
just vendor-fetch && just build. - Test:
just test. - Status: Relaunch in progress; single-mnemonic smoke test is the current milestone.
- Links: PRD, architecture, plan.
The existing CLAUDE.md still describes the C implementation (tc24r,
no structs, no malloc, etc.). It must be rewritten to match the new
scope:
- Remove tc24r C subset constraints.
- Replace build instructions with
just build/just test. - Keep the AgentRail session protocol (it is tool-bound, not implementation-bound).
- Update architecture summary to point at
docs/architecture.mdrather than duplicating it.
Anything not listed above. The plan document enumerates which saga picks up each deferred topic.