25 KiB
MACHINE.md Steps 1–2: The ROM and the .f18 Trailer — Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: The machine boots from its ROM, a prebuilt F18 binary. The ROM assembles the system's
.f18 source at boot, and the result is the same node memory the prebuilt nucleus gives today:
the same dict_hash, POST 538/538.
Architecture: The engine gains a read-only ROM address range above RAM. The ROM is an F18
assembler written in F18, assembled once off the node by text.c, as a bitstream is built. It
reads .f18 text from the console input register and lays down code and data in RAM. When the
loader's last .f18 blob ends, it jumps to the entry the source names. Today's
v4/capsule/*.v4 is converted mechanically into .f18 blobs. Everything mkimage.c did in C
to make the switch-on state (headers, variables, the image record) becomes .f18 source,
written with generic assembler directives. After this plan, the prebuilt nucleus is gone.
Tech stack: C99 (engine, tests, the off-node ROM build), F18 assembler in the text.h
notation, Python 3 (the one-time .v4 → .f18 converter), GNU make.
Spec: docs/v4.0.0/MACHINE.md, sections 1–3, 5, 6 (steps 1 and 2), 7.
Global Constraints
- The VM is a machine: registers, a control unit, the opcodes. The ROM knows no FORTH: no
dictionary layout, no
:as FORTH means it, noINTERPRET. (MACHINE.md 7.1) - The ROM is the only binary. Size: whatever it takes. (MACHINE.md 2)
.f18blobs are plain text, laid out as pretty-printed F18 code, with noBlock Nformat. (MACHINE.md 7.2)- One ROM per cell width, 32 and 64. Same behaviour on the C engine and, later, the FPGA.
- No stubs, stand-ins or "for now" code. Every task delivers working, as-intended code.
- Strict C99,
-Wall -Wextra -Werror. The engine (v4/src) andv4/systemuse no C library (the kernel links them). - Acceptance: three ISAs,
make -f kernel/Makefile ARCH=<arch> clean qemu, one QEMU at a time, in the foreground, took>withPARITY:V4_POST tests=538 pass=538 fail=0,POST: PASSEDand the samePARITY:V4_SYSTEM ... dict_hash=on all three; thenmake -C v4 test-32. Logs committed underlogs/. - Work on
StarForth-v4.0.0. Commit and push per task. No branches. No stash.
Review Focus
- A blob that ends mid-token or mid-definition, such as a file with no trailing newline,
or one whose last word has an unresolved forward branch. Expected: the ROM finishes the
token, and resolution happens only at the final
start. Test in Task 7. - A forward branch that cannot reach its target from its slot at 16384 words plus the ROM
range. Expected: the ROM reports the same error
text.cgives and stops. It never emits a branch that goes somewhere else. Test in Task 5. - A symbol defined twice, or an opcode or reserved word used as a name. Expected: the same
errors as
text.c'sdefine. Test in Task 4. - The ROM's assembly-only tables (symbol table, references) left in RAM after assembly.
Expected: they are zeroed before
startjumps, so nothing of the building is left behind (asmkimage.ctoday). The routines' permanent state area (MACHINE.md 2.1) is not cleared: it belongs to the ROM. Test in Task 8, in the whole-memory comparison, which leaves out exactly that area. - The console input running dry between blobs. Expected: the ROM waits on CONSOLE-STATUS and does not take the absence of a character as the end. Test in Task 3.
How the ROM is checked: the oracle
text.c stays as the oracle. It is the ROM's specification at the bit level, and it is what
builds the ROM. Every ROM component is tested by feeding the same text to text.c and to the
ROM running on the golden model, then comparing node memory word for word. A test that names a
ROM routine calls it directly: the ROM build writes its symbol table (Task 2), so a test sets P
to a routine's address, runs it until it returns, and reads the stacks.
The F18 bodies of the ROM routines are written during each task against these tests. The plan
fixes each routine's name, stack effect, memory and the exact behaviour required, which is
text.c's, cited by function. It does not pre-write unrun F18 code. The test is what decides
whether a routine is done.
File structure
| File | Responsibility |
|---|---|
v4/include/v4/node.h, v4/src/node.c (modify) |
The ROM range: V4_ROM_BASE, V4_ROM_WORDS, v4_node_rom_attach. Fetch and execute from ROM; a store there is an address fault. |
capsules/v4/rom/*.f18 (create) |
The ROM's own source, one file per part: in.f18 (input and tokens), num.f18, sym.f18, pack.f18, word.f18, dir.f18, main.f18 |
v4/tools/mkrom.c (create, replaces mkimage.c) |
Off the node: assemble capsules/v4/rom/*.f18 with text.c at V4_ROM_BASE. Write v4/build/rom-<w>.c (the binary, as const v4_cell v4_rom_<w>[]) and v4/build/rom-<w>.sym (name, address) for the tests. |
v4/tests/test_rom_*.c (create) |
One per ROM part, against the oracle |
v4/tools/v4tof18.py (create; run once, then deleted in Task 9) |
Convert v4/capsule/*.v4 to capsules/v4/sys/*.f18 |
capsules/v4/sys/*.f18 (create, generated once, then source) |
The system's primitives: today's nucleus, as .f18 blobs |
capsules/v4/sys/map-32.f18, map-64.f18 (create) |
The memory map's constants (today host_load's v4_text_constant calls), and the switch-on stores and image record (today mkimage.c's main) |
v4/system/boot.c (modify) |
Attach the ROM, feed the .f18 blobs to the console input, wait for the ROM's start, then go on as today with .4th |
v4/include/v4/image.h, v4/src/image.c (modify) |
v4_image read from the image record the .f18 source stores in RAM, not from a generated C file |
v4/Makefile, kernel/Makefile (modify) |
Build the ROM, drop the nucleus image |
| Deleted at the end | v4/tools/mkimage.c, capsules/v4/nucleus-64.f18, v4/capsule/*.v4, v4/tools/v4tof18.py |
The ROM's language
These are the generic directives that let the system's source say what mkimage.c and
text.c's header say today. Each is a machine notion: an address, a cell, a value. None is
FORTH.
| Directive | Effect |
|---|---|
everything in text.h's notation |
as text.c, bit for bit: opcodes, numbers, constants, CONST+N, &NAME, : NAME, labels, branches, macro … endmacro, FOR/NEXT/UNEXT, \ and ( comments |
constant NAME VALUE |
as v4_text_constant |
set NAME VALUE |
an assembler variable: like a constant, but may be set again |
here |
as a VALUE: the next word address (v4_text_here) |
#, VALUE |
one cell of data at here (text.c put_cell) |
", TOKEN |
TOKEN as a counted string: count byte, then the characters, four to a cell, low byte first, zero-padded to a whole cell (as header lays a name today) |
store ADDR VALUE |
ADDR := VALUE, anywhere in RAM (the switch-on state) |
start NAME |
the end of the system's .f18: as v4_text_finish, then zero the ROM's scratch, then jump to NAME |
VALUE is a number, a constant, a set variable, CONST+N, here, or &NAME.
Today's header NAME f1 f2 : CODE becomes, mechanically (Task 9):
set (NFA) here ", NAME #, F #, (NFA) #, (LINK) set (LINK) here
: CODE
Here F is the flags OR'd together: immediate 1, data 4, inline 8, compile-only 16 (text.c
flag_of). At the end, store puts (LINK) into (LATEST). That is exactly the memory
header lays today.
Task 1: The ROM range in the engine
Files:
- Modify:
v4/include/v4/node.h,v4/src/node.c,v4/src/exec.c(instruction fetch) - Test:
v4/tests/test_rom_range.c
Interfaces:
-
Produces:
V4_ROM_BASE(=V4_NODE_WORDS),V4_ROM_WORDS(build parameter, default 4096);void v4_node_rom_attach(v4_node *n, const v4_cell *rom, unsigned words);. A load or instruction fetch atV4_ROM_BASE + igivesrom[i]. A store there callsv4_node_fault(n, V4_FAULT_ADDR, addr).v4_node_addr_okaccepts the ROM range for loads only.v4_node_resetkeeps the ROM attached: it is the machine, not state. -
Step 1: Write the failing test
/* test_rom_range.c -- the ROM is the machine's: it can be read and run, never written. */
#include "v4/asm.h"
#include "v4/exec.h"
#include "v4/testcode.h"
#include <stdio.h>
static int failures = 0, checks = 0;
#define CHECK(c,...) do{checks++; if(!(c)){failures++; printf("FAIL %s:%d: ",__FILE__,__LINE__); printf(__VA_ARGS__); printf("\n");}}while(0)
static v4_node n; static v4_exec_state es; static v4_heat h;
static v4_cell rom[8];
int main(void)
{
v4_asm as;
/* ROM word 0: 7 ; */
v4_node_reset(&n);
rom[0] = 0; rom[1] = 0;
{ v4_node scratch; v4_asm ra;
v4_node_reset(&scratch);
v4_asm_begin(&ra, &scratch, 0);
v4_asm_lit(&ra, 7); v4_asm_op(&ra, V4_OP_SEMI);
CHECK(v4_asm_ok(&ra), "ROM code assembles");
rom[0] = scratch.mem[0]; rom[1] = scratch.mem[1];
}
v4_node_rom_attach(&n, rom, 8);
CHECK(v4_node_load(&n, V4_ROM_BASE) == rom[0], "ROM reads");
/* RAM 16: call ROM, then ; */
v4_asm_begin(&as, &n, 16);
v4_asm_branch(&as, V4_OP_CALL, V4_ROM_BASE);
v4_asm_op(&as, V4_OP_SEMI);
CHECK(v4_asm_ok(&as), "a call into the ROM assembles");
CHECK(testcode_run(&n, &es, &h, 16) == 7, "code in the ROM runs: left %ld", (long)n.t);
v4_node_store(&n, V4_ROM_BASE + 1, 99);
CHECK(v4_node_load(&n, V4_ROM_BASE + 1) == rom[1], "a store into the ROM changes nothing");
CHECK(n.fault_kind == V4_FAULT_ADDR, "and is an address fault");
v4_node_reset(&n);
CHECK(v4_node_load(&n, V4_ROM_BASE) == rom[0], "reset keeps the ROM");
printf("test_rom_range: %d checks, %d failures\n", checks, failures);
return failures != 0;
}
(testcode_run, n.t and n.fault_kind are the names v4/include/v4/testcode.h and
node.h use. Before writing, read both and use their exact names. If the fault is observed
through the fault table rather than a field, check it the way v4/tests/test_guard.c does.)
- Step 2: Run it and see it fail.
make -C v4 testshould fail to compile withv4_node_rom_attachundeclared. - Step 3: Implement. In
node.h, declare the API above and addconst v4_cell *rom;andunsigned rom_words;tov4_node. Innode.c,v4_node_load,v4_node_fetchand the instruction fetch inexec.cmapV4_ROM_BASE <= a < V4_ROM_BASE + rom_wordstorom[a - V4_ROM_BASE].v4_node_storefaults there.v4_node_resetsaves and restoresrom/rom_wordsaround its clear. Also check thatv4_iword_slot_maskwithtext.c's placement rule still places branches correctly when the address space isV4_NODE_WORDS + V4_ROM_WORDS. Ifplace_branchusesV4_NODE_WORDS - 1as "the whole node", change it to the top of the ROM range. - Step 4: Run it and see it pass.
make -C v4 test test-32: every test passes at both widths. - Step 5: Commit.
feat(v4): the machine has a ROM -- read and run, never written
Task 2: mkrom, the off-node ROM build, and its first part: input
Files:
- Create:
v4/tools/mkrom.c,capsules/v4/rom/in.f18,v4/tests/test_rom_in.c - Modify:
v4/Makefile(rule:$(BINDIR)/rom-<w>.cand.symfromcapsules/v4/rom/*.f18, per width; the ROM tests linkrom-<w>.c)
Interfaces:
- Produces:
extern const v4_cell v4_rom_64[]; extern const unsigned v4_rom_64_words;(and_32). Inrom-<w>.sym, oneNAME ADDRESSper line. A test helper inv4/tests/rom_harness.h:v4_cell rom_addr(const char *name);void rom_boot(v4_node *n);(attach the ROM and the console input),int rom_call(v4_node *n, const char *name, unsigned max_steps);(push a return to an idle word, set P, run until it returns; 1 if it returned). - The entry table (MACHINE.md 2.1).
main.f18starts atV4_ROM_BASEwith onejumpper public routine, in a fixed order:(KEY) (TOKEN) (NUMBER) (FIND) (DEFINE) (OP) (LIT) (BRANCH) (BRANCH>) (RESOLVE) (LABEL), then the ROM's entry. Each task adds its routines' entries. Tests call routines only through the entry table, as a payload would.rom-<w>.symstill names every internal routine, for debugging. - State area. The routines' state (
(TOK),(LINE), the packer's(HERE) (CUR) (SLOT) (LITS) (NLIT)) lives in a reserved RAM area, a constant of the memory map next tohost_map.h's others, outside dictionary space. The assembly-only tables (Task 4's symbol table and pool, Task 6's references) live in dictionary space. - ROM routines from
in.f18(the console input isCONSOLE-RX/CONSOLE-STATUS, ashost_map.h):(KEY) ( -- c ): wait while CONSOLE-STATUS is 0, then fetch CONSOLE-RX.(TOKEN) ( -- len ): skip blanks (space, tab, CR, LF), counting LFs into(LINE). Read up to the next blank into(TOK), at most 63 characters, then NUL.\skips to end of line and(skips to), astext.cnext_token. Length 0 means the end of input was marked by EOT (4), which the loader sends after the last blob. A token longer than 63 is the error "name too long".
- Step 1: Write the failing test. In
test_rom_in.c, feed" dup \\ c\n ( x ) over\t; \x04"withv4_node_console_feed. Call(TOKEN)four times and expectdup,over,;, then length 0. Expect(LINE)to be 1 after the first. Also feed nothing, run(TOKEN)for 10000 steps and expect it to be still waiting with P inside(KEY)(Review Focus 5). Then feed"x\x04"and expect it to finish withx. - Step 2: Run it and see it fail (no
mkrom, norom-64.c). - Step 3: Implement. Write
mkrom.cfrommkimage.c'smain:v4_text_begin(&tx, &n, V4_ROM_BASE), the ROM's constants (CONSOLE-RX,CONSOLE-STATUS, and the scratch addresses below), assemble eachcapsules/v4/rom/*.f18in a fixed order,v4_text_finish, then write the words fromV4_ROM_BASEtohereas the C array and every symbol as.sym. Writein.f18. The ROM's scratch RAM is in dictionary space, which is free during assembly:(TOK)16 cells,(LINE), and the tables of Task 4. Put their addresses as constants inmkrom.c, next tohost_map.h'sDICT_W. - Step 4: Run it and see it pass, at both widths.
- Step 5: Commit.
feat(v4): mkrom -- the ROM built off the node; it reads tokens
Task 3: Numbers
Files: Create capsules/v4/rom/num.f18, v4/tests/test_rom_num.c
Interfaces: (NUMBER) ( -- n flag ) on (TOK): flag 1 if the whole token is a number, 0
if not, -1 if it is a number that does not fit a cell. Same rules as text.c number_of:
optional $ for hex (either case of a–f) or a leading -; it must fit as signed or as unsigned.
- Step 1: Write the failing test. A table of tokens and expected
(n, flag). Run each throughtext.c's rule as well, so the test cannot drift from it:0 1 -1 $ff $FF $7FFF… (max) $1…0 (max+1, flag -1) -$1 (flag 0) 1a (0) - (0) $ (0), at the width under test. Feed each token with EOT, call(TOKEN)then(NUMBER), and compare. - Step 2: Run it and see it fail.
- Step 3: Implement
(NUMBER)innum.f18. Multiply by 10 or 16 with+*, or with shifts and adds. Detect overflow pernumber_of: unsigned beyond~0, or a negative beyondV4_MSB. - Step 4: Run it and see it pass, at both widths.
- Step 5: Commit.
feat(v4): the ROM reads numbers as text.c does
Task 4: The symbol table
Files: Create capsules/v4/rom/sym.f18, v4/tests/test_rom_sym.c
Interfaces:
- Memory:
(SYMS), records ofname-offset kind value;(POOL), packed name characters;(NSYM),(POOLN). Capacity matchestext.h: 2048 symbols, a 65536-character pool. Kinds: 1 word, 2 constant, 3 macro, 4 set variable. (FIND) ( -- rec | 0 )finds(TOK), newest first, astext.cfind.(DEFINE) ( value kind -- )defines(TOK)withtext.cdefine's refusals: an opcode, a reserved word (text.cis_reserved, plusconstant set here #, ", store start), a label form, a number, a name starting&, "already defined" (except kind 4 redefining kind 4), "too many names". On error it stores the message and line in(ERR)/(ERRLINE)and sets(FAILED).(OPCODE) ( -- op | -1 )matches the 32 mnemonics (text.cmnemonic[]).- Step 1: Write the failing test. Define
Aconst 5,Bword 100,Cmacro, and find each. ThenAagain (error "already defined"),dup(error "cannot be used as a name"),x:,12,&y, andset V 1thenset V 2(both allowed, find gives 2). For each case, run the same input throughtext.cand require the same failed/ok outcome (Review Focus 3). - Step 2: Run it and see it fail.
- Step 3: Implement
sym.f18. - Step 4: Run it and see it pass, at both widths.
- Step 5: Commit.
feat(v4): the ROM's symbol table
Task 5: The slot packer
Files: Create capsules/v4/rom/pack.f18, v4/tests/test_rom_pack.c
Interfaces: (OP) ( op -- ), (LIT) ( v -- ), (BRANCH) ( op target -- ),
(BRANCH>) ( op -- ref ), (RESOLVE) ( ref target -- ), (LABEL) ( -- addr ). The state is
(HERE) (CUR) (SLOT) (LITS)×6 (NLIT) in scratch. The behaviour is asm.c exactly: @p plus
the literal after the word, ; and ex close the word, a branch closes it and goes to a new
word from slot 4 or 5, the reach check, plus text.c place_branch (a branch only in a slot
that reaches the whole address space).
- Step 1: Write the failing test. Generate 2000 random sequences of op/lit/branch/label
with a fixed seed. Apply each with
asm.c+place_branchinto node A, and with the ROM routines into node B. Compare RAM word for word. Add a fixed case where a forward branch from slot 3 cannot reach: both must fail (Review Focus 2). - Step 2: Run it and see it fail.
- Step 3: Implement
pack.f18. - Step 4: Run it and see it pass, at both widths.
- Step 5: Commit.
feat(v4): the ROM's slot packer, word for word with asm.c
Task 6: Words, labels, branches by name, macros, FOR
Files: Create capsules/v4/rom/word.f18, v4/tests/test_rom_word.c
Interfaces: (ONE) ( -- ) assembles the token in (TOK), as text.c one_token does
for everything except the directives: :, LABEL:, branch mnemonics with their target,
labels local to a word and settled at end_word, a bare name as a call (now or forward),
&NAME, a number, a constant, CONST+N, a macro (expanded through a reader stack 8 deep),
FOR/NEXT/UNEXT. The forward-reference table is text.h's V4_TEXT_MAX_REFS.
- Step 1: Write the failing test. For each of
v4/tests/test_text.c's source texts (reuse them: same inputs, same expected outcomes), assemble withtext.cinto A, and with the ROM fed the text plusstartinto B. Compare RAM, and the failed/ok outcome and the message. - Step 2: Run it and see it fail.
- Step 3: Implement
word.f18. - Step 4: Run it and see it pass, at both widths.
- Step 5: Commit.
feat(v4): the ROM assembles words, labels, macros and FOR
Task 7: The directives and start
Files: Create capsules/v4/rom/dir.f18, capsules/v4/rom/main.f18,
v4/tests/test_rom_dir.c
Interfaces: constant set here #, ", store start, as in "The ROM's language" above.
main.f18 holds the ROM's entry at V4_ROM_BASE: clear its scratch, then loop (TOKEN) /
directive or (ONE), stopping at an error or at start. On error it writes (ERR) to
CONSOLE-TX and halts at a known address (ROM-FAILED). start NAME does text.c
v4_text_finish (resolve every reference, "never defined"), zeroes the assembly-only tables (Review
Focus 4), and jumps to NAME.
- Step 1: Write the failing test. Five cases:
#, 5 ", DUP store 9000 7 set L here #, Lwithstartto a word that does;: check each cell.- A blob split over two feeds, with an EOT only after the second, and a forward call resolved by a word in the second (Review Focus 1).
- A reference never defined: error, and P at
(ROM-FAILED). - After
start, every assembly-only table cell is zero, and the state area is as the last routine left it. - A header written in the directive form gives the same memory as
text.c'sheaderfor the same name and flags.
- Step 2: Run it and see it fail.
- Step 3: Implement
dir.f18andmain.f18. - Step 4: Run it and see it pass, at both widths.
- Step 5: Commit.
feat(v4): the ROM's directives and start
Task 8: The proof — the ROM rebuilds today's nucleus exactly
Files: Create v4/tools/v4tof18.py, capsules/v4/sys/*.f18 (generated by the script),
capsules/v4/sys/map-32.f18, map-64.f18, v4/tests/test_rom_nucleus.c
Interfaces: v4tof18.py reads v4/capsule/*.v4 in mkimage.c's order (core input dict codegen compile quit forth numout words system qmath blocks log acl) and writes one .f18 per
file. It rewrites header NAME flags : into the directive form and leaves everything else
token for token. map-<w>.f18 holds:
-
a
constantfor eachhost_loadconstant; -
stores for every switch-on valuemkimage.cmainsets (DP LATEST STATE CFP BASE FENCE LOG_LEVEL CONTEXT CURRENT SRC BOOT_CELLS LINE_STATUS,HOST_MESSAGES_EMPTY's cells); -
the image record (Task 9);
-
last,
start (IDLE). -
Step 1: Write the failing test.
test_rom_nucleus.cbuilds node A the waymkimage.cdoes (host_load,v4_text_finish,main's stores) and node B by feeding the ROMmap-<w>.f18(constants), the 14sys/*.f18files in order, then the rest ofmap-<w>.f18, then EOT. Run B until P reaches(IDLE). Require every RAM word equal, A to B, outside the ROM's state area. On a difference, print the first address and the symbol at or below it. -
Step 2: Run it and see it fail.
-
Step 3: Write
v4tof18.py, generate the files, and fix the ROM until the memory is equal at both widths. Fix the ROM, not the test. The test istext.candmkimage.cas they are. -
Step 4: Run it and see it pass, at both widths.
-
Step 5: Commit.
feat(v4): the ROM assembles the system's .f18 to the nucleus, word for word
Task 9: Boot from the ROM; the nucleus binary goes
Files:
- Modify:
v4/system/boot.c(replaceload_nucleus),v4/include/v4/image.h,v4/src/image.c,v4/tools/hosted.c,kernel/src/v4/sk_v4.c,v4/Makefile,kernel/Makefile,capsules/v4/hera.4th((NUCLEUS): see the note below) - Delete:
v4/tools/mkimage.c,capsules/v4/nucleus-64.f18,v4/capsule/*.v4,v4/tools/v4tof18.py,v4/tests/test_rom_nucleus.c's node-A half (it built from.v4; from here the.f18are the source, and the test becomes "boot reaches(IDLE)with POST passing")
Interfaces:
v4_imageis filled from the image record: a fixed RAM block whose address is a constant of the map, whichmap-<w>.f18stores. It holdsidle,fault_table,key_start,key_endand the register addressesv4_imageholds today.v4_image_from_node(const v4_node *n, v4_image *im)reads it.boot.c:v4_node_rom_attach(n, v4_rom_<w>, v4_rom_<w>_words), P =V4_ROM_BASE.- Feed
v4:sys/map-<w>.f18(its constants part), then the system.f18blobs, then the rest of the map, then EOT, each found, hash-checked and signature-checked asNUCLEUS.md5.3 items 1–3. - Run until P = idle. On
(ROM-FAILED), print the ROM's message, thenPARITY:FAILandPOST: FAILED. v4_image_from_node, then the.4thcapsules, POST andCOLDexactly as today.
PARITY:V4_NUCLEUSbecomesPARITY:V4_ROM words=N rom_hash=0x…plus onePARITY:V4_F18 name=… capsule_hash=…per blob.- Note on Hera: a node Hera births is a v4 machine too, so it has its ROM.
(NUCLEUS)streams the system.f18blobs to it as text, not the nucleus binary. Ifhera.4th's tests (test_host_unit.c) need more than that change, stop and report before going further. - Step 1: Write the failing test.
make -C v4 hosted-checkwithnucleus-64.f18removed from the capsule directory must still boot toPARITY:OKon all three hosted ISAs, withPARITY:V4_SYSTEM dict_hash=equal to the value before this task. Run it first to record that value. - Step 2: Run it and see it fail (no nucleus).
- Step 3: Implement the boot change and the deletions.
- Step 4: Run it and see it pass.
make -C v4 test test-32 hosted-check. - Step 5: Acceptance. Read
.claude/CLAUDE.md"Running / Acceptance" first. Then:git status --short diskmust be clean.make -f kernel/Makefile ARCH=amd64 clean qemu, thenaarch64, thenriscv64: one QEMU at a time, in the foreground, each took>. Every log must show POST 538/538,POST: PASSED, and the samedict_hashas before this task.make -C v4 test-32: 0 failures.- Commit the logs plain (memory
git-lfs-logs-plain).
- Step 6: Commit.
feat(v4): the machine boots from its ROM; the prebuilt nucleus is gone
What this plan does not do (later plans, MACHINE.md section 6)
- Step 3,
init.4thafter POST. - Step 5, FORTH-79 moved out of
.f18intoforth79.4th(all 130 names), plusstarforth.4th. - Step 6, v3 out of the build.
- Step 4 beyond the note in Task 9.