Release Date: unveröffentlicht — Tag noch nicht gesetzt Vorgänger: v4.1.5 (2026-06-05)
Nicht um neue Formate — die stehen unter Moratorium (MF-363/498). Sondern darum, dass keine Aussage des Werkzeugs mehr unbelegt ist, auch nicht die darüber, was es nicht kann.
Die vollständige Liste steht in CHANGELOG.md. Die
Kurzfassung dessen, was gefunden wurde:
| Fund | vorher | nachher |
|---|---|---|
SCP→D64 |
0 von 683 Sektoren, meldet Erfolg | 683 von 683 |
uft_convert_memory() |
ging komplett am Preflight-Tor vorbei; 3,7 MB erfundener Fluss aus 4 KB Zufall | Tor greift, 32 Paare abgewiesen |
| Konvertieren-Knopf | QFile::copy() + „Conversion complete!" |
geht durch uft_convert_file() |
| Datei-Browser | 13 erfundene Verzeichniseinträge | sagt, dass er nicht liest |
| Belegungskarte | jede Diskette grün „frei" | grau „?", Vorbehalt in der Anzeige |
| Forensik-Bericht | Filesystem: ✓ Valid ohne ein Byte zu lesen |
„— not checked" mit Begründung |
| Lizenz | SPDX: MIT auf einer GPLv2+-Portierung, in einem GPLv2-Projekt |
GPL-2.0-or-later |
Ziel dieses Releases: kein Satz ohne Messung. Diese vier Zahlen sagen, wie weit das Werkzeug geprüft ist — jede mit dem Weg, sie selbst nachzurechnen. Wer sie nachmisst und etwas anderes bekommt, hat einen Fehler gefunden, und der gehört gemeldet.
| Kennzahl | Messwert | Quelle |
|---|---|---|
| Ungeprüfte Formate | 56 von 88 auf T3. Belegt: T1=2, T1b=12, T2=18 | docs/VERIFICATION_TIERS.md; Kurzfassung im README.md |
| Angebotene Wandlungspfade | 12 von 44, davon 4 verlustfrei je mit Bit-Identitäts-Messung, 8 nur mit Zustimmung. 30 weist das Preflight-Tor als UNGEPRÜFT ab, 2 als unmöglich | abgeleitet aus src/core/uft_roundtrip.c durch scripts/update_inventory.py (DERIVED_CLAIMS) — bei jedem Commit gegen die Quelle geprüft |
| Leckende Tests | 0 von 266 unter LeakSanitizer, 0 von 266 unter UBSan. Der Weg dahin, jeder Schritt gemessen: 58 → 15 (MF-592) → 2 (MF-595/598) → 0 (MF-599) | CI-Auftrag „Sanitizer Checks", Schritte Full suite under ASan (reporting only, incl. leaks) und Full suite under UBSan. Die scharfen Tore (je 9 Tests) sind 9/9 |
| Bench-Alter je Controller | 1 von 9 hat je einen Tier-3-Bench: Greaseweazle, 2026-05-15. Die übrigen acht: nie | docs/CAPABILITIES.md, Abschnitt „Bench-Alter je Controller" |
Wie diese Zahlen ehrlich bleiben. Die zweite ist abgeleitet, nicht gepflegt — von Hand geführte Zahlen driften, und das ist in diesem Baum dreimal belegt (MF-526, MF-541, MF-567). Die dritte kommt aus dem CI-Protokoll, nicht aus einer Notiz. Die erste und vierte stehen in einem Dokument, das eine Zeile pro Format bzw. Controller führt.
Zum Testlauf: auf einem Entwicklungsrechner mit vollständigem Korpus
laufen 266/266 durch, davon einer ausgelassen: test_freezer
lässt 8 seiner 14 Prüfungen aus, weil es für das Action-Replay-Format
keine benannte Referenz gibt (siehe unten). Auf einem frischen Klon
überspringen weitere sechs Testdateien, weil tests/corpus/ gitignored
ist (10 Dateien, nicht verteilbar geklärt). tests/corpus_free/ liegt im
Repo. Die Liste steht in docs/OPEN_ITEMS.md unter „Korpus-gebundene
Tests".
Und diese Zahl war vorher falsch. Bis MF-596 zählte das Aufruf-Makro
RUN_TEST in 32 Testdateien den Erfolg bedingungslos hinter dem
Aufruf, während ASSERT bei Fehlschlag nur aus der Testfunktion
zurückkehrte. main() gab (tests_passed == tests_run) ? 0 : 1 — also
immer 0. Diese Tests konnten nicht rot werden; sie druckten „PASSED" in
die Zeile direkt hinter „FAILED at line N".
Dahinter lagen sieben Tests mit 18 Prüfungen, die seit unbekannter Zeit fielen. Vier waren Testfehler (Vorlagen, die nicht hergaben, was die Prüfung behauptete). Drei waren echte Fehler im Format-Layer:
- Jedes Game-Gear-Abbild wurde als Master System gemeldet — die
Region steht im oberen Halbbyte von
$7FFF, gelesen wurde das untere. Der ROM-Größencode kam aus dem falschen Byte (MF-598). - Der Game-Boy-Kopf wurde als Speicherabbild überworfen, obwohl seine
Felder überlappen:
sizeof= 86 statt 80,cartridge_typelandete auf$014Cstatt$0147, die globale Prüfsumme sechs Byte hinter dem Kopfende (MF-598). - Die kennungslose Z80-Vermutung stand vor TAP und DSK und verschluckte beide Erkennungen (MF-598).
Ein achter zeigte sich nur unter Windows (/tmp fest verdrahtet,
MF-600) — „lokal grün" und „CI grün" sind zwei verschiedene Aussagen.
Alle drei Format-Fehler sind gegen eine benannte Referenz behoben,
die im Code steht: maxim-zhao/sega8bitheaderreader für SMS/GG, Pan Docs
für den Game-Boy-Kopf. Wo keine Referenz zu finden war — Action Replay —
ist nichts angepasst, sondern ausgelassen: eine Seite an die andere
anzugleichen wäre Erfindung.
Diese Liste gehört zum Release, nicht in eine Fußnote:
- 56 von 88 tier-geführten Formaten sind ungeprüft (T3). Belegt: T1=2, T1b=12, T2=18.
- 12 von 44 Wandlungspfaden werden angeboten, davon 4 verlustfrei mit Messung. Die übrigen weist das Preflight-Tor ab — das ist Absicht.
- Der Kopierschutz-Katalog (55+ Verfahren) hat keinen Aufrufer. Automatisch läuft Signal-Erkennung plus drei heuristisch benannte Schemata.
- Das Dateisystem wird nicht gelesen. Verzeichnisliste und Belegungskarte sagen das jetzt in der Anzeige.
- Ein Test lässt 8 seiner 14 Prüfungen aus (
test_freezer): für das Action-Replay-Format gibt es keine benannte Referenz, und beide Seiten aneinander anzupassen wäre Erfindung. Ausgelassen, nicht grün gemacht. - Kein Bedien-Abnahmetest. Sechs kopflose Qt-Tests decken Logik und Anzeige aller Reiter ab — nicht Aussehen, Bedienfluss oder alles hinter einem modalen Dialog.
- Tier-3-Hardware-Prüfung fehlt weiterhin — kein Gerät verfügbar, an die Gemeinschaft delegiert.
Der Release-Text von v4.1.5 nannte zwölf Punkte, die ausdrücklich „auf v4.1.6 verschoben" wurden. Ein Release, das behauptet, jede Aussage sei gemessen, muss auch diese Liste abschließen — sonst bleibt eine Zusage offen, die niemand mehr nachschlägt.
Stand: 2 erledigt, 10 bleiben. Jede Zeile mit dem, was gemessen wurde.
| # | Punkt | Stand |
|---|---|---|
| 1 | HIL.GW formale Bench-Sitzung | bleibt — kein Gerät hinter diesem Projekt (MF-310); Tier-3 kann nur von einem fremden Schreibtisch kommen |
| 2 | UFT-008 SCP-Direct Tier-3 | bleibt — dito. 22/22 Opcodes byte-exakt gegen samdisk ist eine benannte Referenz, kein Gerät |
| 3 | M3.2 XUM1541 Tier-3 | bleibt — dito. Drahtprotokoll gegen die OpenCBM-Quelle geprüft (MF-301), Emulator 56/56 |
| 4 | M3.3 Applesauce ?disk-Zustandsautomat |
bleibt — gemessen: ?disk kommt in src/ nicht vor. ?vers ist verdrahtet |
| 5 | UFI Windows + macOS | bleibt — nur ufi_linux.c (SG_IO). docs/CAPABILITIES.md sagt das seit MF-589 als „weiter offen" statt „v4.1.6" |
| 6 | ARCH-9 XUM1541 macOS-.dylib-Lader |
bleibt — gemessen: OpenCbmLibrary::load() kommt im Baum nicht vor |
| 7 | 5-und-3-GCR (Apple DOS 3.2) | bleibt — gemessen: kein Code dafür. Neuer Decoder-Code fällt unter die EINFRIER-REGEL und braucht zuerst eine benannte Referenz |
| 8 | FM-Decoder vervollständigen | bleibt, nicht belegt — der Unvollständigkeits-Vermerk steht nicht mehr in flux_decode_fm(), aber die Historie der Datei ist ein einziger Sammel-Commit (4d622192 v4.1.0 Release). Ob vollständig oder nur der Vermerk entfernt, lässt sich hier nicht entscheiden |
| 9 | Sidecar je Wandler genauer | bleibt — gemessen: src/core/uft_loss_report.c schreibt weiter Einträge auf Kategorie-Ebene ("category"), keine Spur-Zählungen |
| 10 | uft-decode-CLI verdrahten |
bleibt, Eigentümer-Entscheidung — cli/uft-decode/main.c liegt im Baum, ist aber in keinem Bausystem (0 Treffer in .pro und tests/CMakeLists.txt). UFT ist ein GUI-Werkzeug; ob es eine CLI geben soll, ist keine Frage, die der Code beantwortet |
| 11 | MF-271 src/switch/ + src/cart7/ löschen |
✅ erledigt (MF-441) — gemessen: beide Verzeichnisse existieren nicht mehr |
| 12 | USBFloppy SG_IO-Mock | ✅ erledigt — tests/emulators/ufi/ mit Firmware-Zustandsautomat; er treibt den Produktions-HAL (src/hal/ufi.c) über den eingesetzten uft_ufi_ops_t-Unterbau |
Sechs der zehn offenen brauchen ein Gerät, das es hier nicht gibt. Einer ist eine Eigentümer-Entscheidung. Drei sind unfertige Arbeit, und einer davon (Nr. 8) ist ehrlicherweise unbekannt statt offen — das ist ein Unterschied, und er gehört benannt.
Gemessen gegen die acht Punkte der Freigabe-Bedingung:
| # | Bedingung | Stand |
|---|---|---|
| 1 | ctest 100 % auf dem Tag-Kandidaten |
✅ 266/266, ein benannter Skip (test_freezer, 8 von 14 Prüfungen; Grund oben) |
| 2 | Sanitizer grün inkl. Testcode, 0 Lecks | ✅ ASan 0/266, UBSan 0/266, beide scharfen Tore 9/9 |
| 3 | Alle Tore 0, keines abgeschaltet oder aufgeweicht | ✅ scripts/check_consistency.py — 36 Kategorien, alle 0. Keines in den Warn-Modus geschoben |
| 4 | Offene P0 nur eigentümer- oder hardware-blockiert | ⚠ siehe unten — die Bedingung kennt eine Kategorie nicht |
| 5 | Version, CHANGELOG, verschobene Liste 12/12 | ✅ VERSION.txt = uft_version.h = 4.1.6; die zwölf Punkte abgerechnet (2 erledigt, 10 bleiben, jeder mit Messung) |
| 6 | Vier Kennzahlen mit Wert und Quelle im Release-Text | ✅ Abschnitt „Die vier Kennzahlen" |
| 7 | CI-Matrix grün auf genau dem getaggten Commit | ⏳ offen bis zum Tag. Zuletzt vollständig grün auf 3700fbdf: Konsistenz, Linux 6.7.3/c++17, Linux 6.10.1/c++20, macOS, Windows — dazu Sanitizer, Audit und Coverage. Der Tag-Commit selbst braucht denselben Nachweis; ein grüner Vorgänger zählt nicht |
| 8 | Kein ungedeckter Satz in README, CLAUDE.md, Release-Text | ✅ zwei veraltete Sätze berichtigt (MF-602); die Zahlen der Wandlungsmatrix sind seit MF-541 abgeleitet, nicht gepflegt |
Die Bedingung verlangt, offene P0 seien entweder eigentümer- oder hardware-blockiert. Ein Eintrag ist keines von beidem:
P0-2 — der Kopierschutz-Katalog hat keinen Aufrufer. 38 Dateien, 350
Funktionen, 4 Aufrufer, 16 von einem Test berührt (gemessen MF-557,
seither von scripts/audit_protection_claims.py festgehalten).
Der ist nicht blockiert, sondern entschieden: ihn anzuschließen hieße, 334 ungeprüfte Funktionen an ein forensisches Urteil zu hängen — genau die Lage, aus der die fünf fabrizierten Parser kamen. Das Ziel dieses Releases sagt es selbst als Nicht-Ziel: „niemals Code verdrahten, damit er benutzt aussieht."
Die Dokumentation ist seit MF-508/509 ehrlich; README.md schreibt
wörtlich „a function that exists is not a feature until something calls
it". Der Eintrag bleibt also offen — als Entscheidung, nicht als
Rückstand. Wer ihn schließen will, braucht zuerst Prüfung für die 334,
nicht einen Aufruf.
- Bedien-Abnahme der neuen Oberflächen-Funktionen (MF-496
Feineinsteller, MF-501 Schadenslage-Zeile). Protokoll liegt in
docs/CLICK_SESSION_v4.1.6.md; ein Mensch an der Maus kann es nicht ersetzen. - Tier-3-Bench für die acht Controller ohne Gerät — an die Gemeinschaft delegiert (MF-310).
- Ein echtes Action-Replay-Einfrierabbild, damit
test_freezerseine acht ausgelassenen Prüfungen wieder aufnehmen kann. - Lizenz-/Verteilungsklärung für die 10 Dateien in
tests/corpus/.
Release Date: 2026-06-05
Tag: v4.1.5 (squash-merge of tests/v4.1.5-hardening onto main,
39 commits MF-240..MF-276 plus .claude hygiene v2/v3 plus Phase A/B/D
demo-ready bundle).
Version-string convention: VERSION.txt carries the numeric
4.1.5 per the existing release convention.
The Greaseweazle production path (src/hal/uft_greaseweazle_full.c) is
byte-identical to the v4.1.4-rc1 commit that was hardware-verified
2026-05-15 — zero lines changed across the 39 squashed commits, the
file is in .claude/CLAUDE.md's protected-paths list. HIL.GW
formal-bench session (tests/hil/run_hil.py) is deferred to the
post-tag window; substantively the HIL output for v4.1.5 would be
identical to v4.1.4-rc1's because the GW code is unchanged.
UFT-008 SCP-Direct Tier-3 verification still requires real SCP
hardware and is explicitly v4.1.6 scope — see docs/CAPABILITIES.md
for the per-controller status matrix.
v4.1.5 hardens the v4.1.4 release across 9 dimensions without breaking any v4.1.4 user-facing behavior. The Greaseweazle workflow is unchanged. Everything new is either honesty (more accurate status labels), structural correctness (ABI gate, single-chokepoint preflight), or scaffolding (Tier-2.5 simulators, sidecar reports).
Code-side gates closed in this release (V415_GOAL_PLAN.md):
| Gate | Status | Impact |
|---|---|---|
| All 9 V2 providers have a live code path | ✅ MF-249..MF-258 | No silent no-op when user clicks "Connect" on a non-Greaseweazle controller |
| 22/22 SCP USB opcodes byte-verified | ✅ MF-254 + MF-261 | Cross-referenced against samdisk/SuperCardPro.h. Pre-MF-254 placeholder bytes (0x02-0x40) are replaced with the documented 0x80-0xD2 opcode space |
| SCP-Direct M3.1 read-flux payload parsing | ✅ MF-276 | Full samdisk-reference algorithm: CMD_READ_FLUX → CMD_GET_FLUX_INFO → CMD_SENDRAM_USB; 16-bit big-endian samples decoded with 0x0000-overflow accumulator, reset per revolution. 9/9 mock-tests pass. impl_complete stays false until real-HW bench |
uft_format_plugin_t ABI gate |
✅ MF-260 | New api_version field + _Static_assert(sizeof == 216) + registrar reject for future-ABI plugins |
| Plugin Prinzip-7 compliance 84/84 | ✅ MF-262 + MF-263 | spec_status and features populated for every plugin (was 15/84 and 5/84 respectively) |
| LOSS.preflight Phase 1 + 2 | ✅ MF-263 + MF-268 | All 44 conversion paths gated through uft_preflight_check() at the uft_convert_file() chokepoint. LOSSY_DOCUMENTED paths emit a .loss.json sidecar after successful convert with category-level loss entries |
| Tier-2.5 hardware simulator system | ✅ MF-267 + MF-270 | KryoFlux + FluxEngine + FC5025 via Python "fake binary" sims, SCP-Direct + XUM1541 via libusb-mock framework, Greaseweazle via serial-protocol sim. 10/10 SIMULATED in CI |
| ARCH-7-C Teensy-probe wrapper | ✅ MF-213 + MF-263 | Probe-on-Connect with mismatch warning for ADFCopy ↔ Applesauce (both Teensy 0x16C0:0x0483) — prevents disk-corrupting cross-wire |
| Test pass-rate 47/180 → 153/153 | ✅ MF-245..MF-260 + MF-270 + MF-276 | Stale exclusions reviewed; new test_plugin_abi.c, test_scp_direct_usb_mock.c (9 tests), test_xum1541_usb_mock.c |
| Build-system parity baseline shrunk | ✅ MF-262 + MF-272 | verify_build_sources.py baseline 224 → 219 → 216 (LOSS.preflight sources wired) |
| qmake .pro wiring fixed | ✅ MF-259 + MF-266 | preflight/loss_report/roundtrip sources, libusb IMPORTED target, audit-vector pins |
| Phase A demo-ready docs bundle | ✅ MF-273 + MF-274 | docs/SHOWCASE.md (179 LOC), docs/CAPABILITIES.md (189 LOC), docs/demo/QUICK_DEMO.md (253 LOC), docs/KNOWN_ISSUES.md Demo-Impact annotation, docs/release/v4.1.5_github_release_body.md |
uft_scp_direct_read_flux() was the last NOT_IMPLEMENTED in the SCP
M3.1 scaffold. v4.1.5 implements it as a byte-for-byte port of the
samdisk SuperCardPro::ReadFlux() reference (the de-facto open-source
SCP USB reference implementation).
Algorithm:
CMD_SIDE [side]CMD_READ_FLUX [revs, ff_Index=0x01]— waits on index pulseCMD_GET_FLUX_INFO→ 40-byte rev_index table (5 pairs of[index_time_be32, flux_count_be32])- Per revolution:
CMD_SENDRAM_USB [offset_be32, length_be32]→flux_count × uint16_tbig-endian samples - Decode:
0x0000accumulates 0x10000 ticks (cell overflow); non-zero emits(accum + sample) × 25 ns; accumulator resets per revolution (matches samdisk — each capture starts fresh after its index pulse)
uft_scp_direct_write_flux() remains intentionally NOT_IMPLEMENTED.
A malformed flux stream can physically damage forensic media; write is
blocked at the HAL boundary until the read path is bench-verified
against real SCP hardware. Forensic safety overrides feature-
completeness.
Mock-test coverage (9 tests in tests/test_scp_direct_usb_mock.c):
open/close, seek with packet format verification, error-status handling,
bulk-out timeout, read_flux two-revs happy-path with full 5-step wire
dance, overflow accumulation ({0000, 0010, 0000, 0000, 0020} → 2
emits), single-revolution rejection (revs=1 → UFT_ERR_INVALID_ARG,
no silent coercion), per-revolution accumulator-reset regression-guard.
The biggest forensic-confidence win of v4.1.5: all V2 providers can now be exercised end-to-end without owning the corresponding hardware.
Three different mocking strategies for three different transport layers:
| Transport | Mocking strategy | Coverage |
|---|---|---|
| QProcess (CLI subprocess: KryoFlux DTC, FluxEngine, FC5025 fcimage) | Python fake-binary scripts on PATH | 7/7 SIMULATED |
| libusb-direct (SCP-Direct, XUM1541) | Scripted libusb-mock framework (tests/usb_mock/), per-target wrapper TU, zero production-code changes |
5+3 = 8 SIMULATED |
| USB-CDC virtual serial (Greaseweazle, Applesauce, ADFCopy) | Python protocol simulators paired with com0com/socat | 3 byte-compile-verified |
The libusb-mock framework is novel: a libusb_mock.h header that
pre-defines LIBUSB_H (the real libusb's include-guard) so any later
#include <libusb-1.0/libusb.h> becomes a no-op. The mock provides
minimal libusb types + a scripted-exchange queue that test code
populates. Production C-HAL code is bytewise unchanged.
Coverage runs in CI on every push via the Audit job, reporting SIMULATED / FAIL / NOT_RUN per controller. SIMULATED is never reported as PASS — real hardware (Tier 3) remains the only PASS authority.
uft_convert_file() is the single chokepoint for all 44 conversion
paths. v4.1.5 wires uft_preflight_check() into that entry point
(Phase 1, MF-263) plus .loss.json sidecar emission for successful
LOSSY_DOCUMENTED conversions (Phase 2, MF-268).
- LOSSLESS runs silently as before.
- LOSSY_DOCUMENTED requires
accept_data_loss=true— the GUI/CLI must obtain explicit user consent. On success, a category-level<target>.loss.jsonis written: FLUX_TIMING, WEAK_BITS, INDEX_PULSES, MULTI_REVOLUTION, plus a path-quality warning. - IMPOSSIBLE and UNTESTED ABORT with a diagnostic.
Per-track exact loss counts (e.g. "23 weak bits in track 18 sector 4") are v4.1.6 follow-up — Phase 2 emits category-level entries, sufficient for the user to know "this converter lost flux timing".
MF-261 cross-referenced all 22 SCP USB command bytes + response code +
checksum init in include/uft/hal/uft_scp_direct.h against
samdisk's SuperCardPro.h.
samdisk has shipped SCP read/write against real hardware since 2017
and is the de-facto open reference implementation.
Pre-MF-254 the header carried six placeholder opcodes (0x02-0x40)
traceable to a8rawconv, not to the vendor SDK. The current opcodes
0x80-0xD2 (CMD_SELA..CMD_SCPINFO), PR_OK=0x4F, CHECKSUM_INIT=0x4A
are byte-exact with samdisk. MF-276 adds CMD_GET_FLUX_INFO=0xA1,
CMD_SENDRAM_USB=0xA9, and ff_Index=0x01 flag (also byte-exact).
Before v4.1.5 only 5 plugins had a hand-curated .features array
(ADF, HFE, IPF, STX, WOZ) and 15 had a non-UNKNOWN .spec_status.
The audit_plugin_compliance.py script flagged the gap as a §7.1 +
§7.2 violation.
Two mass-populator scripts solved it:
scripts/populate_spec_status.py— per-format-subdir mapping (OFFICIAL_FULL / OFFICIAL_PARTIAL / REVERSE_ENGINEERED / DERIVED) based on documentation provenance.scripts/populate_features.py— derives a per-plugin feature matrix from.capabilitiesbitfield (Read/Write/Create/Flux/ Timing/Weak Bits/MultiRev → SUPPORTED or UNSUPPORTED).
Result: 84/84 (100%) Prinzip-7 compliance. Per-plugin hand-tuning where the heuristic was too coarse (e.g. WOZ PARTIAL with explanatory note) is ongoing in v4.1.6 as documentation hygiene.
| ID | Pre-v4.1.5 state | Resolution |
|---|---|---|
| UFT-002 | CMakeLists.txt header comment "Version: 4.1.3" stale | Removed numeric; refers to VERSION.txt SSOT |
| UFT-003 | HardwareTab honest-stub providers visually identical to production GW | Orange "Disconnect (Beta)" Button + tooltip distinct from green production GW |
| UFT-004 | uft_format_plugin_t had no API-version field; ABI-bomb risk |
api_version + _Static_assert(sizeof==216) + registrar gate |
| UFT-005 | test_transitions_ns_contract covered only MockProviderV2 |
KryoFlux + FluxEngine contract-shield FFI added |
| UFT-006 | .claude/CLAUDE.md listed 6 controllers, code had 9 V2-Providers |
List corrected with per-provider status |
| UFT-007 | Claimed VID/PID mismatch between header + GUI-hint | Verified consistent (MF-212): 0x16D0:0x0F8C via shared macro |
| UFT-T01 | <threads.h> include broke MinGW build |
__has_include guard added |
| UFT-T02 | 4 tests linked phantom symbols | Per-test target_sources wired correctly |
| UFT-T04 | 43 tests excluded from build | Stale exclusions reviewed; 4 re-enabled (test_scp_direct_hal, test_applesauce_hal, test_fnmatch_shim, test_whdload_resload). Remaining 38 are MF-011-deleted-impl orphans, documented per-line |
| UFT-T05 | events CMakeLists Standalone-Pfad bug | Verified already fixed (uses CMAKE_CURRENT_SOURCE_DIR) |
| ARCH-2 | KryoFlux/FluxEngine providers fabricated FluxCaptured from container bytes | Closed in MF-208 (KF) + MF-209 (FE); shield test in MF-260 prevents regression |
| SCOPE | src/switch/ (10MB Nintendo Switch + mbedtls) off-mission |
Decision: Option C — Delete, post-tag (MF-271). Backup tag planned: archive/pre-mf271-switch-removal |
These are intentionally deferred to v4.1.6 (with reasoning):
- HIL.GW formal bench session — production GW code unchanged so
substantive risk is null, but the formal report
(
releases/v4.1.4/hil_report.md) is still pending. - UFT-008 SCP-Direct Tier-3 verify — needs real SCP hardware
- M3.2 XUM1541 libusb — wired but Tier-3 hardware verification needs opencbm + real device
- M3.3 Applesauce serial-protocol full handshake —
?versis in; the?disk-readx state machine still has gaps - UFI Windows + macOS backends (
ufi_win.cvia DeviceIoControl,ufi_mac.cvia IOKit). Linux SG_IO is in (MF-207) - ARCH-9 XUM1541 macOS .dylib loader — needs
OpenCbmLibrary::load()path - 5-and-3 Apple GCR (DOS 3.2 13-Sektoren)
- FM-Decoder completion —
flux_decode_fmflagged as incomplete in the decoder source - Per-converter sidecar precision — current Phase 2 emits category-level entries; per-track exact counts are v4.1.6
uft-decodeCLI binary build wiring — scaffold atcli/uft-decode/ships; CMake/qmake enable + emulator.yml round-trip integration is v4.1.6- MF-271 Switch/cart7 delete — decided 2026-05-25, execution post-tag
- USBFloppy SG_IO mock — Tier-2.5 framework supports it but SG_IO-mock not yet written; v4.1.6
- No breaking changes. v4.1.4 → v4.1.5 is purely additive.
- The
uft_format_plugin_tABI gate (MF-260) is opt-in: legacy plugins withapi_version=0are accepted with a one-shot stderr warning. Plugin authors should set.api_version = UFT_PLUGIN_API_VERSIONbefore v5.0. - LOSS.preflight requires explicit consent for
LOSSY_DOCUMENTEDconversions. GUI workflows that previously ran lossy converts silently now need to setaccept_data_loss=trueinuft_convert_options_t(preserve_errorsfield maps to this). uft_scp_direct_read_flux()rejectsrevolutions=1withUFT_ERR_INVALID_ARG(SCP firmware requires ≥2; honest, no silent coercion). Provider layer (src/hardware_providers/scp_provider_v2.cpp) still acceptsrevolutions=1in its parameter range — open audit finding to be reconciled in v4.1.6 (seedocs/KNOWN_ISSUES.md).
Detailed per-controller capability matrix (Tier-3 / Tier-2.5 / mock-only /
scaffold) lives in docs/CAPABILITIES.md.
Status quick-glance:
- 1 production: Greaseweazle (Tier-3 PASS, read+write)
- 3 read-real via subprocess wrapper: KryoFlux (DTC), FluxEngine (CLI), FC5025 (fcimage)
- 3 libusb-wired, mock-only: SCP-Direct, XUM1541, Applesauce (byte-protocol validated)
- 1 USB-CDC-wired with sim: ADF-Copy
- 1 Linux-only: USB-Floppy (SG_IO; Win/Mac backends are v4.1.6)
Any provider not in Tier-3 PASS reports not_implemented for unwired calls
— never a silent no-op. GUI shows orange "Preview" badge instead of
the green Production badge.
- Local: MinGW-w64 g++ 13.1.0, Qt 6.10.1
- CI: Linux GCC (Qt 6.7.3 + 6.10.1 with c++17 + c++20), macOS Clang, Windows MinGW
- Plugin compliance:
audit_plugin_compliance.py84/84 ✓ - Build parity:
verify_build_sources.py216/0 - Consistency:
check_consistency.py0/0/0/0 ✓ - Test suite: ctest 153/153 (100%)
- Tier-2.5 simulator gate: 10/10 SIMULATED (Audit CI job)
- All 5 CI workflows green on
tests/v4.1.5-hardeningHEAD before squash (Audit · Emulator-CI · Sanitizer · Coverage · CI)
39 commits squashed from tests/v4.1.5-hardening (MF-240..MF-276 plus
.claude hygiene v2/v3 plus Phase A/B/D bundles plus hermes-PoC park):
MF-240 docs(release): pre-tag cleanup
MF-241/2 docs/release alignment
MF-243 build(release): uft_p24_verify.sh — 4-BEFUND verification gate
MF-244 build(hil): goal v4.1.4-final Phase-3 infrastructure
MF-245/6 v4.1.5-hardening: full audit findings — 26%→100% pass rate
MF-247 UFT-003 HardwareTab honest-stub visual + UFT-007 verified
MF-248 M3.3 concrete Applesauce production-transport plan
MF-249 M3.3 QSerialPort-backed Applesauce transport
MF-250 M3.3 Applesauce live end-to-end + HardwareTab wiring
MF-251/2/3 M3.3 Applesauce sim + ADFCopy transport — UFT-001 3/9
MF-254 M3.1 SCP-Direct libusb wiring — UFT-001 4/9
MF-255 M3.2 XUM1541 libusb wiring — UFT-001 5/9
MF-256 M3 FluxEngine + KryoFlux QProcess runners — UFT-001 7/9
MF-257 M3 FC5025 fcimage QProcess runners — UFT-001 8/9
MF-258 M3.4 USB Floppy UFI runners — UFT-001 9/9 COMPLETE
MF-259 CI qmake .pro wiring + libusb IMPORTED + audit-vector pins
MF-260 UFT-004 plugin ABI gate + UFT-005 KF/FE shields + UFT-T04 reductions
MF-261/2 V415-PLAN — SCP.D1 verify + spec_status 100% + BUILD.rebaseline
MF-263 V415-PLAN deep — features 100% + LOSS Phase 1 + EMUCI/release scaffolds
MF-264 fix stray-comma after trailing comment in 79 plugin literals
MF-265 UFT_FORMAT_ID_T_DEFINED guard in roundtrip.h + format_validate.h
MF-266 add preflight + loss_report + roundtrip sources to .pro
MF-267 Tier-2.5 hardware simulator system (KryoFlux/FluxEngine/FC5025)
MF-268 LOSS.preflight Phase 2 + ADFCopy sim + CI gate + RELEASE_NOTES draft
MF-269 hw simulators self-contained on CI fresh checkout
MF-270 Tier-2.5 USB-mock framework (SCP/XUM/GW without hardware)
MF-272 rebaseline build_system_baseline (3 resolved entries)
MF-273 Phase A demo-ready bundle (SHOWCASE+CAPABILITIES+KNOWN_ISSUES Demo-Impact+RELEASE_NOTES section)
MF-274 Phase C.1 QUICK_DEMO.md (10-min demo script pull-forward)
MF-275 Phase B/D pre-work bundle + rescue ignored release files
MF-276 M3.1 SCP-Direct read-flux payload parsing — full samdisk impl + per-rev-reset fix
+ chore(.claude): hygiene-pass v2 (a0f8187) — agent count sync + EOL + htb-mentor flatten
+ chore(.claude): hygiene-pass v3 (db14e70) — Eigenständigkeit, Eigenverantwortung, Vollständigkeit
+ docs(.claude): park hermes-agent PoC goal for post-v4.1.5-tag
+ spike(proto/hermes-eval): Session #1+#2 (skeleton + prompts + rubric + hermes CLI research)
Release Date: 2026-05 (v4.1.4-rc1 pre-release tag first;
14-day window; promote to v4.1.4 final on success).
Branch: main already carries the type-driven HAL refactor
(PR #24 squash-merged) and the 4 Phase-2 hardening commits
(PR #26 merged 2026-05-15). The v4.1.4 tag goes on main HEAD —
no further merge step required.
Version-string convention: VERSION.txt carries the numeric
4.1.4. The -rc1 / -rc2 suffix lives only in the git tag
(v4.1.4-rc1, v4.1.4), per the project's existing release-notes
header convention.
- Working Greaseweazle workflow: unchanged behavior, structurally hardened internals. Read / write / detect / motor / seek / RPM / recalibrate all preserve every byte they used to. Auto-detect on Connect still happens (now via the V2 surface).
- 5 X1541-family controller-combo entries gone — XA1541 / XAP1541 / XM1541 / XE1541 / X1541. They never had a working backend in any release; selecting them only produced an error dialog. Removed for honesty.
- 8 non-Greaseweazle controllers (SCP, KryoFlux, FluxEngine, FC5025, XUM1541, Applesauce, ADFCopy, USBFloppy) now display a clear "Controller routing pending" message on Connect instead of silently no-op'ing. Their V2 wrappers exist, are conformance- tested, and will be wired in v4.1.5 (task P1.23).
The Type-Driven HAL refactor underneath this RC is substantial — 10 mixin-composed V2 providers, codegen-driven GUI wiring, 65-section conformance harness, ~12 700 LOC of V1 hierarchy deleted. From a USER perspective the change set is:
- The Greaseweazle pipeline is preserved + hardened.
- Phantom-feature combo entries are cleaned up (UI more honest).
- Non-GW controllers display a "pending" message instead of silent no-op (UI more honest).
None of those break a working user workflow. UFT is an end-user
application — no third-party code links the deleted C++ classes —
so there is no external SemVer contract to honor. The PATCH bump
v4.1.3 → v4.1.4 reflects "hardening of the v4.1 line" honestly;
the refactor magnitude is documented in CHANGELOG.md's v4.1.4-rc1
section, in docs/REFACTOR_BRIEF.md (the architecture spec), and in
this release-notes file. Full rationale in REFACTOR_BRIEF.md §9.
- Windows COM-port
\\.\prefix bug across all serial-port providers (MF-145). - Applesauce ↔ ADFCopy USB VID/PID disambiguation (MF-146).
- Greaseweazle bootloader-firmware-mode detection (MF-129).
- MFM IDAM/DAM standalone sector parser for SCP→ADF/IMG/D64 paths (MF-141 / AUD-002).
- 14 test executables, all green:
test_hal_foundation,test_hal_conformance(65 sections),test_greaseweazle_v2,test_scp_provider_v2,test_kryoflux_provider_v2,test_fluxengine_provider_v2,test_fc5025_provider_v2,test_xum1541_provider_v2,test_applesauce_provider_v2,test_adfcopy_provider_v2,test_usbfloppy_provider_v2,test_mock_provider_v2,test_mock_hardware,test_wiring_runtime. scripts/check_consistency.py0/0/0/0.scripts/verify_build_sources.pyclean (no new divergence).tools/wiring_codegen_tests/run_tests.py6/6 (incl. drift detector forgenerated/tab_hardware_wiring.gen.cpp).- CI: green on Linux GCC (Qt 6.7.3 + 6.10.1), macOS Clang, Windows MinGW — all 3 platform jobs.
This is v4.1.4-rc1. 14-day window starts on tag. During the window:
- Hand-test on real Greaseweazle hardware
(
tests/HARDWARE_TRUTH_TESTS.mdchecklist). - File any regression in working v4.1.3 workflows as a release-blocker.
- Watch CI runs on the
refactor/type-driven-halbranch.
If nothing breaks in 14 days: squash-merge → v4.1.4 final tag.
What v4.1.4 honestly does not ship — flagged here so users and reviewers know what is intentionally absent, not silently broken (MF-235 / MF-240 follow-up to the original "queued for v4.1.5" block that pretended these were just architectural polish):
- M3.x production transports — not in this release. Only
Greaseweazle has a real USB/serial transport wired through. The
other 8 controllers (SCP-Direct, KryoFlux, FluxEngine, FC5025,
XUM1541, Applesauce, ADFCopy, USBFloppy) have V2 wrappers that are
conformance-tested against the Mock surface but display
"Controller routing pending" on Connect. M3.1 (SCP USB), M3.2
(XUM1541 USB), M3.3 (Applesauce serial) and the remaining
transports are v4.1.5 scope — see
docs/MASTER_PLAN.md§M3. - ARCH-7 sub-B / sub-C field-validation follow-ups. Sub-B
(SCP
0x16D0:0x0F8C) and sub-C (ADF-Copy / Applesauce stock-Teensy0x16C0:0x0483two-tier probe) are implemented in code (MF-212 / MF-213) and unit-tested; they have not yet been re-verified end-to-end on real hardware after the type-driven HAL refactor. The single-device readout that produced the original ID values still stands, but a post-refactor HIL pass on SCP + ADF-Copy + Applesauce is queued for v4.1.5. Seeaudit/ARCH-7_VID_PID.md. - §6.1 Emulator-CI — not in this release. The HIL runner
(
tests/hil/run_hil.py) has a two-tier Golden-Reference catalog (CI-runnable software tier + hardware tier), but no emulator loop (Greaseweazle firmware simulator, virtual SCP/KryoFlux device, WinUAE / VICE harnesses) runs in GitHub Actions yet. The hardware tier therefore staysNOT_RUNin CI by design; only the software tier is exercised. Emulator-CI is planned for v4.3 — seedocs/M3_HAL_PLAN.md§M3.5. - ARCH-8 — official protocol sources not yet vendored. GW
cmd.h, SCP SDK, OpenCBM and fluxengine / DTC are currently declaredrecalled(working from memory + observed behaviour) in the audit-compliance matrix. Promoting them tovendoredis what turnsaudit.ymlinto a real CI gate — until then the gate is advisory. Vendoring is v4.1.5+ scope. - §1.2 Loss-Report wiring incomplete. The 44
convert_*format-conversion paths still need ~10 lines of glue each to propagate "data that did not survive" into the audit trail. Lossy conversion (Flux → Sector drops timing + weak bits) is labelled as lossy at the format-graph level, but per-conversion loss reports are not yet emitted end-to-end. v4.1.5+ scope. - Plugin-Compliance matrix sparsely populated. Of 80 plugin
registrations:
spec_statusis populated for 15,featuresfor 5,compat_entriesfor 1,is_stubflag for 0/287 stubs. The data model is in place (MF-187 onward), filling it iteratively is 4.2.x scope. - ARCH-9 — XUM1541 macOS
.dylibload path. The libxum1541 shared-object lookup on macOS does not yet match the system's Homebrew //usr/local/libconventions. Linux + Windows paths work; macOS users currently need a manualDYLD_LIBRARY_PATH. v4.1.5 scope.
These are real gaps, not paperwork. v4.1.4 is shippable because the Greaseweazle workflow — the one path used in practice — is preserved + hardened, and the 8 non-GW controllers show an honest "pending" message instead of silently no-op'ing. Anyone relying on a non-GW transport should wait for v4.1.5.
Release Date: 2026-04-16
- CRC / status flag propagation wired across the PLL → sector pipeline
- IMD + FDI read_track (real ImageDisk + Formatted Disk Image extraction)
- Plugin-B parsers added for DO, PO, ADL, V9T9, VDK, STX, PRO, ATX, ADF, SCL
- write_track support for TRD, D64, ATR, SAD, SSD, HFE, D80, D82, D71, D81
- Format registry: 138 IDs / 80 plugin registrations after dead-code cleanup
- 6 hardware controllers: Greaseweazle production; SCP-Direct, XUM1541,
Applesauce as M3 partial scaffolds (real lifecycle + utility code,
USB/serial wiring pending — see
docs/MASTER_PLAN.md§M3)
- 3 silent I/O errors in SAD, SSD, HFE parsers
- NULL-checks and silent-error fixes in 8 registry plugins
- Memory allocation limits, bounds checks, forensic fill (security pass)
- CMake Sanitizer / Coverage builds — dead file references removed
- Test include paths repaired
- Cleanup: 674 orphaned source files deleted (~386 000 LOC dead code)
- 109 dead format files removed from disk
- 165 non-floppy stubs removed (32 active plugins after pruning)
- 6 test modules excluded (missing dependencies)
- Some C headers use
protectedas field name (C++ incompatible) - DeepRead forensic modules (write-splice, aging, cross-track, fingerprint, soft-decode) and ML analysis are planned for v4.2.0 — not in this release
See CHANGELOG.md for the complete entry list.