Home
JAQForum Ver 24.01
Log In or Join  
Active Topics
Local Time 01:06 08 Aug 2026 Privacy Policy
Jump to

Notice. New forum software under development. It's going to miss a few functions and look a bit ugly for a while, but I'm working on it full time now as the old forum was too unstable. Couple days, all good. If you notice any issues, please contact me.

Forum Index : Microcontroller and PC projects : Claude Fable in action

Author Message
matherp
Guru

Joined: 11/12/2012
Location: United Kingdom
Posts: 11721
Posted: 10:23am 07 Aug 2026
Copy link to clipboard 
Print this post

Fable is the sanitised version of Mythos from what we understand
Me:
  Quote  Fuzix is now reliable and sized sensibly to run on a RP2350B with PSRAM. The C compiler has been optimised and the library that runs programs bcrun has been reduced in size. The next steps for this project are to increase the scope of MMBasic coverage in the transpiler/compiler/runtime chain. Hopefully, this won't change the compiler, just the transpiler and runtime.
At the moment there are three ways Basic commands are handled.
1. Fully inbuilt
2. C header containing static functions with the compiler supporting dead code elimination (e.g. graphics)
3. spawned program (e.g. PLAY MP3)
I want you to conduct a comprehensive review of coverage compared to MMBasic. The first part of the review should be to look at what is covered now and ensure that it in the correct build category as defined above. The more that is in 2 or 3 the smaller each individual program will be. The second part of the review is to look at all MMBasic functionality that isn't already covered and allocate it to one of the three categories OR to identify it as 4. functionality that will never be supported and isn't needed, or 5: functionality that is large and complex and where the rational for support is more tenuous (e.g. the 3d engine). Please conduct the review and report findings. Make no changes to the current code.

Response:
  Quote  mmb2c → Fuzix build & link model on the Pico Computer 3 (RP2350B)
1. How a BASIC program is built on Fuzix
The pipeline
On the board (FUZIX-PC3-MANUAL.md:759-765):


mmbc prog.bas   -> prog.c      (translator; C rewrite of mmb2c.py)
cc prog.c       -> prog.bc     (ccbc.c driver: cpp -> cc0 -> cc1 -> cc2)
./prog.bc                      (runs via "#!/usr/bin/bcrun" shebang)
On the development host, the same chain is driven by fcc/fccbuild.sh:

mmb2c.py --fcc -o $W/$b.c — fcc/fccbuild.sh:43
gcc -E -P -nostdinc … -DMM_FCC -I fcc/include -I hosttest/ctest-include — fcc/fccbuild.sh:58-61
cc0 (tokenize) :65, cc1 (front end / IR) :71, cc2 .symtmp armm0 0 (backend, writes the loadable object directly) :80
prepend #!/usr/bin/bcrun, chmod 755 — fcc/fccbuild.sh:90-91
optionally bcrun $W/$b.bc — :95
There is no assembler and no linker on this target — ccbc.c:9-14: "This target has neither: cc2 writes a loadable object directly, so there is nothing to link and one source file is one program." Hence FUZIX-PC3-MANUAL.md:1317: "One .bas file per program, because cc compiles one file per program."

cc targets armm0 — ccbc.c:286-289. The object is mixed bytecode + native Thumb spans (PC3-C-MANUAL.md:38-40, PLAN-arm-backend.md:17-22).

What bcrun is, exactly
Both: it is a bytecode/native-mixed runner, and it is where the mm_* runtime physically lives. It is not a shared library — it is a full Fuzix process image, one per running program:

REVIEW-2026-08-06.md:135 — "Every running .bc program is one full bcrun process via the #! line."

bcrun contains the whole mm runtime, compiled in as source:

Applications/CC/bcrun_mm.c:87-88 — #define MM_HOSTED 1 then #include "mmb_runtime.c", and bcrun_mm.c:2-7: "Included from the end of bcrun.c so everything stays one translation unit."
bcrun_mm.c:9-11 and fcc/sync-runtime.sh:11 — the masters live in mmb2c; sync-runtime.sh copies mmb_runtime.c/.h, mmb_gfx.h, mmb_gpio.h into $FUZIX/Applications/CC/.
How a compiled program reaches mm_* functions
Via named libcalls resolved at load time — not static linking, not a shared library, not the kernel.

BYTECODE.md:66-71 — "BC_LIBCALL n calls runtime library function n. There is no linker, so the library … is provided by the interpreter rather than linked in. … it is the mechanism a BASIC front end would use for its own runtime too."
bcrun.c:2445-2461 — at load, any BC_CALL fixup naming a symbol this module never defines and typed BC_SYM_LIB is rewritten in place to BC_LIBCALL + two BC_NOPs.
bcrun_mm.c:438-640 — mmwtab[], a 196-entry name→wrapper table ({"mm_tmp", w_tmp} … {"mm_lfree", w_lfree}), scanned by mm_wrap_lookup() (bcrun_mm.c:641-649).
bcrun.c:169, bcrun.c:2355, bcrun.c:2145-2153 — libbind[] caches the resolved wrapper per symbol, so "the name is matched once per run, not once per call" (bcrun.c:163-168).
Program-visible runtime state (scratch string pool + by-ref pool, 4,240 bytes) is carved out of the program's own VM memory: bcrun_mm.c:669-704 (mmrt_bytes() / mmrt_reserve()), called from bcrun.c:2366.
Empirically minimal: Print "hi" translated with --fcc imports exactly three runtime names — mm_mark, mm_pr_s, mm_pr_nl.

The kernel-exported libm is a separate mechanism — it carries libm only, not mm_*
platform-rpipico/libm_table.c:49-64 — 19 doubles (sin…fmod) exported from kernel flash, address handed out by PICOIOC_LIBM (libm_table.c:69-72).
Rationale libm_table.c:12-18: no MMU/MPU, PROGBASE is an array inside the kernel's address space, so userland calls them with "an ordinary bl, not a syscall", and "the kernel's copy lives in flash, so it costs no RAM at all."
Consumed by bcrun.c:1666-1695 (mfns_share()), fatal if absent (bcrun.c:1678).
The mm runtime's own direct maths calls are macro-routed into that table: bcrun_mm.c:75-78 (sqrt→mfns[9], log10→12, pow→16, atan2→17), which removed ~5K of duplicated libm per process (bcrun_mm.c:61-74).
Phase 0 vs phase 1 (historical, still reachable)
Phase 0 concatenated mmb_runtime.c with the program: "~45K of every .bc is the runtime, recompiled every time" — fcc/PLAN-fuzix.md:75-77.
Phase 1 (done 2026-07-30, on hardware) moved it native into bcrun — PLAN-fuzix.md:137-144. Result: "programs shrink ~4.7× (t-tests 74K → 15.7K; the eclipse 163K → 103K)" — PLAN-fuzix.md:166-167.
The old shape survives for differential debugging: RTBC=1 fccbuild.sh — PLAN-fuzix.md:172, fcc/fccbuild.sh:49-52.
2. Linking granularity
mmb_runtime.c: no per-function GC, in either build
build how mmb_runtime.c is compiled granularity
Board / bcrun (production) #include "mmb_runtime.c" into bcrun.c's single TU (bcrun_mm.c:87-88) All of it, always. Not linked per program; it is 26.9K of bcrun's text (REVIEW-2026-08-06.md:142) and every .bc process pays it whether the program is BASIC or plain C.
Host gcc reference $(CC) $(CFLAGS) -o $@ $< mmb_runtime.c $(LDLIBS) — mmb2c/Makefile:29, :44, :94 Whole .c on the command line, no -ffunction-sections/--gc-sections ⇒ entire object linked in.
Phase-0 / RTBC=1 cat mmb_runtime.c prog.c > prog.one.c — fcc/fccbuild.sh:51 Whole runtime compiled as bytecode into the program. Its functions are extern, so the cc1 dead-static rule (below) cannot touch them. PLAN-fuzix.md:76 measured ~45K.
Key consequence for the parent review: on the board the runtime cost is per-process, not per-program-file. Shrinking mmb_runtime.c does not shrink prog.bc; it shrinks the ~104.5K bcrun floor that every concurrent .bc process pays (see §6).

The header/DCE approach: confirmed, and it does work
Both headers are all-static:

mmb_gfx.h:47, 57, 79, 114, 115, 136, 184, 370, 375, 425, 500, 532 — every function static, plus two static short arrays.
mmb_gpio.h:34, 45, 53 — mmg_setpin, mmg_pin_put, mmg_pin_get, all static.
Design rationale, stated in the headers themselves:

mmb_gfx.h:5-10 — "These are NOT in bcrun. bcrun is loaded for every translated program and shares one 256K process … so a byte added there is a byte taken from every program on the machine."
mmb_gfx.h:13-17 — "Every function is static, and cc1 generates nothing for a file scope static that nothing else names (hosttest/deadstatic.sh) … There is no linker on this target to do that afterwards, which is why the compiler learned to do it."
mmb_gpio.h:8-17 — same argument for SETPIN/PIN; only the ioctl crossing (mm_gpio) stays in the runtime "the on-board cc has no ioctl, so the crossing itself cannot live in a header."
The compiler really does eliminate them. Implementation:

Applications/CC/body.c:492-502:

dead = (st == S_STATIC && name_used_once(name));
if (dead) out_off++;
with the comment "A file scope static whose name occurs exactly once in the token stream … can have no caller … parse it for its errors and generate nothing. That is what lets a header carry a library of helpers and a program pay only for the ones it uses."
Applications/CC/lex.c:42-87 — name_used_once(): a name-occurrence count over a pre-pass of the token stream, two saturating bits per name, 16,384 names tracked. lex.c:54-58: "Counting names rather than resolving references … errs the safe way … It can waste code, never lose it."
Regression test Applications/CC/hosttest/deadstatic.sh:76-81 asserts: unused static costs 0 bytes; used static is kept; recursive static kept; address-taken static kept; ten statics with one used costs < 3× one.
Three real limits of the DCE, worth flagging
It is not transitive reachability. A dead static that is named by another dead static counts twice and survives. In mmb_gfx.h: mmg_circle calls itself (mmb_gfx.h:208-209), so its name count is ≥ 3 and it is always generated once the header is included; mmg_ring (named at :211), mmg_extent (:147, :149), mmg_pt/mmg_rc (named throughout mmg_circle), mmg_upper (named at mmb_gfx.h:379) all survive for the same reason. Only the leaf entry points — mmg_text, mmg_just, mmg_map_maximite, mmg_map_greyscale — can actually be dropped. So a program that uses only TEXT still carries the whole circle machinery.
It applies to functions only. body.c:500 is the sole call site of name_used_once (grep across Applications/CC/*.c). mmg_eo[481] and mmg_ei[481] (mmb_gfx.h:114-115) are ~1,922 bytes of BSS charged to any program that includes the header, contrary to the header's own comment at mmb_gfx.h:112-116.
The translator's include gate is one flag per header, not per function — mmb2c.py:4052-4055 keyed on self.uses_gfx / self.uses_gpio (set at mmb2c.py:1189, 2418, 2454, 2475, 2488, 2508, 2512). Comment at mmb2c.py:4047-4051 acknowledges this: "One flag for the whole header … and the compiler sorts out which."
3. Inventory of mmb_runtime.c
3,897 lines / 125,069 bytes. 196 entry points are exported as libcalls (bcrun_mm.c mmwtab). Compiled size in bcrun: 26.9K text (REVIEW-2026-08-06.md:142).

Line spans are from function-definition boundaries in the file.

Group A — paid by essentially every program (~370 lines / ~10%)
group lines span public functions
Scratch pool + by-ref stack ~67 50–116 mm_tmp(50) mm_hosted_bind(77) mm_mark(88) mm_release(93) mm_byref_f(99) mm_byref_i(107)
Core string ops ~65 117–181 mm_ssetn mm_sset mm_ssetc mm_scopy mm_scat mm_scmp
Number → string ~139 182–320 mm_int_to_str(182, 21) mm_int_to_str_pad(203, 32) mm_float_to_str(235, 86)
Console PRINT ~97 321–417 mm_putc mm_col(336) mm_pr_s mm_pr_i mm_pr_f(352, 30) mm_pr_nl(360) mm_pr_tab(361) mm_pr_se/ie/fe/tabe(388-391) mm_tab(394) + statics mm_puts_raw, mm_pr_end
Numeric helpers ~82 418–499 mm_toint mm_idiv mm_mod mm_pow(436) mm_atan3(439, 34) mm_int mm_fix mm_sgn mm_randomize mm_rnd
Group B — very common but not universal (~455 lines)
group lines span public functions
String functions ~189 500–688 mm_asc mm_instr mm_val mm_chr mm_left mm_right mm_mid mm_mid_assign mm_ucase mm_lcase mm_ltrim mm_rtrim mm_space mm_strrep mm_str_i mm_str_f mm_hex/oct/bin(685-687)
error / END / TIMER ~88 2307–2394 mm_error(2307) mm_end(2316) mm_timer(2324, 71)
GOSUB/RETURN ~14 2293–2306 mm_gosub_push mm_gosub_pop
PAUSE + µs clock ~74 1899–1972 mm_pause(1946, 51), static mm_us_now
heap ~49 2884–2932 mm_heap mm_lheap mm_lfree
Group C — only some programs; candidates to move to header/DCE or a spawned helper
group lines span public functions
Graphics, kernel-facing ~645 2920–3565 mm_fontinfo mm_map mm_map_set mm_map_reset mm_map_get mm_font mm_gtext mm_at mm_mode mm_pixel mm_pixel_get mm_plot mm_fill mm_line(3460, 54) mm_pixels(3514, 52) mm_hres mm_vres mm_colour mm_cls mm_fg mm_bg; statics mm_gfx_open mm_gfx_setcol mm_gflush mm_gputc(3162, 51) mm_gfx_dim mm_gfx_flush_pts mm_gfx_rect
Files (channels, I/O, position, read) ~252 1134–1385 mm_ls_file mm_open mm_close mm_close_all mm_fpr_s/i/f/nl/tab mm_eof mm_loc mm_lof mm_seek mm_getline mm_input_str mm_input_line mm_input_next mm_atoi mm_atof
File/dir management ~234 1386–1619 mm_kill mm_rename mm_copy mm_mkdir mm_rmdir mm_chdir mm_cwd mm_dir (3 variants: 1499 POSIX, 1533 Windows, 1587 MM_NO_DIRS stub) mm_files; static mm_wild(1474, 25)
LONGSTRING ~199 2094–2292 mm_ls_len mm_ls_clear mm_ls_append mm_ls_load mm_ls_copy mm_ls_concat mm_ls_left mm_ls_right mm_ls_mid mm_ls_replace mm_ls_resize mm_ls_setbyte mm_ls_trim mm_ls_ucase mm_ls_lcase mm_ls_print mm_ls_getstr mm_ls_getbyte mm_ls_instr mm_ls_compare mm_ls_input (21 functions)
INKEY$ / key decoding ~175 2395–2569 mm_inkey(2486, 84); statics mm_kpush mm_rd1 mm_esc_decode(2413, 73)
FRAMEBUFFER ~168 3730–3897 mm_fb_create mm_fb_close mm_fb_write mm_fb_copy mm_fb_wait + 8 _hw statics
Date / time ~159 888–1046 mm_epoch_now(895) mm_epoch_str mm_datetime mm_time_str mm_date_str mm_day; statics mm_civil_from_days mm_break_epoch mm_days_from_civil mm_parse_hms
Host-side graphics/GPIO stubs ~155 3590–3744 duplicate no-op mm_gpio mm_at mm_fontinfo mm_font mm_map* mm_gtext mm_line mm_mode mm_plot mm_fill mm_pixels mm_pixel mm_pixel_get mm_hres mm_vres — #else branch, not compiled on the board
FORMAT$ ~134 754–887 mm_format(797, 105); statics mm_decexp mm_striptz mm_fmt_exp
PLAY (fork/exec + device arbitration) ~101 2753–2853 mm_play_start(2803) mm_play_stop(2823, 41); static mm_play_owner
run-another-program (arg collection) ~86 1973–2058 mm_run_begin mm_run_arg mm_run_arg_i mm_run_arg_f mm_error_s; static mm_run_push
run-another-program (fork/exec) ~124 2626–2751 mm_run_exec(2657, 70) mm_run_bg(2727, 54) — plus 25-line MM_FCC && !MM_HOSTED stub set at 2624-2650
SORT ~95 1709–1803 mm_sort_i mm_sort_f mm_sort_s(1780, 24); statics mm_scmp_ci mm_sort_cmp_s mm_sort_range mm_seed_index
DATA / READ / RESTORE ~89 1620–1708 mm_data_init mm_data_init4 mm_restore mm_read_f mm_read_i mm_read_s mm_read_save mm_read_unsave; statics mm_d4_str mm_next_idx
BIN2STR$ / STR2BIN ~87 1047–1133 mm_bin2str(1049, 39) mm_str2bin_f mm_str2bin_i(1101, 33)
BYTE / TRIM$ / FIELD$ ~65 689–753 mm_byte mm_trim mm_field(732, 26); statics mm_base mm_in_mask mm_scan_delim
MATH() reductions ~59 1840–1898 macro MM_STAT_BODY ×2 ⇒ 12 public: mm_st_{sum,mean,sd,max,min,med}_{i,f} + static mm_kth_*
GPIO crossing ~55 3546–3600 mm_gpio(3566)
DATE$= / TIME$= ~35 2059–2093 mm_timer_set mm_set_date mm_set_time
Whole-array ops ~36 1804–1839 mm_arr_count mm_arr_set_{i,f,s} mm_arr_add_{i,f,s} mm_arr_scale_{i,f}
Migration shortlist by size, in Group C, that is pure arithmetic with no syscall — i.e. exactly the mmb_gfx.h bargain:
LONGSTRING (~199), INKEY$/escape decoding (~175 — the ESC decoder at 2413 is 73 lines of pure table walking), FORMAT$ (~134), date/time civil arithmetic (~159), SORT (~95), DATA/READ (~89), BIN2STR$/STR2BIN (~87), BYTE/TRIM$/FIELD$ (~65), MATH() reductions (~59), whole-array ops (~36). ≈ 1,100 lines of the 3,897 with no kernel dependency at all.
Files/dirs (~486 lines) need only open/read/write/opendir, all of which the bcrun libcall table already exposes by name (PC3-C-MANUAL.md:48-53), so they are movable too but require the libcall surface, not a header alone.

4. mmb_gfx.h and mmb_gpio.h inventories
mmb_gfx.h — 543 lines, 16,792 bytes
BASIC statement function lines how it reaches hardware
CIRCLE x,y,r[,lw[,aspect[,colour[,fill]]]] mmg_circle (:184) 146 Nothing directly. Batches into mm_plot / mm_fill only (:328, :326, :240)
(helper for thick borders) mmg_ring (:136) 30 mm_fill (:164)
(helper) mmg_extent (:79), mmg_pt (:47), mmg_rc (:57) 28+9+12 mm_plot (:52), mm_fill (:65)
TEXT x,y,s$[,just$][,font][,scale][,fg][,bg] mmg_text (:425) 59 mm_fontinfo (:447) and mm_gtext (:467, :482)
justification parsing mmg_just (:375), mmg_upper (:370) 32+4 none
MAP MAXIMITE mmg_map_maximite (:500) 26 mm_map ×16 + mm_map_set (:523-524)
MAP GRAYSCALE/GREYSCALE mmg_map_greyscale (:532) 10 mm_map ×16 + mm_map_set (:538-540)
Storage: mmg_eo[481], mmg_ei[481] — mmb_gfx.h:114-115, ~1,922 bytes BSS.
Contract: mmb_gfx.h:19-21 — "The only things reaching outside are mm_plot and mm_fill — a run of points and a run of rectangles — so however many primitives end up here, the kernel and bcrun stay the size they are." Batch size MMG_BATCH 32 (:37), max radius MMG_RMAX 480 (:45).

Translator dispatch: CIRCLE → mmb2c.py:2395-2419; TEXT → :2422-2455; MAP MAXIMITE/GRAYSCALE → :2508-2513.

Everything else graphical is still in the runtime/bcrun, not the header: PIXEL (mmb2c.py:2545 → mm_pixel), LINE (:2570 → mm_line, comment at :2573-2578 explicitly says "The geometry is in the runtime, not the kernel"), CLS (:2253), MODE (:2264), FONT (:2522), MAP SET/RESET/MAP(n)= (:2501-2506, :2520), PRINT @ (mm_at), FRAMEBUFFER (mm_fb_*). No BOX or BLIT dispatch exists in mmb2c.py despite COVERAGE.md:98 listing them.

mmb_gpio.h — 65 lines, 2,018 bytes
BASIC function reaches hardware
SETPIN n, DIN|DOUT mmg_setpin (:34) mm_gpio(MM_GPIO_DIR, …) (:38)
PIN(n) = v mmg_pin_put (:45) mm_gpio(MM_GPIO_PUT, …) (:50)
PIN(n) (function) mmg_pin_get (:53) mm_gpio(MM_GPIO_GET, …) (:61)
Range check 0 ≤ pin < MM_GPIO_NPINS (= 48, mmb_runtime.h:558) is done header-side; mode constants MMG_PIN_DIN/DOUT at mmb_gpio.h:31-32.

The crossing itself: mm_gpio (mmb_runtime.c:3566-3588) opens /dev/gpio (:3572) and issues MM_GPIOC_SETRW / MM_GPIOC_SET / MM_GPIOC_GETBYTE (0x0534 / 0x0531 / 0x0533, mmb_runtime.h:552-554). Why it can't be in the header: mmb_gpio.h:14-16 — "the on-board cc has no ioctl, so the crossing itself cannot live in a header."

Graphics ioctls, for comparison, all go through /dev/sys (mmb_runtime.c:2936): GFXIOC_MODE 0x0003, PIXEL 0x000F, COLOUR 0x0010, GETPIXEL 0x0011, RECT 0x0012, INFO 0x000E, PIXELS 0x0014, RECTS 0x0015, FBSEL 0x0016, FBCOPY 0x0017, FBOPEN 0x0018, VSYNC 0x0019 (mmb_runtime.c:2924-2928, 3322, 3359-3361, 3736-3739). Numbers are duplicated from pico_ioctl.h rather than included (mmb_runtime.c:2607-2609).

Translator dispatch: SETPIN → mmb2c.py:2476; PIN(n)= → :2489; PIN(n) → :1190.

5. Spawned-program mechanism
Mechanism: sync() → fork() → execvp(), no shell, no posix_spawn
Two entry points, both in mmb_runtime.c:

mm_run_exec() (:2657-2712) — waits. sync() (:2686), fork() (:2689), child execvp(mm_run_argv[0], mm_run_argv); _exit(127) (:2695-2696), parent waitpid(pid, &status, 0) (:2698), sync() again (:2701). Exit 127 → "no such program"; any non-zero → BASIC error (:2703-2710).
mm_run_bg() (:2727-2751) — does not wait. Same sync/fork/execvp, returns the pid (:2750). :2721-2723: "The child is left for init to reap … the alternative is carrying a SIGCHLD handler in every translated program."
Argument marshalling is an argv, not a command line — mm_run_begin / mm_run_arg / mm_run_arg_i / mm_run_arg_f (:1997-2045), caps MM_RUN_MAXARG 16, MM_RUN_TEXT 256 (:1989-1990). Rationale :1981-1984: "no quoting to get wrong, no shell process in the middle."

The sync() calls are a deliberate mitigation, guarded by MM_PC3 || __FUZIX__ (:2685, 2700, 2737): "A fork here swaps this process out — bcrun plus a loaded program is around 200K — and the filesystem corruption seen after SAVE IMAGE points at dirty blocks still sitting in the buffer cache" (:2670-2681).

Under MM_FCC && !MM_HOSTED (bytecode-compiled runtime, i.e. the RTBC gate path) all four become errors: "running a program needs the native runtime" (mmb_runtime.c:2624-2650).

PLAY MP3 specifically
Translation — mmb2c.py:2383-2393:


self.emit('mm_run_begin();')
self.emit('mm_run_arg(%s);' % c_string_literal('playmp3'))
self.emit('mm_run_arg(%s);' % v[0])          # the file name
self.emit('mm_run_arg_i(mm_play_volume);')
self.emit('mm_play_start();')
mm_play_start() (mmb_runtime.c:2803-2821): reaps the previous player with waitpid(WNOHANG) (:2810), asks the kernel who owns the audio stream, refuses with "sound output in use" if taken (:2813-2816), else calls mm_run_bg() (:2817).

Ownership arbitration is via the kernel, not a remembered pid — mm_play_owner() (:2781-2791) opens /dev/sys and issues MM_SNDIOC_PCMOWNER (0x0025, :2777). Rationale :2755-2766.

PLAY STOP → mm_play_stop() (:2823-2851): SIGINT to the owner (:2829), poll the device for up to 5×100 ms (:2841-2842), then SIGKILL (:2844).

The player binary: platform-rpipico/utils/playmp3.c (8,823 bytes source; playmp3 176,068 bytes, playmp3.stripped 30,448). Usage playmp3 file.mp3 [volume] (playmp3.c:4). It is the only program on the machine allowed hardware FP — utils/Makefile:32-38 (playmp3.o: CCOPTS += $(FPFLAGS)), because "no FP register state is saved across a context switch, so exactly one process on the machine may execute FP instructions … the device lock is the FPU lock" (PC3-C-MANUAL.md:90-97). Decoder is dr_mp3.h (208,336 bytes, vendored in utils/).

Design note PC3-MP3-PLAN.md:252-256: "Because it is a separate process, playback continues while a BASIC program runs on — there is no idle-loop refill, no checkWAVinput equivalent anywhere in the interpreter."

Other commands that already spawn
mmb2c.py:2312-2345 — one shared code path:

BASIC binary waits?
SYSTEM prog$[, arg…] whatever is named yes (mm_run_exec)
SAVE IMAGE f$[, x,y,w,h] saveimage (mmb2c.py:2328) yes
LOAD IMAGE f$[, x,y] loadimage (mmb2c.py:2328) yes
PLAY MP3 f$ playmp3 no (mm_play_start → mm_run_bg)
Binaries exist in platform-rpipico/utils/: saveimage (45,016 / stripped 5,160), loadimage (64,008 / stripped 9,500), playmp3 (176,068 / stripped 30,448).

The stated policy — mmb_runtime.c:1975-1979: "MMBasic is firmware and has nothing to run; this is a Fuzix machine and has a filesystem full of programs, so a BASIC command that wants real work done can hand it to one. SAVE IMAGE and LOAD IMAGE are the first, and neither costs bcrun or the calling program a byte, because the code is in a separate binary cross compiled with gcc."

PLAY VOLUME emits only static int mm_play_volume into the program's prologue, and only when the program plays something — mmb2c.py:4059-4064, described in PC3-MP3-PLAN.md:286-288 as "the mmb_gfx.h/mmb_gpio.h bargain."

6. On-board constraints
Process address space
quantity value source
TOTALMEM (cmake) 312 KB NOTES-process-memory.md:99 (-DPICO_BOARD=pico2 -DTOTALMEM=312)
USERMEM (TOTALMEM-NETMEM)*1024 = 319,488 config.h:210
UDATA_SIZE 3 << BLKSHIFT = 1,536 config.h:194-195; confirmed 1,536 in REVIEW-2026-08-06.md:145
PROGSIZE USERMEM - UDATA_SIZE ≈ 317,952 config.h:224
PROGLOAD &progbase[UDATA_SIZE] config.h:244
PROGTOP PROGLOAD + PROGSIZE config.h:245
process pool 320 KB pinned linker region at 0x20030000 NOTES-process-memory.md:71-74
pool as documented for users 340 KB; one process ≈ 292 KB measured (memprobe) PC3-C-MANUAL.md:118-119
max processes 64 PC3-C-MANUAL.md:123
default C stack / max 8 KB / 64 KB via -z stack-size=N config.h:231, 240; PC3-C-MANUAL.md:87-88
config.h:212-223 records that the old 262,144 ceiling was removed: "it stopped bcrun loading a 140K translated BASIC program on a 312K machine … A process this big leaves nothing else resident. That is fine: the others swap." Memory is packed in 4 KB chunks at actual size; swap is 8 MB of PSRAM (config.h:249, PC3-C-MANUAL.md:124-125).

No MMU — PC3-C-MANUAL.md:129-131: "A wild pointer corrupts the kernel and takes the machine down with no diagnostic. This is the single most important thing to know."

The bill for a running .bc program (REVIEW-2026-08-06.md:137-154)
item bytes
bcrun text+rodata — incl. 26.9K mm BASIC runtime, ~14K interpreter core, 5.2K relinked libm, ~2K softfloat, ~17K libc 67,796
ELF dynamic/reloc LOAD segment (recycled as stack — see R1 withdrawal, :158-165) 13,697
data + bss (2K getstr, 4K graphics batch, 2K profiling arrays, stdio) 13,460
udata 1,536
native C stack 8,192
floor before the program loads ~104.5 K (~31% of the pool)
Per program on top: code[] (Dhrystone 9.7K, eclipse 100.5K), data+bss, sym+strtab+bind kept for the whole run (eclipse 16.3K), and mem[] demand-sized as NULLGUARD + data/bss + 4,240 (mm pools) + 8K STACKROOM + 2K slack. Trivial utility ≈ 135 K total; the eclipse ≈ 250 K of 340 K (:149-153).

bcrun's own VM data space
MEMSIZE = 49,152 on the board (131,072 with BIG_TABLES on the host) — bcrun.c:63-71.
mem[] is malloc'd sized to the program, not to MEMSIZE, under MM_PC3 — bcrun.c:107-131 and bcrun.c:2301-2321. This replaced a static 48K array: "154,469 bytes of process image, of which 48K was this whether the program needed it or not. Two BASIC programs then nearly filled the 340K pool" (bcrun.c:110-114).
NULLGUARD 16, STACKROOM 8192, MEM_SLACK 2048 — bcrun.c:74, 75, 147.
Arrays and strings do not live in that 48K: they go to the PSRAM heap (bcrun.c:115-118, heap_init at bcrun.c:1281, PSRAMIOC_ALLOC at bcrun.c:1259-1274; FUZIX-PC3-MANUAL.md:915-918 — "a framebuffer-sized array — Dim Integer fb(4800), 38,408 bytes — is unremarkable now").
Native-code translation bails at: span > TMAX (48K on the board), native function > THUMB_MAXFN 40,000 bytes, LOCAL/ENTER offsets > 4095 — REVIEW-2026-08-06.md:29-31.
Bearing on "DCE in the program" vs "spawn it"
Anything left in mmb_runtime.c lands in bcrun's 67,796-byte text and is charged to every .bc process, including plain-C ones that never touch BASIC. The single largest reduction identified in the review is exactly this: R5, "Resident bcrun (text in flash, per-process data only) — up to ~68K per process", rated high effort / medium-high risk, and gated on "Only worth it if the roadmap needs >2 concurrent .bc processes" (REVIEW-2026-08-06.md:177-184). Moving code out of mmb_runtime.c into DCE'd headers attacks the same number at far lower risk.
Anything in a header is charged only to programs that include it, and only for the functions the name-count rule can drop (with the transitivity caveat of §2).
Anything spawned costs neither: mmb_runtime.c:1978-1979 — "neither costs bcrun or the calling program a byte" — but it costs a fork() of a 200K process (mitigated to "tens of milliseconds rather than seconds" by PSRAM swap, mmb_runtime.c:1986-1987), two sync()s, and it must be a cross-compiled native ARM binary built through utils/Makefile (PC3-C-MANUAL.md:69-85), not something cc on the board can produce.
Remaining low-risk reductions already identified: R2 (route direct pow/atan2/log10/sqrt through the kernel table, ~5.2K — already implemented, see bcrun_mm.c:75-78), R3 (lazy profiling arrays, 2K), R4 (eager libcall binding then free sym+strtab, ~12K on big programs) — REVIEW-2026-08-06.md:166-176. Explicitly left alone: MM_BATCH 512, the 4K graphics batch, the unconditional 4,240-byte mm pools, STACKROOM/USERSTACK (:191-197).
The MMBasic keyword universe is now mapped too: ~365 distinct keywords (~350 applicable to the RP2350B/HDMI reference platform, once WebMite networking, LCD-panel and USB-variant-only features are excluded), plus roughly 630 second-word sub-verbs across families like OPTION, MATH, PLAY, GUI, TURTLE and SPRITE. The extraction came from the PicoMite 6.03.01 working-copy tables, which are byte-identical to the V6.03.00 release.

Still waiting on the third agent, which is mapping the Fuzix build/link architecture — that one determines what each of the three build categories actually costs per program, which part 1 of the review hinges on. I'll produce the full synthesis when it lands.

All three inventories are in. Here is the full review. No code has been changed.

MMBasic coverage review — mmb2c → cc → bcrun on Fuzix/PC3
Reference universe: PicoMite 6.03.01 (tables byte-identical to V6.03.00): ~200 distinct command keywords, ~165 function/token keywords, plus ~630 second-word sub-verbs. After removing variants that don't apply to an RP2350B/HDMI-class machine (WebMite networking, LCD-panel drivers, USB-variant-only gamepad/keyboard commands), the applicable universe is ~190 commands and ~160 functions.

Current coverage: ~50 command keywords and 71 functions translated. The mmbc C port of the translator tracks the Python at near-parity (gated by byte-identical generated C), so translator-side additions land in both.

0. What the three categories actually cost (this reframes part 1)
The build model on the board: mmbc → cc (no assembler, no linker, one file = one program) → .bc run by bcrun. Since phase 1, mmb_runtime.c is not linked into programs — it is compiled natively into bcrun itself (26.9K of bcrun's 67.8K text), and programs reach it by named libcalls resolved at load. Every running .bc is a full bcrun process: the floor is ~104.5K of the 340K process pool before the program loads, paid by every .bc process — including plain C programs that never touch BASIC.

Category Cost per program file Cost per running process Speed Constraint
1. Inbuilt (bcrun runtime) zero inflates the ~104.5K floor for everyone native ARM (fast) 340K pool ÷ concurrent processes
2. Header, static + DCE only programs that use it only those programs cc-generated code (slower) cc1 drops unused file-scope statics — with limits (below)
3. Spawned binary zero zero (own process) native gcc ARM, may use FPU must be cross-compiled off-board; fork of a ~200K process + two sync()s
So the premise "more in 2/3 = smaller programs" is right in the sense that matters — process footprint — but the lever for category 1→2 moves is bcrun's floor, not the .bc file (which actually grows for the programs that use the feature). The trade per item is: universal floor vs pay-per-use vs pay-nothing-but-native-only, with a speed penalty attached to category 2.

Three real limits of the category-2 mechanism found in the compiler (body.c:492-502, lex.c:42-87):

DCE is name-counting, not reachability. A static that names itself or is named by another (even dead) static survives. Concretely: mmg_circle is self-recursive, so once mmb_gfx.h is included the entire circle machinery (mmg_circle/ring/extent/pt/rc, ~230 lines) is always generated — a program using only TEXT still carries it. Only the leaf entries (mmg_text, mmg_just, the two MAP palettes) can actually be dropped.
DCE applies to functions only. mmg_eo[481]/mmg_ei[481] cost ~1.9K of BSS in any program that includes the graphics header, contrary to the header's own comment.
The translator's include gate is one flag per header, not per feature.
None of these need compiler changes to fix — splitting mmb_gfx.h into per-feature headers (mmb_gfx_circle.h, mmb_gfx_text.h, …) with per-feature translator flags gets true pay-per-use, and the extent arrays can move inside the one function family that uses them.

Part 1 — is what's covered today in the right category?
Correctly placed — leave alone
Category 1, universal: scratch/by-ref pools, core string ops, IntToStr/FloatToStr, PRINT and column tracking, numeric helpers, error/END, heap, GOSUB stack (~825 lines, Groups A+B). Every program touches these; native speed matters; correct.
Category 1, correctly kept for other reasons: the kernel crossings themselves — mm_plot/mm_fill/mm_gpio and every /dev/sys ioctl — must stay in bcrun because the on-board cc has no ioctl. The spawn plumbing (mm_run_*) is the category-3 enabler and belongs where it is.
Category 2: CIRCLE, TEXT, MAP MAXIMITE/GRAYSCALE geometry in mmb_gfx.h; SETPIN/PIN in mmb_gpio.h. Right model, with the split-the-header caveat above.
Category 3: PLAY MP3 (background, kernel arbitrates ownership, only FPU-licensed process), SAVE IMAGE/LOAD IMAGE, SYSTEM. Textbook placements — "neither costs bcrun or the calling program a byte."
Misplaced — category 1 code that is pure arithmetic with no syscall (1→2 candidates)
About 1,100 of mmb_runtime.c's 3,897 lines have no kernel dependency at all and are used by only some programs:

Feature ~lines Notes
LONGSTRING family (21 fns) 199 pure buffer arithmetic
INKEY$ escape decoder 175 the 73-line ESC table-walk is pure; the one-byte read stays in bcrun
Date/time civil arithmetic 159 epoch↔civil conversion is pure; only "now" needs the OS
FORMAT$ 134
SORT (3 types) 95 speed caveat: cc-compiled sort over big arrays will be noticeably slower than native
DATA/READ/RESTORE 89
BIN2STR$/STR2BIN 87
BYTE/TRIM$/FIELD$ 65
MATH() reductions 59 same speed caveat as SORT
Whole-array ARRAY SET/ADD/SCALE 36
Also movable but needing the libcall surface rather than a header alone: file management (KILL RENAME COPY MKDIR RMDIR CHDIR DIR$ FILES + wildcards, ~486 lines — bcrun already exposes open/read/write/opendir by name), and graphics geometry that isn't a crossing (mm_line's 54-line Bresenham, mm_pixels, the 168-line FRAMEBUFFER block, ~400 lines net of the ioctl code).

The prior bcrun review rated shrinking resident bcrun as its biggest but riskiest item (R5, "only worth it if the roadmap needs >2 concurrent .bc processes"). These 1→2 moves attack the same 26.9K at much lower risk — but each one slows that feature down (native → cc-generated) and grows the .bc of programs using it. Given the pool math (trivial utility ≈135K, eclipse ≈250K of 340K), I'd move the cold, size-heavy items (LONGSTRING, FORMAT$, BIN2STR$, date/time arithmetic, escape decoder, file management) and leave SORT/MATH() reductions native, since they're exactly the things that get called in hot loops.

Other part-1 findings
BOX and BLIT do not exist. COVERAGE.md:98 claims they're translated; there is no dispatch for either anywhere in mmb2c.py, mmbc/, or the headers. Doc bug — and BOX is a genuine gap (it's trivial: one mm_gfx_rect).
Docs are stale in the other direction too: INKEY$, PIN/SETPIN, PIXEL, MAP, the whole graphics set, PLAY, the spawns, MM.HRES/VRES, the heap split and --fcc mode are all implemented but README's "Not yet"/"Currently translated" sections predate them.
PLAY VOLUME as an emitted-only-when-used static is the right pattern (the "mmb_gfx.h bargain" applied to a variable).
Part 2 — everything not covered, allocated
Legend: 1 inbuilt · 2 header+DCE · 3 spawned program · 4 never/not needed · 5 large/tenuous. † = also needs new kernel surface (ioctl or /dev node). Where a family is split, the members are split.

Language core
Feature Cat Rationale
TYPE/END TYPE, STRUCT members, STRUCT( 1 maps to C structs; translator-only except the known tokenizer change (a.b lexing). The largest single win left, per the project's own triage
ON ERROR SKIP/IGNORE, MM.ERRNO, MM.ERRMSG$ 1 cross-cutting error flag or setjmp in the runtime; can't be a header
REDIM [PRESERVE] 1 easier than COVERAGE.md says now: arrays already live in a mm_heap PSRAM block, so the "would introduce malloc" objection has expired
CALL(fname$, …) by-name 1 translator emits a name→pointer dispatch table; no runtime cost
RUN prog$, CHAIN 3 mm_run_exec of another compiled .bc is a near-exact semantic match (fresh process, fresh variables)
EXECUTE, EVAL 4 need the interpreter, which the translator has discarded
GOSUB across SUB boundary 4 already correctly refused
OPTION ESCAPE, OPTION ANGLE 1 translator-only
Remaining OPTION sub-keywords (~120) 4 firmware/board configuration; the kernel and Fuzix own all of it
CSUB/END CSUB 4 embedded ARM blobs are meaningless; the nicer path already exists — write C and SYSTEM it, or link via a future extern declaration
TRACE, LIST, EDIT, NEW, SAVE, LOAD, AUTOSAVE, LIBRARY, XMODEM/YMODEM, HELP 4 REPL/editor duties; fzsh, levee, fm and uusend already do these jobs as OS programs
SETTICK, ON KEY, ON PS2, INTERRUPT/IRETURN 5 a SIGALRM-based SETTICK is possible but reentrancy semantics diverge from MMBasic's between-statement interrupt model; a silent divergence is worse than the current honest error
Strings, data, math
Feature Cat Rationale
ARRAY SLICE/INSERT 2 pure index arithmetic
LONGSTRING BASE64, MATH BASE64 2 pure, small
LONGSTRING AES128, MATH AES128 2 pure; add on demand
MATH matrix/vector/quaternion (M_* V_* Q_*), C_* complex, CRC8/12/16/32, INTERPOLATE, WINDOW, SHIFT, SLICE, SINC, CHI, CORREL, CROSSING, MAGNITUDE, DOTPRODUCT 2 all pure arithmetic; the set is large so gate each behind its own header/flag, added as programs need them
MATH FFT 2 pure but big; on-demand header; note cc-code speed vs MMBasic's native FFT
MATH PID, MATH SENSORFUSION 5 only meaningful with periodic ticks (SETTICK) — inherits its category
MANDELBROT 4 demo command; write it in BASIC
BIT(/BYTE(/FLAG( command forms 1 inline emission, trivial
FM fixed-point 4 not in the reference variant
Files
Feature Cat Rationale
FLUSH 1 one fflush wrapper, trivial
Wildcard/bulk KILL ALL, COPY, FILES sort options 3 Fuzix has rm/cp/ls; spawn them (the wildcard matcher already exists in the runtime for DIR$)
LOAD BMP/JPG/PNG 3 extend the existing loadimage binary (decoder libraries are already in the wider tree)
SAVE COMPRESSED IMAGE 3 extend saveimage
VAR SAVE/RESTORE/CLEAR, SAVE/LOAD DATA, SAVE PERSISTENT 2 file-backed; cheap since the file layer exists
DRIVE, SAVE/LOAD CONTEXT 4 one filesystem; contexts are an interpreter notion
Serial OPEN "COMn:" 2† maps naturally onto Fuzix tty devices; needs termios-style setup via the libcall surface; currently (correctly) refused
Graphics
Feature Cat Rationale
BOX, RBOX 2 rectangles → existing mm_fill batches; BOX is the most-missed drawing primitive and near-free
ARC, TRIANGLE, POLYGON, BEZIER, LINE AA/PLOT/GRAPH 2 exactly the CIRCLE bargain: geometry in a header, only mm_plot/mm_fill cross
FILL (flood) 2 needs pixel readback; mm_pixel_get exists
BLIT + BLIT MEMORY (load/read/write/close) 2† buffers go in PSRAM heap arrays; needs a block pixel-read ioctl to complement RECTS
GETSCANLINE 4 FRAMEBUFFER WAIT (vsync) already serves the use case
FRAMEBUFFER LAYER/MERGE, second buffer 5 already analysed: a driver-side layer needs ~40K of SRAM the machine doesn't have; MERGE was deliberately not taken
SPRITE (28 verbs), TILEMAP, TILE 5 persistent background-save state, collision detection and interrupts belong to an interpreter's idle loop; the honest subset (blit-style) is covered by BLIT above
TURTLE 5 pure geometry so a header is possible, but ~35 verbs of stateful toolkit for a teaching demo — tenuous
DRAW3D, RAY 5 the user's own canonical example; agreed
DEFINEFONT 5† kernel owns the text engine and its fonts; per-program fonts would need kernel font-upload surface
RESOLUTION, SYNC, REFRESH 4 kernel owns video timing; MODE covers what programs legitimately do
COLOUR MAP 2 palette arithmetic over existing mm_map
GUI / input
Feature Cat Rationale
GUI controls (~35 verbs), CTRLVAL, MSGBOX 5 a full widget toolkit with interrupt-driven redraw; the Northwind/pcgui path is the PC3-native answer
TOUCH(), CLICK() 4 no touch hardware
KEYDOWN() 2† needs a kernel key-state ioctl; wrapper is tiny. Same gap already noted for INKEY$ function keys
MOUSE, DEVICE(MOUSE…) 5† needs a kernel USB-mouse driver and reopens the pointer/layer question
GAMEPAD, WII, KEYBOARD ON/OFF 4 wrong variant / no hardware path
FRAME text windows 4 Fuzix ttys and the existing console are the windowing story
Sound
Feature Cat Rationale
PLAY WAV/FLAC/MODFILE 3 clone the playmp3 pattern; dr_wav/dr_flac/hxcmod decoders are already vendored in the wider tree
PLAY MIDI/MIDIFILE, PLAY TONE/SOUND/NOTE (synthesis) 3†/5 a synth is a separate program by the same pattern, but it's a real synthesiser to write — tenuous beyond a simple two-tone generator (PLAY TONE alone: 3, small)
PLAY PAUSE/RESUME 3 SIGSTOP/SIGCONT to the kernel-reported owner — fits the existing mm_play_stop model exactly
PLAY NEXT/PREVIOUS, PLAY LOAD SOUND/SAMPLE/ARRAY/STREAM/EFFECT 5 playlist/streaming state belongs in a player program; buffer-refill-from-idle-loop forms can't be honest
Hardware I/O
Feature Cat Rationale
SETPIN AIN/ARAW, ADC 2† kernel ADC ioctl + header wrapper; genuinely useful on this board
PWM, SERVO, SETPIN PWM… 2† kernel PWM ioctl + header wrapper
SETPIN INTH/INTL/INT/FIN/CIN/PIN 5 pin interrupts/frequency into a user process need a signal-delivery design; polling PIN() covers most uses
I2C/I2C2 (incl. slave) 2† /dev/i2c with kernel arbitration is already the agreed resource-sharing model; header wrappers over ioctls
SPI/SPI2 4 SPI bus belongs to the SD card; user SPI would fight the kernel
ONEWIRE, TEMPR, HUMID 5† µs bit-timing under a preemptive kernel needs a kernel driver; possible, tenuous
PIO + 23 assembler mnemonics 4 PIO blocks are owned by video/audio scanout; user PIO programs would destabilise the machine
IR, WS2812, BITSTREAM, PULSE, PULSIN(), DISTANCE(), KEYPAD, STEPPER/TMC22xx/SLEW, CAMERA, GPS(), LOCATION, STAR/ASTRO 4 timing-critical bit-bang or hardware the PC3 doesn't have/expose
RTC GETTIME/SETTIME/GETREG/SETREG 4 the OS owns the DS3231 (and the plan already moves it to cron); DATE$/TIME$ are the interface
WATCHDOG, CPU SPEED/RESTART/SLEEP, UPDATE FIRMWARE 4 kernel/OS responsibilities (CPU SPEED specifically: QMI flash timing must span clock changes — not user business)
Memory, misc, MM.*
Feature Cat Rationale
PEEK/POKE raw addresses, PEEK(PROGMEM/VARTBL/…) 4 no MMU — a wild pointer takes the machine down with no diagnostic; the current refusal is a safety feature
PEEK(VAR/VARADDR), POKE VAR 1 translator-resolvable to real C addresses; safe subset
MEMORY COPY/SET/PACK/UNPACK (typed, on variables/arrays) 2 pure memmove arithmetic
MEMORY (report), RAM, FLASH families 4 OS owns storage; free/df exist
JSON$() 2 WebMite-only in firmware, but pure parsing and genuinely useful for data files; optional on-demand header (cJSON)
MM.VER, MM.DEVICE$, MM.CMDLINE$ (argv), MM.HPOS/VPOS†, MM.FONTWIDTH/FONTHEIGHT, MM.INFO(FILESIZE/EXISTS/VERSION/DEVICE) 1 trivial and small; MM.ERRNO/ERRMSG$ ride with ON ERROR
Remaining ~90 MM.INFO selectors 4 interpreter/firmware introspection with no referent here
WEB family, MM.MESSAGE$/ADDRESS$/TOPIC$ 4 no radio on the PC3; wrong variant
Summary and suggested sequence
Rough score after allocation of the ~350 applicable keywords: ~120 covered, ~35 more to category 1 (dominated by TYPE/STRUCT, ON ERROR, REDIM, MM.* — almost all translator-side, compiler untouched except the known TYPE tokenizer change), ~60 to category 2 (drawing primitives, MATH library, MEMORY ops, VAR SAVE, plus the 1→2 migrations), ~15 to category 3 (audio formats, image formats, RUN/CHAIN, bulk file ops), ~90 category 4, ~30 category 5.

If I were sequencing it: (1) split mmb_gfx.h per-feature and fix the extent-array BSS — it makes every later category-2 addition genuinely pay-per-use; (2) BOX/RBOX/TRIANGLE/ARC — cheap, visible, and BOX is currently falsely documented as done; (3) TYPE/STRUCT — biggest single win, already triaged as such; (4) ON ERROR+MM.ERRNO/ERRMSG$ — unlocks defensive real-world programs; (5) PLAY WAV/FLAC/MOD + PLAY PAUSE/RESUME — pure clones of the proven playmp3 pattern; (6) the 1→2 migration of cold runtime code (~800 lines' worth, keeping SORT and the MATH reductions native for speed); (7) fix README/COVERAGE.md, which now misstate coverage in both directions.

One honest caveat on the user premise: only TYPE, and the tokenizer with it, touches the compiler proper; everything else here is translator + runtime + headers + new spawned binaries — plus a handful of small kernel ioctls (marked †) for ADC, PWM, I2C, key-state and block pixel reads, which are kernel work rather than compiler work.

Happy to save this as a report file (e.g. REVIEW-COVERAGE-2026-08-07.md in the mmb2c tree) if you want it alongside COVERAGE.md — say the word, since you asked for no changes.

Edited 2026-08-07 20:29 by matherp
 
Mixtel90

Guru

Joined: 05/10/2019
Location: United Kingdom
Posts: 9012
Posted: 11:36am 07 Aug 2026
Copy link to clipboard 
Print this post

Sheesh... That look very thorough. Extremely clever stuff.

Sanitised? Did Mythos used to swear at you when you got something wrong?  ;)
Mick

Zilog Inside! nascom.info for Nascom & Gemini
Preliminary MMBasic docs & my PCB designs
 
matherp
Guru

Joined: 11/12/2012
Location: United Kingdom
Posts: 11721
Posted: 11:55am 07 Aug 2026
Copy link to clipboard 
Print this post

No, mythos was the version that could hack and design biological weapons in the wrong hands. They disabled those features and called it fable before making it available to the punters
 
lizby
Guru

Joined: 17/05/2016
Location: United States
Posts: 3849
Posted: 12:07pm 07 Aug 2026
Copy link to clipboard 
Print this post

So impressive. Did you, as I did, get a $100 credit (or £ equivalent) to use on Fable with my monthly subscription? Otherwise, at per token rates, I don't think I could afford it.
PicoMite, Armmite F4, SensorKits, MMBasic Hardware, Games, etc. on FOTS
 
matherp
Guru

Joined: 11/12/2012
Location: United Kingdom
Posts: 11721
Posted: 12:13pm 07 Aug 2026
Copy link to clipboard 
Print this post

You can burn though credit on Fable very quickly, but on a standard subscription you can now use fable for half your normal credit each week. Useful for the really hard stuff. I typically get it to do this sort of stuff and then revert to Opus 5.0 for implementation
 
Mixtel90

Guru

Joined: 05/10/2019
Location: United Kingdom
Posts: 9012
Posted: 12:20pm 07 Aug 2026
Copy link to clipboard 
Print this post

Ah... It appears to have been sanitised for a very good reason then. lol

I do like my idea though....

Claude:

ERROR BOX and BLIT do not exist.
You are the second cousin of a three eared ocelot of the lowest order
Write out 100 times:
"I must check that I have included the commands that I say I have"



(no offence intended!)
Mick

Zilog Inside! nascom.info for Nascom & Gemini
Preliminary MMBasic docs & my PCB designs
 
lizby
Guru

Joined: 17/05/2016
Location: United States
Posts: 3849
Posted: 01:14pm 07 Aug 2026
Copy link to clipboard 
Print this post

  matherp said  revert to Opus 5.0 for implementation


I'm not seeing Opus 5.0 yet. I've found Opus 4.8 is very satisfactory for coding while Claude talks directly to PicoMite hardware. My Fable 5 says it's "included until July 19", so that notice is a bit out of date.
PicoMite, Armmite F4, SensorKits, MMBasic Hardware, Games, etc. on FOTS
 
thwill

Guru

Joined: 16/09/2019
Location: United Kingdom
Posts: 4381
Posted: 01:21pm 07 Aug 2026
Copy link to clipboard 
Print this post

  lizby said  I'm not seeing Opus 5.0 yet. I've found Opus 4.8 is very satisfactory for coding while Claude talks directly to PicoMite hardware. My Fable 5 says it's "included until July 19", so that notice is a bit out of date.


I just wanted to mention that for most coding purposes Sonnet 5 should be more than capable and significantly cheaper (i.e. do more at the free tier), I've certainly been using it for almost everything and I still do this professionally (not free tier professionally.)

YMMV,

Tom
MMBasic for Linux, Game*Mite, CMM2 Welcome Tape, Creaky old text adventures
 
lizby
Guru

Joined: 17/05/2016
Location: United States
Posts: 3849
Posted: 01:47pm 07 Aug 2026
Copy link to clipboard 
Print this post

  thwill said  I just wanted to mention that for most coding purposes Sonnet 5 should be more than capable and significantly cheaper


Thanks for that information. For my uses for having Claude write little sensor programs to run on a Pico 2 W (doing all the work interactively with the board after I give it the prompt) or refactor big programs like my PicoDB or PicoRR, it doesn't matter how token-using Opus 4.8 is relative to Sonnet 5, because I can't keep the pipeline full in any case--that is, a lot of time under my monthly subscription is idle, because there's only so much I can do.

On the other hand, when trying to vibe code a PCB design, I've been regularly brought up short in my 5-hour time slots, because Claude just isn't that efficient at it. (Though still, if successful, far more efficient than I am.)
PicoMite, Armmite F4, SensorKits, MMBasic Hardware, Games, etc. on FOTS
 
Print this page


To reply to this topic, you need to log in.

The Back Shed's forum code is written, and hosted, in Australia.
© JAQ Software 2026