Dependency reference for the SD1 base: what spruceOS needs from it, and what oakMOSS ships. It began as the study written while bringing the image up (September 2026), against Moss's glibc 2.29 / GCC 7.4.1 userland on the Zero 28 and Zero 40, and it is the evidence behind the toolchain choice (docs/toolchain.md) and the config additions (docs/sdk-mods.md). Brought up to date for v0.4.2-beta.1 (2026-10-05): each section says where things stand first, and keeps the study's findings where they still explain a decision. spruceOS paths are from https://github.com/spruceUI/spruceOS; the checks dated 2026-10-04 are against its Development branch.
Boards: MagicX Mini Zero 28 (640x480), Zero 40 (480x800 portrait, AXS15205 touch) and XU20 V32
(1024x768, Hynitron touch). Allwinner A133P aarch64, kernel 4.9.191, PowerVR GE8300, AXP2202
PMIC. Radios: an XR829 (WiFi, and Bluetooth over UART) on the Zero 40, a Realtek RTL8189ES (WiFi
only) on the Zero 28 and XU20. Userland: glibc 2.38 and libstdc++ GLIBCXX_3.4.32 /
CXXABI_1.3.14 from the Arm GNU Toolchain 13.3 (docs/toolchain.md); the kernel is built with
the vendor's GCC 6.4. spruce lives on SD2: /usr/magicx/bin/runmagicx.sh hands off to
/mnt/SDCARD/.tmp_update/updater (or magicx/init.sh). Line numbers cited from the Tina SDK
(sdk/lichee, proprietary and untracked) are the study's; they are the only durable record of
those files.
Tier A is what spruce needs to boot at all, tier B its full feature set, tier C PortMaster.
| need | tier | v0.4.2-beta.1 |
|---|---|---|
squashfs root with overlayfs (how SD1 itself boots: the vendor BoardConfig.mk builds rootfs, overlay and data as squashfs + ext4) |
A | on |
the hand-off (runmagicx.sh) and SDL2 under /usr/magicx/lib, where PyUI's PYSDL2_DLL_PATH points |
A | shipped: oakMOSS's own runmagicx.sh; TrimUI's SDL2 2.26.1 with image, mixer and ttf |
C runtime: libc, libm, libpthread, libdl, librt, libutil, libcrypt, libresolv, libgcc_s, the aarch64 loader |
A | glibc 2.38 |
a correctly named libSDL2-2.0.so.0 for native programs (§2) |
A | /usr/magicx/lib/libSDL2-2.0.so.0 |
| GE8300 userspace, kernel modules and firmware (§4) | A | shipped (fbdev) |
| onboard WiFi (§5) | A | XR829 and RTL8189ES modules with firmware |
TrimUI's daemons under /usr/trimui |
A (study) | not needed: spruce's MagicX device files do not run them (§8.1) |
| Bluetooth: bluez, Bluetooth audio, the XR829's HCI path (§5) | B | bluez 5.54 with bluez-alsa 4.0.0 (round 16); Zero 40 headset audio tested |
| USB gadget: mass storage and ADB | B | on |
| exFAT on user cards over 32 GB | B | open: no in-kernel exFAT; FUSE is a module, and no exfat-fuse package exists |
INPUT_FF_MEMLESS (rumble on external pads), SND_USB_AUDIO |
B | on (=y, =m) |
| loop devices for PortMaster's runtimes | C | on, 8 at least |
| squashfs decompressors for those runtimes | C | XZ, LZO, LZ4, gzip and zstd (round 17) |
INPUT_UINPUT (gptokeyb), INPUT_JOYDEV (/dev/input/jsN readers) |
C | on |
libstdc++ at GLIBCXX_3.4.26 or newer (§3) |
C | GLIBCXX_3.4.32 |
| 32-bit (armhf) programs | C | the kernel runs them (COMPAT=y), but the base has no armhf loader or libraries; spruce carries a loader under spruce/flip/. Not run on these boards |
| PortMaster device profiles (§6) | C | open, on PortMaster's side |
Condensed: the study's full 214-file / 102-soname table is not reproduced. It came from running
readelf -d/-V/--dyn-syms over every aarch64 ELF that Zero28.cfg's PATH and
LD_LIBRARY_PATH reach, in September 2026. The version notes are against the study's glibc 2.29
/ GLIBCXX_3.4.24 ceiling; today's userland (glibc 2.38, GLIBCXX_3.4.32) meets every one of
them (§3).
| binary/lib | representative DT_NEEDED | class | version note |
|---|---|---|---|
spruce/flip/bin/python3.10 |
libpthread, libdl, libutil, librt, libm, libc, libpython3.10.so.1.0 |
SHIPPED | GLIBC_2.17 |
spruce/flip/lib/libpython3.10.so.1.0 |
libdl, librt, libm, libpthread, libutil, libc | SHIPPED | GLIBC_2.17; _ssl/_hashlib/_sqlite3/_socket/_ctypes/array/math/zlib all statically built in (confirmed via nm -D PyInit_* symbols), not separate .sos |
spruce/flip/lib/lib-dynload/_crypt*.so |
libcrypt.so.1 |
mixed | needs BASE-OS libcrypt.so.1 |
spruce/flip/lib/lib-dynload/_tkinter*.so |
bundled libtcl8.6.so/libtk8.6.so |
SHIPPED | GLIBC_2.17 |
spruce/flip/lib/libSDL2-2.0.so (SONAME libSDL2-2.0.so.0, filename has no .0; still so on Development, 2026-10-04) |
libX11, libwayland-client, … | broken by name, see §3 and §8.2; on these boards the base's /usr/magicx/lib copy stands in |
GLIBC_2.29 |
overlay/usr/magicx/lib/libSDL2{,_image,_mixer,_ttf}-2.0.so.0.* |
correctly named/symlinked | BORROWED (TrimUI Smart Pro / Moss-zero28) | GLIBC_2.29 max |
spruce/flip/lib/libavformat.so.58, libavutil.so.56, libmp3lame.so.0 |
many | SHIPPED | GLIBC_2.38 |
spruce/flip/lib/{libsystemd,libxkbcommon,libwayland-egl++,liblzma,libvpx,libzstd,libdecor-0} |
— | SHIPPED | GLIBC_2.30–2.34 |
spruce/bin64/{busybox,curl,ffmpeg,rsync,ssh-keygen,jq,wget,…} (47 tools) |
mostly libc.so.6 only | SHIPPED | GLIBC_2.17–2.21 |
spruce/bin64/{fbfixcolor,gpiowait,setsharedmem,setsharedmem-flip} |
libc.so.6 | SHIPPED | GLIBC_2.34 (__libc_start_main@GLIBC_2.34, confirmed live with readelf --dyn-syms) |
RetroArch/ra64.universal |
libSDL2-2.0.so.0, libEGL, libGLESv2, libX11, libstdc++, libc | SHIPPED (bin); SDL2/EGL/GLESv2/libstdc++ → BASE-OS | GLIBCXX_3.4.26 |
| cores64 sample (genesis_plus_gx, snes9x, fbneo, fceumm, gpsp, mgba, mupen64plus_next, fake08, pcsx_rearmed, …; 91 cores total) | libc/libm/libstdc++/libz | SHIPPED | ≤GLIBCXX_3.4.21 except swanstation_libretro.so (3.4.26) |
Emu/PSP/PPSSPPSDL_TrimUI |
libSDL2-2.0.so.0, libEGL, libGLESv2, libstdc++, libc | SHIPPED; SDL2/EGL/GLESv2/libstdc++ → BASE-OS | GLIBCXX_3.4.24/CXXABI_1.3.11 |
Emu/PS/pcsx_64 |
bundled libSDL-1.2.so.0, BASE-OS libasound.so.2, libpng16, libc | SHIPPED (SDL1.2 self-contained) | GLIBC_2.17 |
Emu/NDS/drastic64, Emu/NDS/dsperate |
libSDL2-2.0.so.0, libstdc++, libc | SHIPPED; SDL2/libstdc++ → BASE-OS | low GLIBCXX |
Emu/N64/mupen64plus/* (13 files) |
libSDL2-2.0.so.0, libstdc++, libc | SHIPPED; SDL2/libstdc++ → BASE-OS | GLideN64/glide64mk2 need GLIBCXX_3.4.26 |
Emu/SATURN/yabasanshiro, Emu/DC/flycast |
libstdc++, libc + bundled libs (flycast bundles OpenSSL 1.1) | SHIPPED; libstdc++ → BASE-OS | GLIBCXX_3.4.26 |
App/Moonlight/…/moonlight,love (+20 bundled libs incl. OpenSSL 3.x) |
SDL2 via liblove, libavcodec, libcurl, libssl.so.3 | SHIPPED (self-contained); SDL2 → BASE-OS | max GLIBC_2.30, GLIBCXX_3.4.21 |
spruce/smartpro/inputd/trimui_inputd (patched, drops left-stick range check) |
libm, libpthread, libgcc_s, libc | SHIPPED, TrimUI only (TRIMUI_INPUTD_PATCHED is exported by SmartPro.cfg) |
GLIBC_2.17 |
Emu/SCUMMVM/scummvm_64.7z, App/PortMaster/portmaster.7z |
n/a — still packaged | n/a | not inspectable until extracted at runtime |
Sonames spruce leaves to the base (absent from every aarch64 directory of the spruceOS tree the
study checked): libc.so.6, libm.so.6, libpthread.so.0, libdl.so.2, librt.so.1,
libutil.so.1, libcrypt.so.1, libresolv.so.2, ld-linux-aarch64.so.1, libEGL.so.1,
libGLESv2.so.2, libdrm.so.2, libudev.so.1, libasound.so.2, libfreetype.so.6,
libfontconfig.so.1, libffi.so.8, libtinfo.so.6/libncursesw.so.6, libgcc_s.so.1, and,
per the SONAME finding above, effectively libSDL2-2.0.so.0 and libstdc++.so.6. The v0.4.2-beta.1
root filesystem provides all of them except libdrm.so.2 and libfontconfig.so.1 (neither
package is built) and libffi.so.8 (it has libffi.so.7); its libpng is 1.2 (libpng12.so.0),
not 1.6.
Today: glibc 2.38 and libstdc++ GLIBCXX_3.4.32 / CXXABI_1.3.14. Every requirement the
study found is met: up to GLIBC_2.38 in spruce's own bundle, GLIBCXX_3.4.26 in RetroArch,
several cores and emulators, and PortMaster's Godot/FRT and Solarus runtimes. None of the gaps
below remains; they are why oakMOSS left Moss's toolchain for the Arm GNU Toolchain 13.3
(docs/toolchain.md).
The study's findings, against Moss's glibc 2.29 / GCC 7.4.1 Linaro (and the GCC 7.5.0 stand-in
it was built with), whose libstdc++ stops at GLIBCXX_3.4.24 / CXXABI_1.3.11
(phase2.config:398-408: CONFIG_GLIBC_USE_VERSION_2_29=y, CONFIG_GCC_USE_VERSION_7_4_LINARO=y):
- Nothing in the closure exceeded
CXXABI_1.3.11. - Six SHIPPED aarch64 binaries need
GLIBCXX_3.4.26(GCC ≥9 libstdc++, none bundling their own):RetroArch/ra64.universal,RetroArch/.retroarch/cores64/{flycast,swanstation}_libretro.so,Emu/DC/flycast,Emu/N64/mupen64plus/{mupen64plus-video-GLideN64,mupen64plus-video-glide64mk2}.so,Emu/SATURN/yabasanshiro. They would not link against GCC 7's libstdc++.PPSSPPSDL_TrimUIsat exactly at the ceiling (GLIBCXX_3.4.24). - Four SHIPPED aarch64 tools need
GLIBC_2.34:fbfixcolor,gpiowait,setsharedmem,setsharedmem-flip(spruce/bin64/). Their crt object comes from a glibc ≥2.34 toolchain, so glibc 2.29 refused them ("version `GLIBC_2.34' not found"). - The
spruce/flip/lib/*bundle (python3.10's ffmpeg, audio and wayland stack) mixes GLIBC_2.30 through 2.38:libavformat.so.58/libavutil.so.56/libmp3lame.so.0at 2.38,libsystemd/libxkbcommon/liblzma/libvpx/libzstd/libdecor-0at 2.30–2.34. Not on the hot path unless dlopened (Tkinter, ffmpeg features). - The GE8300 userspace blobs (
libEGL.so,libGLESv2.so,libIMGegl.so,libsrv_um.so,libusc.so,libpvrNULL_WSEGL.so) need onlyGLIBC_2.17;libglslcompiler.soalso needslibstdc++.so.6at the unversionedGLIBCXX_3.4. No GPU-driver blocker (checked withreadelf --dyn-symsagainstsdk/lichee/package/libs/libgpu/ge8300/fbdev/glibc/lib64/*.so). - spruce's own
python3.10/libpython3.10.so*need onlyGLIBC_2.17. - PortMaster: the sampled port
2048_libretro.so.aarch64needsGLIBC_2.33. The sampled shared runtimesruntimes/frt_3.3.4(Godot/FRT, the most shared runtime in the catalog) andruntimes/solarus-1.6.5both needGLIBCXX_3.4.26.port.json'smin_glibcgate is enforced live (harbour.py:1160-1164, againsthardware.py:272-314'slibc.so.6 --versionprobe), but there is nomin_glibcxxfield: a libstdc++ mismatch is only discovered at run time, as a dynamic-linker failure.
The study's conclusion (since acted on): glibc 2.29 was adequate for almost everything spruce
ships; the libstdc++ ceiling was the real risk, two minor versions behind what
GLideN64/flycast/yabasanshiro/swanstation and the Godot/FRT runtime need. The options it gave were
a newer libstdc++.so.6 ahead of the system one, or giving those up; oakMOSS took a third, a whole
userland on a newer toolchain.
Today: shipped as the study found it in the SDK: fbdev, no DRM, libgpu and
kmod-ge8300-km on, firmware rgx.fw/rgx.sh 22.102.54.38 in /lib/firmware. The kernel now
also rotates the framebuffer in hardware (see the last point).
- Kernel module:
sdk/lichee/package/kernel/gpu-km/Makefile:36-50selectsPKG_NAME:=ge8300-kmforTARGET_PLATFORMinmr813 r818 a133, buildingpvrsrvkm.ko+dc_sunxi.kofromlichee/linux-4.9/modules/gpu/img-rgx/linux/rogue_km/binary_sunxi_linux_nullws_release/target_aarch64/. - Userspace package:
package/libs/libgpu/Makefile:27-28auto-selectsGPU_TYPE=ge8300fora133;DEPENDS:=+kmod-ge8300-km +libstdcpp +zlib(L45-46); the window system is fbdev, not wayland/DRM, becauseCONFIG_WESTON_DRMis off (Makefile L52-56;libdrmis off too). - Blob directory
package/libs/libgpu/ge8300/fbdev/glibc/lib64/:libEGL.so(.1),libGLES_CM.so(.1)(ES1.1),libGLESv2.so(.2),libIMGegl.so,libglslcompiler.so,libsrv_um.so,libusc.so,libtqvalidate.so,libufwriter.so,libpvrNULL_WSEGL.so,libPVROCL.so(.1)+libOpenCL.sosymlink,libPVRScopeServices.so,libVK_IMG.so(.1),libvulkan.so(.1)(Vulkan 1.x present);bin/{pvrsrvctl,ocl_unit_test};firmware/{rgx.fw.22.102.54.38,rgx.sh.22.102.54.38}->/lib/firmware. DDK peraw_version: "img-rgx: add 1.19 ddk". OpenCL is off. - The SDL2 that spruce, PyUI and RetroArch need, cross-validated against an independent port
for this device:
knulli-linux/board/allwinner/a133/magicx-zero-28/and.../magicx-zero-40/. Theirpatches/sdl2/001-add-pvr-ge8300-mali-driver.patchadds aSDL_MALI/CheckMaliCMake option that compiles SDL2'ssrc/video/mali-fbdev/*.cbackend against<EGL/egl.h>(-DLINUX -DEGL_API_FB): SDL2 with the mali-fbdev video backend pointed at the GE8300's own EGL/GLESv2,SDL_VIDEODRIVER=mali, the patternApp/PyUI/launch.sh:133-162documents for the Anbernic XX (H700/Mali) line. Knulli ships byte-identicalrgx.fw.22.102.54.38/rgx.sh.22.102.54.38: the same vendor DDK build. - Kernel-side fbdev is present and sufficient:
CONFIG_FB=y(config-4.9 L2333),CONFIG_DISP2_SUNXI=y(L2375);CONFIG_DRM=y/DRM_KMS_HELPERexist but this fbdev-only GPU path does not use them.CONFIG_PACKAGE_libdrmis off in the vendor defconfig and every config since, consistent with fbdev only. - Framebuffer rotation: the study found hardware rotation not built
(
CONFIG_SUNXI_DISP2_FB_DISABLE_ROTATE=y, config-4.9 L2376-2377), so the Zero 28's portrait panel needed rotation in software. It is built now: G2D rotation on every flip (CONFIG_SUNXI_DISP2_FB_HW_ROTATION_SUPPORT=y, tree patches100and101, rounds 11 and 13 indocs/sdk-mods.md), from the vendor patch in MagicX's a133 Tina BSP update of 2025-10-11. Each board turns it on in its device tree: the Zero 28 a quarter turn, the XU20 a half turn.
- WiFi. Today:
XR829_WLAN=mwithkmod-net-xr829andxr829-firmwarefor the Zero 40 (the 26 MHz variant:kmod-net-xr829-40MandXR829_USE_40M_SDDstay off, §8.4), andRTL8189ES=mfor the Zero 28 and XU20. The study found the XR829 off everywhere: kernelconfig-4.9:1262-1263(# CONFIG_XR819_WLAN / CONFIG_XR829_WLAN is not set, full driver atlichee/linux-4.9/drivers/net/wireless/xr829/), andkmod-net-xr829,kmod-net-xr829-40Mandxr829-firmwareoff in the vendor defconfig (L3542, L2917) and both phase configs (phase1 L3143; phase2 L3129, L2590), though the blobs exist atpackage/firmware/linux-firmware/xr829/. Knulli'setc/init.d/S03modules:1-15picks between a genericxradio_{core,mac,wlan}.kobuild and a TrimUI Smart Pro_tsp.kovariant by board name; the MagicX boards take the generic one. - Bluetooth. Today: bluez 5.54 (
bluez-libs,bluez-daemon,bluez-utils,bluez-utils-extra), dbus 1.10.4,XR829_BT=y, and bluez-alsa 4.0.0 (round 16, with upstream drain fixes backported; the SDK's 2018 snapshot wedged). The XR829's Bluetooth is on UART/dev/ttyS1; spruce attaches it and startsbluealsaitself, nothing in the base does. Zero 40 headset audio was tested on 2026-10-04; the Zero 28 and XU20 have no Bluetooth. The study found the vendor defconfig with Bluetooth on (bluez-libs=yL3986,bluez-alsa=yL4395,bluez-daemon=yL5328,bluez-utils(-extra)=yL5331-5332,dbus=yL5346) and Moss's phase configs with every bluez package andCONFIG_XR829_BToff (phase2 L3573, L3982, L4647-4651), while spruce's TrimUI A133P device file started/etc/bluetooth/bluetoothdunconditionally. The vendor HCI attach scripts arepackage/allwinner/bluetooth/xradio/hciattach_xr829.initandpackage/allwinner/btmanager/config/xradio_bt_init.sh. - wpa_supplicant / hostapd: one package,
package/network/services/hostapd/MakefilePKG_VERSION:=2017-11-08(wpa_supplicant 2.6/2.7 era), internal TLS (CONFIG_WPA_SUPPLICANT_INTERNAL=y).wpa-supplicantandwpa-cliare on;hostapdis off since round 8, withsoftap, whose Makefile injectedCONFIG_NF_CONNTRACK=y(docs/sdk-mods.md).wireless-toolsis off (the vendor default); spruce usesip/wpa_cli/jq. - Audio:
alsa-lib1.1.4.1;CONFIG_SND_SOC=y(config-4.9 L2496);CONFIG_SND_USB_AUDIO=msince round 8 (the study found it off, L2486). - Input:
libevdev1.4.6 andlibinput1.5.0 on, with eudev'slibudev.so.1. Since round 15 the touch controllers (axs_ts,hyn_ts) are tagged as touchscreens in udev (overlay/etc/udev/rules.d/60-oakmoss-touchscreen.rules), which libudev users need, and the kernel drops repeated multitouch values, so a held touch stays held. - Library versions (the study):
zlib1.2.8,libpng1.2.56 (the old 1.2 branch, not 1.6),libjpeg(IJG) 9a,freetype2.6.1,bluez5.54,dbus1.10.4.libffiislibffi.so.7. - SDL2 is not a Tina package: only SDL 1.2 (
package/multimedia/sdl/Makefile), and it is off. spruce, PyUI and RetroArch bring SDL2 themselves, and the base ships TrimUI's SDL2 2.26.1 under/usr/magicx/lib(§1, §2).
Not re-checked against PortMaster since the study (PortMaster-GUI, September 2026), apart from
the runtime compressors (§8.8). On spruce's side, all three boards have platform files now
(Zero28.cfg, Zero40.cfg, XU20.cfg).
Detection is two separate, disagreeing code paths:
- Bash (
PortMaster/device_info.txt):L52-54checks/mnt/SDCARD/sprucefirst (commitef16b68, "check for spruce first because of our dArkOS shenanigans") and setsCFW_NAME=spruce.L234computesRESOLUTIONwith a live SDL probe (sdl_resolution.$DEVICE_ARCH), so each board gets its real resolution and aspect without a profile. TheL276-495FIXEScase statement (hand overrides forANALOG_STICKS/DEVICE_CPU/DEVICE_NAMEper known board) has no MagicX branch: an A133P board'sDEVICE_NAMEfalls through to a generic devicetree-model parse, and sinceCFW_NAME=spruce, the TrimUI override does not fire either. The MagicX boards very likely report the same rawDEVICE_NAME=sun50iw10that Brick/TSP/BrickPro do under spruce.ANALOG_STICKSdefaults to 2 with no override (right for the Zero 28, not for the Zero 40 with one stick or the XU20 with none). - Python (
harbourmaster/hardware.py): a static table, no live probe, and no/mnt/SDCARD/sprucecheck (git logshows no spruce-related commits upstream).PlatformSpruce(PR #337,platform.py:1277-1309) is unreachable in stock upstream; the TrimUI Smart Pro reaches it only because spruce ships a patchedhardware.py/platform.pyon the card.HW_INFO(hardware.py:103-193) has no MagicX key, so an unprofiled device falls to"default"(L192-193):{"resolution":(640,480),"analogsticks":2,"cpu":"unknown","capabilities":["opengl","power"]}, the bug already measured on the TrimUI Smart Pro (device=unknown, a wrongopengltag). The Zero 28 happens to match the default resolution; the Zero 40 (480x800) and XU20 (1024x768) get the wrong one. - Tag machinery (
hardware.py:647-732,expand_info):display_ratiovia gcd (4:3 for the Zero 28 and XU20, 3:5 for the Zero 40, which has no built-in curation),analog_0..N,lowres/hires/wide, RAM-tier tags.harbour.py:1142-1189+util.py:700-730AND-match a port's"reqs"tags (e.g.ports/air/port.json: "reqs":["4:3"],ports/aquaria/port.json: "reqs":["!lowres","analog_1"]) against these. - Proposed profiles (schema per
hardware.py:30-193), not taken up:DEVICES["MagicX Mini Zero 28"] = {"device":"zero28","manufacturer":"MagicX","cfw":["spruce"]},HW_INFO["zero28"] = {"resolution":(640,480),"analogsticks":2,"cpu":"a133p","capabilities":[],"ram":1024}(no"opengl": the GE8300 is GLES-only); the Zero 40 likewise with{"resolution":(480,800),"analogsticks":1,...}and the XU20 with{"resolution":(1024,768),"analogsticks":0,...}(RAM to be measured, §8.9). Both code paths would need the/mnt/SDCARD/sprucecheck to reach them, and the bashFIXEStable MagicX branches so the boards stop colliding with Brick/TSP'ssun50iw10. - Runtime and mount needs. Today's base: loop devices (8 at least), squashfs with XZ, LZO,
LZ4, gzip and zstd, and a BusyBox with
losetup,stat,sha1sum,unzipandtar -z(nobc); with glibc 2.38 the PATHunzipthat needsGLIBC_2.34loads. Still to check on these boards:/dev/shmat boot (TODO.md). The study's measurements on other spruce devices: loop mounts worked everywhere except the armv7l A30; the TSPS wants 16+ loop devices where 8 exist;losetupwas missing on BusyBox-heavy units;/dev/shmwas shadowed or missing on the TSPS, wherepm_message(called by 375 ports) hangs; BusyBox 1.27.2 rejectsfind -quit(8 ports) andmv/cp -T(3);tarwithout-zbroke 9 ports.gptokeybships with spruce (spruce/bin64/gptokeyb) and needs only/dev/uinput.xdelta3is not a system dependency: sampled ports (e.g.HFTSR.zip) bundle their own static aarch64 copy. - Library patterns (the study's
PortMaster-New/common/pmsampling): ports overwhelmingly bundle their own SDL1/SDL2-family and codec/format libs (libvorbis, libmodplug, libFLAC, libwebp, liblzma, libjpeg, libtiff, libmikmod, libfluidsynth, libphysfs, libssl/libcrypto, libzip); they expect the base forlibc/libm/libpthread/libdl/libgcc_s, GLES/EGL, ALSA and the CFW'slibSDL2-2.0.so.0. Desktop-GL-only ports often bundle a gl4es shim (antecrypt,counter-strike,pigments,wratchsden) to run on the GLES-only GE8300; a few (xroar) bundle a rawlibGL.so.1instead, unlikely to work without testing. - Port categories. No longer blocked by the base: Godot/FRT and Solarus (libstdc++, and
their gzip runtimes since round 17) and libretro cores needing a newer glibc
(
2048_libretro.so.aarch64,GLIBC_2.33); none of these has been run on these boards yet. Still out or untested: x86/x86_64 ports; armhf ports, which depend on spruce's armhf loader (the study counted 204 across spruce devices); desktop-OpenGL-only ports without a gl4es shim; ports gated on aspect orwidetags until device profiles exist; heavy mono/Godot 4.x/dotnet bundles until RAM and CPU are measured (§8.9).
Kernel (sdk/lichee/device/config/chips/a133/configs/aw3/linux/config-4.9, changed through
sdk-patches/tree/020; the study's line numbers). Every one of these was also on in Knulli's
linux-sunxi64-legacy.config for these boards (a different, newer kernel base: corroboration of
the requirement, not a config to copy):
L956 CONFIG_BLK_DEV_LOOP=y # on since round 8 (BLK_DEV_LOOP_MIN_COUNT=8)
L1424 CONFIG_INPUT_UINPUT=y # on since round 8
L1300 CONFIG_INPUT_JOYDEV=y # on since round 8
L1291 CONFIG_INPUT_FF_MEMLESS=y # on since round 8
L2486 CONFIG_SND_USB_AUDIO=m # on since round 8
L1263 CONFIG_XR829_WLAN=m # on since round 8
squashfs: the study found only XZ (SQUASHFS_ZSTD/LZ4/LZO off, L3459-3462). Round 8 set
SQUASHFS_LZ4/LZO=y, but only LZO took effect; since round 17 the kernel reads XZ, LZO, LZ4,
gzip and zstd (ZLIB and ZSTD added in 020, and 134 stops the rootfs compressor choice
from switching the others off). exFAT: still none in the kernel, Tina's or Knulli's newer sunxi64
tree; the route is FUSE (CONFIG_FUSE_FS=m, L3390) plus an exfat-fuse/exfatprogs userspace,
not built yet (TODO.md). CONFIG_MODVERSIONS is still off (L267): a prebuilt .ko (spruce's
own zram.ko/lzo.ko/zsmalloc.ko, or a prebuilt xr829/GE8300 module) must come from a kernel
with an identical version and config to insmod, a build-process constraint rather than a
config line. Since round 8 the image carries zram, zsmalloc, LZ4 and LZO itself.
Packages (the study measured these against Moss's phase2.config; phase1.config's package set
is identical but for cortana-demo/cortana-sdk). The build uses
configs/ext-armgnu13.config:
CONFIG_PACKAGE_kmod-net-xr829=y # on
CONFIG_PACKAGE_kmod-net-xr829-40M=y # off: the 26 MHz variant is built (§8.4)
CONFIG_PACKAGE_xr829-firmware=y # on
CONFIG_XR829_USE_40M_SDD=y # off (§8.4)
CONFIG_PACKAGE_bluez-libs=y # on
CONFIG_PACKAGE_bluez-alsa=y # on; 4.0.0 since round 16
CONFIG_PACKAGE_bluez-daemon=y # on
CONFIG_PACKAGE_bluez-utils=y # on
CONFIG_PACKAGE_bluez-utils-extra=y # on
CONFIG_XR829_BT=y # on
Everything else the study checked (libgpu, kmod-ge8300-km, alsa-lib, libevdev,
libinput, libexpat, libffi, dbus, wpa-supplicant, zlib, libpng, libjpeg,
freetype) is on, as it was in both phase configs; hostapd is off since round 8 (§5).
/usr/trimui/binon MagicX hardware. Settled. The study found spruce's MagicX boards sharing the TrimUI A133P device file, whoserun_trimui_blobsexpects TrimUI'strimui_inputd/trimui_scened/trimui_btmanager/hardwareservice/musicserverin/usr/trimui/bin, which MagicX's firmware does not have. spruce's MagicX boards now have their own device files (Zero28.sh,Zero40.sh,XU20.sh,magicx_a133p.sh), and none of them runs those daemons (Development, 2026-10-04).spruce/flip/lib/libSDL2-2.0.sofilename/SONAME mismatch (§2). Still in spruce (Development, 2026-10-04): it defeats the "spruce brings its own SDL2" fallback for every native (non-PyUI) consumer. On these boards the base's/usr/magicx/lib/libSDL2-2.0.so.0stands in.- No Zero 40 platform in spruce, and touch unresolved. Settled. spruce has
Zero40.cfg/Zero40.sh(and the XU20's). Touch works through our kernel's drivers (sdk-patches/tree/080-*, round 10), loaded as modules after boot, and since round 15 also in programs that read it themselves, such as DraStic. (Knulli's Zero 40 kernel config enabled no touchscreen driver at the time.) - XR829 crystal (26 MHz vs 40 MHz SDD). Open. Only the Zero 40 has an XR829; the Zero 28
and XU20 carry an RTL8189ES. The 26 MHz variant is built, and WiFi and Bluetooth both work on
it; the crystal itself is unconfirmed (
docs/hardware-notes.md). - An earlier note claimed Moss disabled wpa-supplicant, dbus, libinput, the wireless drivers,
libexpat and libffi. Closed. The two phase configs on disk showed all of them on; only the
bluez set (and the vendor-default-off
wireless-tools/kmod-net-xr829/xr829-firmware) differed. oakMOSS builds from its ownconfigs/ext-armgnu13.configin any case. - The
/usr/magicx/devicemarker. Settled. Every image writes it (zero28,zero40orxu20), and spruce tells the boards apart by it. - Toolchain substitution. Superseded. The study ran on
phase2-gcc750.config(vanilla GCC 7.5.0) because the Linaro 7.4.1 tarball was unfetchable; both stop atGLIBCXX_3.4.24. The build now uses the Arm GNU Toolchain 13.3 (configs/ext-armgnu13.config,docs/toolchain.md); the phase configs remain only as a fallback. - PortMaster runtime squashfs compressor. Settled (2026-10-04). The study sampled two
runtimes,
frt_3.3.4andsolarus-1.6.5, and took them for XZ-compatible; both are gzip. Read from 46 PortMaster runtimes (2022-2026): 45 are gzip, and one, the 32-bit Godot 4.5, is zstd. The kernel read only XZ and LZO until round 17, which added gzip, LZ4 and zstd. - RAM/CPU headroom for heavy PortMaster runtimes (mono, Godot 4.x, dotnet). Open: not
measured on these boards;
HW_INFO'sramfigure (used for theNgbcapability tags) needs a real number before the profiles in §6 are final.
Added after the study, from bench work with spruceOS on all three boards:
- One key code per press. Images from 2026-09-26 on no longer give A a second code
(353), nor the XU20's B and MENU (158, 256). spruce checks the pad's key bitmap at run
time: if 353 is still listed, it is running on an older image and also accepts the second
codes, so one spruce card works on both. The XU20's extra face button is
KEY_HOMEPAGE(172). - Rumble through sysfs GPIO. spruce drives the motor on PH3 through
/sys/class/gpio, which needsCONFIG_GPIO_SYSFS(on since 2026-09-26). The Zero 28 has no motor. - Real suspend-to-RAM. spruce's platform files choose it per board
(
MAGICX_REAL_SLEEP=1), and the XU20 suspends its devices one at a time (MAGICX_PM_ASYNC=0). On the SDK chain it needs the I2C bus-error fix (sdk-patches/tree/130-*) in the image: without it, the XU20 froze on the way into sleep with the touch driver loaded. On its stock boot chain (images before 2026-09-28) the XU20 slept without it on the bench. - Charge mode stays in the base. When U-Boot boots for the charger, the base shows its own charge screen instead of starting the launcher, and hands off only when the power key is pressed. spruce needs nothing for this.
- Touch in programs that read it themselves (round 15, 2026-10-02): the kernel drops repeated multitouch values, and udev tags the touch controllers as touchscreens, which libudev users such as trngaje's advdrastic need (§5).
- Bluetooth audio (round 16, 2026-10-03): bluez-alsa 4.0.0 with
bluealsa,bluealsa-aplay,bluealsa-cli, the ALSA plugins (/usr/lib/alsa-lib) and the D-Bus policy fororg.bluealsa. Nothing in the base starts it; spruce startsbluealsa(-p a2dp-source). - squashfs with gzip, LZ4 and zstd (round 17, 2026-10-04): PortMaster's runtimes mount.
- The spruceOS pairing: images from v0.4.1-beta.1 on pair with spruceOS v4.5.0 or a newer
nightly (
docs/status.md).