Tie-up: one tested product (2026-10-27, branch claude/tie-up-2026-10-27)¶
One long Mac session tied Studio, the recompiler output, 32-bit, DLC/updates, the overlay, amiibo, Mii, camera, the online hook, packaging, docs and CI into one product and tested it end to end with real runs. iOS untouched. Decisions were taken without asking (the user was away): they are marked Decision.
Seams¶
| # | Seam | Result |
|---|---|---|
| 4 | One system data root | Done, tested. g_cfg.support_dir / napple_support_dir() (runtime/src/napple.h): NAPPLE_SUPPORT_DIR or ~/Library/Application Support/Napple, independent of --save-root; the Mii database, fonts, icon, amiibo store and applet scratch use it (mii.c, pl.c, account.c, applet.c, offline.c); the launcher installs fonts there (not under --save-root); Studio uses SystemData.supportDir; boot_test.sh/golden.py pass only --save-root, so they resolve the same root. test_svc_sysroot.c (8 checks), Studio SystemDataTests. The second game reads the same MiiDatabase.dat (MK8D: mii: list ... wrote 2 after LAS-side creation, in the e2e table). |
| 3 | Amiibo list source | Done, tested. The C loader (amiibo_lib_core.c) now reads the repository's full database (amiibos + characters/amiibo_series/game_series/types, icon URL derived) as well as the API shape; the frontend converter is gone (simulators.m); default source is the AmiiboAPI repository's raw database/amiibo.json (documented configuration, no data bundled; amiiboapi.com answers 404); cache + local folder remain the offline fallback; test_svc_amiibo 124 checks. |
| 2 | Overlay amiibo settings, lazy reader | Done. The overlay's amiibo section has list source and key fields saved to system.json (see seam 1); the reader is installed on first Place/Import or NAPPLE_AMIIBO, never at boot. The Break 0x35f202: not an nfp problem. It is SetTerminateResult 0x35f202 (fs 2-6905) raised by LAS itself 0.4-0.8 s after start, about 1 start in 5 (headless 1/7, windowed 2/7 seen; 0/25 and 0/40 with FS/IPC tracing, so timing dependent), with no nfp service ever opened by LAS. Reproduced with IPC error tracing: the game probes saves/files with concurrent GetEntryType/OpenFile (PathNotFound, expected on first use) and aborts after the burst on some starts. Not fixed: no deterministic reproduction; documented as KNOWN flaky in tools/e2e (retried up to 3 times) and in the LAS title note. |
| 1 | Studio panes | Done, tested. Settings > Mii (list, delete, Back Up, Export, Restore, Open Folder), Amiibo (source, key, store), Online (provider module; empty = NullProvider) and Library > Play with Log… (live runtime log, filter, copy, stop) in studio/Sources/NappleStudio/Views/SystemSettingsViews.swift; logic in NappleCLI.swift (SystemConfig, MiiStore, GameRun, SystemData): no second code path. They write ONE contract, <support>/system.json, read by the runtime at boot (sysconfig_core.c, env wins). Swift tests in SystemDataTests.swift (64 tests green). Screenshots: img/tie-up-studio-settings-{mii,amiibo,online}.png, img/tie-up-overlay-game-las.png. |
| 5 | Mii Maker child | Done, verified. The child exits when its parent is gone (NAPPLE_APPLET_PARENT + watch thread in main.c); libnapple-applet.dylib found in a dev tree (../build-lib) and bundled in Mac apps (pipeline.rs Contents/Frameworks, build_app.sh, make_release.sh, EngineInstaller); game frames are paced (60 Hz per swap interval) while an applet hides the game (gfx_glue.c; the presenter dropped them without pacing: 17-20k frames/60 s); scripted exit NAPPLE_APPLET_SECONDS. Verified end to end: tools/e2e drives the real Mii Maker (firmware applet in the app) with NAPPLE_INPUT_SCRIPT + NAPPLE_SWKBD_ANSWER, the new Mii (UTF-16 name) is in MiiDatabase.dat, and MK8D's mii:u lists it after restart. |
| 6 | Camera (and microphone) | Done, tested. Generated apps are ad-hoc signed with com.apple.security.device.camera and com.apple.security.device.audio-input (bundle.rs mac_entitlements()), Info.plist has NSCameraUsageDescription and NSMicrophoneUsageDescription (for NAPPLE_AUDIN_SOURCE=mic); Studio's entitlements.plist has both. Decision: no App Sandbox (the system folders need an unsandboxed app); Rust tests assert the keys, no duplicates (codesign rejects a repeated key) and no app-sandbox. Verified with codesign -d --entitlements in every e2e run. |
| 7 | Online provider | Done. Already: a bad path/ABI/symbol falls back to the NullProvider with one online: log line (test_svc_online); now also a Studio setting and system.json key. |
| 8 | Tooling | studio/build_app.sh honours CARGO_TARGET_DIR and bundles the applet lib; test_timescale.c takes the best of five samples (robust under load); swift test note for ~/Documents. |
| 9 | A32 through Studio | Verified. MK8D built through Studio's pipeline (napple-studio-build, the exact Add Game code path with the three files): modules/a32jit.dylib embedded, update + DLC in the app (3 version dirs, 1 DLC), boots 45 s. |
| 10 | E2E | Done: tools/e2e/run.sh <las|smo|acnh|mk8d|all> (e2e.py), table below. |
| 11 | CI + docs | tests.yml: applet-lib, the golden selftest, py_compile/bash -n of the e2e script, and a studio-app job (build_app.sh smoke + helpers, applet dylib and entitlements checks); release.yml smoke checks the applet dylib (in Studio and the zip's bin/) and the entitlements; ci_build.sh builds the applet lib. Docs: new system data folders, updated hardware-simulators, network-providers, Studio, environment variables, testing guide (end-to-end section), the four title notes. gen_support.py --check and check_docs.py green. CI YAML was not run (no runner): validated by reading only. |
SMO threads.c stall¶
Not fixed (blocked: no reproduction). In the last two end-to-end runs SMO's golden route went past the old stall point (14143 and 15451 frames, frames 8000/9000 captured, 0-1 gfx warnings); the stall after ~7,500 frames (a spinning guest thread holding a virtual core, no timer preemption) did not occur, so there was nothing to test a preemption fix against. The cores model (threads.c: a guest thread owns one of three virtual cores until it blocks; preempt is honoured only at svc entry) is the place; a fix needs a signal-based preemption point and a deterministic repro first.
tools/e2e results (this branch, final end-to-end run, MacBook, real titles)¶
LAS was run first with everything (build ... golden); mk8d/acnh/smo in a second invocation on the same support root. Overlay rows say tabs [0..6] in the first invocation: the overlay has 4 tabs (0 Audio/Video, 1 FPS/Speed, 2 Controls, 3 Game/System, which holds the amiibo section) and higher numbers fall back to tab 0; e2e now renders [0,1,2,3] / [3]. Golden diffs are report only (non deterministic run to run even on main), goldens approved by the user are used for the comparison.
| title | step | status | detail |
|---|---|---|---|
| las | build | PASS | The Legend of Zelda Link's Awakening.app in 56 s |
| las | bundle | PASS | 64-bit; 1 version dir(s); 0 dlc |
| las | camera-mic | PASS | usage strings + camera/audio-input entitlements, no sandbox |
| las | mii-db | PASS | Mii Maker scripted: Mii 'E2E099770' (swkbd answer) is in the shared database (0 -> 2 Miis); every later title reads this file |
| las | boot | PASS | exit 0, 2582 frames in 45 s (floor 1200) |
| las | audio | PASS | 4263840 frames pushed, 2067135 above the noise floor, peak 0.273 |
| las | input | PASS | 3 of 3 scripted presses reached the guest |
| las | amiibo | N/A | the title never opened nfp:user/nfc:user in this route (the reader installs lazily: no abort) |
| las | save | PASS | 2 save file(s) written, second boot mounted them again (exit 0) |
| las | overlay | PASS | tabs rendered [0, 1, 2, 3, 4, 5, 6] (PNGs in $TMPDIR/napple-e2e-all/overlay/las) |
| las | golden | REPORT | report only: boot-to-village: exit 0, 8539 frames, warnings 0, crash False, frames {'1500': 'differs', '3000': 'differs', '4500': 'differs', '6000': 'ok'} |
| smo | build | PASS | SUPER MARIO ODYSSEY.app in 49 s |
| smo | bundle | PASS | 64-bit; 1 version dir(s); 0 dlc |
| smo | camera-mic | PASS | usage strings + camera/audio-input entitlements, no sandbox |
| smo | boot | PASS | exit 0, 5396 frames in 100 s (floor 1000) |
| smo | audio | PASS | 4786320 frames pushed, 2835880 above the noise floor, peak 1.000 |
| smo | input | PASS | 3 of 3 scripted presses reached the guest |
| smo | amiibo | N/A | nfp:user opened, but the title never started tag detection in this route (the scripted amiibo fires at the first StartDetection; nfp itself is covered by test_svc_nfp / test_svc_amiibo) |
| smo | save | PASS | 2 save file(s) written, second boot mounted them again (exit 0) |
| smo | overlay | PASS | tabs rendered [4] (PNGs in $TMPDIR/napple-e2e-all/overlay/smo) |
| smo | golden | REPORT | report only: title-and-kingdom: exit 0, 15451 frames, warnings 1, crash False, frames {'1500': 'ok', '4000': 'ok', '8000': 'new', '9000': 'new'} |
| acnh | build | PASS | Animal Crossing New Horizons.app in 238 s |
| acnh | bundle | PASS | 64-bit; 4 version dir(s); 3 dlc |
| acnh | camera-mic | PASS | usage strings + camera/audio-input entitlements, no sandbox |
| acnh | boot | PASS | exit 0, 1193 frames in 45 s (floor 1000) |
| acnh | audio | PASS | 2145120 frames pushed, 1593672 above the noise floor, peak 0.430 |
| acnh | input | PASS | 3 of 3 scripted presses reached the guest |
| acnh | amiibo | N/A | nfp:user opened, but the title never started tag detection in this route (the scripted amiibo fires at the first StartDetection; nfp itself is covered by test_svc_nfp / test_svc_amiibo) |
| acnh | save | PASS | 2 save file(s) written, second boot mounted them again (exit 0) |
| acnh | overlay | PASS | tabs rendered [4] (PNGs in $TMPDIR/napple-e2e-all/overlay/acnh) |
| acnh | golden | REPORT | report only: airport-counter: exit 0, 2624 frames, warnings 0, crash False, frames {'300': 'ok', '600': 'ok', '1200': 'differs'} |
| mk8d | build | PASS | Mario Kart 8 Deluxe.app in 198 s |
| mk8d | bundle | PASS | a32jit.dylib embedded; 3 version dir(s); 1 dlc |
| mk8d | camera-mic | PASS | usage strings + camera/audio-input entitlements, no sandbox |
| mk8d | boot | PASS | exit 0, 2442 frames in 45 s (floor 1000) |
| mk8d | audio | PASS | 2135280 frames pushed, 2115305 above the noise floor, peak 0.253 |
| mk8d | input | PASS | 8 of 8 scripted presses reached the guest |
| mk8d | amiibo | N/A | the title never opened nfp:user/nfc:user in this route (the reader installs lazily: no abort) |
| mk8d | save | PASS | 6 save file(s) written, second boot mounted them again (exit 0) |
| mk8d | mii-serve | PASS | mii:u opened, mii: list kind 0 flags 0x1: output buffer 9200 bytes (100 elements of 92), wrote 2 |
| mk8d | overlay | PASS | tabs rendered [0, 1, 2, 3, 4, 5, 6] (PNGs in $TMPDIR/napple-e2e-all/overlay/mk8d) |
| mk8d | golden | REPORT | report only: title-menus-race: exit 0, 8362 frames, warnings 0, crash False, frames {'600': 'ok', '1500': 'differs', '3000': 'differs', '4500': 'differs'} |
Known flaky (tagged KNOWN, never hit in the final run): LAS 0x35f202 early abort (above), ACNH Break at sdk+0xc80730.
Findings: SMO is silent for the first 45 s (boot step uses 100 s for it); SMO and ACNH open nfp:user but the 45 s routes never call StartDetection (amiibo step N/A for them; nfp state machine and picker are unit tested); NAPPLE_CLI in the user's shell points at the installed engine, e2e overrides it with this checkout's CLI.
Files¶
runtime (napple.h, main.c, gfx_glue.c, services/{applet,mii,pl,account,offline,amiibo_lib_core,sysconfig_core}, tests), frontend/src/simulators.m, audio (PCM counter), packaging/launcher.c, crates/napple-build (bundle.rs, pipeline.rs), studio (Sources, Tests, build_app.sh, packaging), tools/e2e (new), tools/ci_build.sh, .github/workflows, docs.