CPU-compatible Python dependencies for running FieldStation42 on an older AMD G-T48E ("Bobcat", Family 14h) thin client (e.g. an HP/older ThinTerminal).
The target CPU is 64-bit but only x86-64-v1. It has:
sse sse2 sse3 ssse3 sse4a (plus popcnt/abm, cx16)
but it does NOT have sse4.1, sse4.2, avx, or avx2.
(sse4a is an AMD-only extension and does not include the SSE4.1 pinsr*/pextr*
instructions.)
Modern prebuilt Python wheels are compiled for x86-64-v2 (SSE4.1/4.2 baseline). When such a binary runs on this CPU the kernel kills it with SIGILL — "Illegal instruction (core dumped)" and no Python traceback, because it is a hardware trap, not a Python exception.
Confirmed culprits when launching field_player.py:
| Package | Symptom | Faulting instruction |
|---|---|---|
numpy (via moviepy→catalog→liquid_manager) |
SIGILL on import | pinsrq (SSE4.1) in _multiarray_umath static init |
PySide6 / shiboken6 |
SIGILL on import | pinsrq (SSE4.1) in Shiboken::init() |
bundled Qt6 itself |
SIGABRT: "This Qt build requires: sse4.1 sse4.2" | Qt's runtime baseline check |
Two unrelated launch blockers were also found and are handled at deploy time:
tkintermissing (python3-tk) — needed by the classic channel guide.- (web rendering / QtWebEngine is intentionally not built — see below.)
Rebuild the offending native packages with a CPU baseline of -march=btver1
(gcc's exact target for AMD Bobcat: SSE/SSE2/SSE3/SSSE3/SSE4a, no SSE4.1+).
Qt's higher SIMD paths remain runtime-dispatched, so they're simply not taken on
this CPU.
We build Qt 6.11.1 qtbase only (Core/Gui/Widgets + the xcb platform plugin)
and PySide6/shiboken6 6.11.1 against it, plus a from-source numpy.
QtWebEngine / QtQuick are deliberately skipped (a 204 MB Chromium build). The
only FieldStation feature that needs them is the optional web channel type
(rendering a web page as a TV channel). The video path (mpv→HDMI), the ticker /
now-playing / NFO overlays (Qt Widgets), and the classic tkinter guide all work
without it, and FieldStation degrades web rendering gracefully when the modules
are absent.
numpy note: numpy has its own SIMD baseline, independent of -march. In
2.4.x it defaults to X86_V2 and the -Dcpu-baseline= meson option will not go
below v2 (verified: it's floored/ignored). The reliable lever is
-Ddisable-optimization=true, which turns numpy's SIMD dispatcher off and builds
generic (SSE2) code with no x86-64-v2 requirement. The Bobcat has no AVX/SSE4.1 to
dispatch to, so this loses nothing on the target. (Also beware: pip wheel silently
skips building when the target wheel already exists in -w — the scripts delete the
prior wheel first.)
Versions are pinned to match the existing venv so the rest of the install is undisturbed:
- Qt / PySide6 / shiboken6: 6.11.1
- numpy: 2.4.6
- Python: 3.12 (cp312), Ubuntu 24.04 / glibc 2.39 — identical to the target.
docker/Dockerfile.builder Ubuntu 24.04 build image (matches the target OS/toolchain)
scripts/build-qtbase.sh clone + build qtbase 6.11.1 (btver1) -> /work/qt6
scripts/build-pyside.sh clone + build PySide6/shiboken6 wheels (btver1) -> dist/
scripts/build-numpy.sh build numpy 2.4.6 wheel from source (btver1) -> dist/
scripts/build-all.sh runs the three builds in order inside the container
scripts/deploy.sh copy wheels to the thin client + install + python3-tk
dist/ output wheels (git-ignored; large)
work/ sources + Qt install tree (git-ignored; large)
./scripts/run-build.sh # builds the image, then runs build-all.sh in a container
The container mounts this repo at /work; sources land in work/, wheels in dist/.
./scripts/deploy.sh <user>@<host> # default: fieldstation@172.16.225.187
Installs the wheels into /home/fieldstation/FieldStation42/env and ensures
python3-tk is present.
A build host's own CPU is irrelevant to the target baseline — only the compiler
flags matter. We build with -march=btver1 inside an Ubuntu 24.04 container that
matches the target's OS, glibc, gcc (13.3) and Python (3.12), so the resulting
.so/wheels link and run cleanly on the Bobcat (and on any newer x86-64, since
btver1 is a subset).
For community reference — this whole diagnosis, build, and deployment was done with Claude Code (Claude Opus 4.7).
Reproducible compile time on a 20-core x86-64 host (Ubuntu 24.04):
| Step | Time |
|---|---|
qtbase 6.11.1 (-march=btver1) |
~4m53s |
| PySide6 + shiboken6 (Core/Gui/Widgets) | ~2–3m |
| numpy 2.4.6 (from source) | ~3m |
One-time: Docker builder image (apt build-dep qt6-base) |
~5–8m |
| Source clones (qtbase ~414 MB, pyside-setup ~71 MB) | network-bound |
A clean from-scratch build is ~20–25 min end-to-end.
Whole-session effort — initial "illegal instruction" diagnosis → working, verified deploy on the target, including several debugging iterations and preparing the upstream contributions:
- Wall-clock: ~2h 48m · API compute: ~56m · Cost: ~$21.15
- Model: Claude Opus 4.7 (via Claude Code)
- Tokens (Opus 4.7): ~22.3k input · ~242.4k output · ~24.9M cache read · ~405k cache write