Design decisions
← POPSLoader docs ยท view on GitHub โ
Last updated: 2026-06-21 (BETA-13 session: frame-counted nav/desc-scroll, analog-fold gate + hysteresis, layered cover art / MISSING.png drop, OPL-style overscan, boot-sound toggle, HDD scan-slot steering, Codex BETA-13 audit). Released line: BETA-12 (2026-06-18; BETA-11 2026-06-15); active dev / rolling branch BETA-13-PLAY (created off BETA-12-PLAY @ 8d1e67a; BETA-12-PLAY is now archival / frozen; rolling-release.yml was repiped to publish from BETA-13-PLAY). BETA-13 is the in-progress rolling candidate, not yet cut. Shared volatile facts (Known Issues, Preservation Contracts, Settings behavior, Invariants, Hardware Status) live in STATE.md โ this log records the decisions, not the current-state snapshot.
DECISIONS
Decision Log Format
Each entry records:
- Date (YYYY-MM-DD)
- Decision
- Rationale
- Implications
- Evidence
Decision Log
2026-06-21 โ Frame-Count Nav Auto-Repeat Instead of the Wall Clock (d128779)
- Decision: Drive UI navigation auto-repeat by counting frames, never by reading
Timer.getTime(). Inresolve_navinsideUI.Pad.Listen(bin/POPSLDR/ui.lua):nav_fps = ((UI.SCR.Y or 448) >= 512) and 50 or 60;NAV_DELAY_FRAMES = ceil(nav_fps * 0.6);NAV_RATE_FRAMES = ceil(nav_fps * 0.2); a per-directionUI.Pad.NavHoldFrames[dir]counter increments once perUI.Pad.Listenframe. The press edge fires immediately; only UP/DOWN repeat (after ~0.6s, then ~0.2s โ 5/sec) โ LEFT/RIGHT stay edge-only so holding never page-jumps or spins the carousel. - Rationale:
Timer.getTime()returns rawclock()ticks, which are MICROSECONDS on the PS2 EE toolchain (CLOCKS_PER_SEC == 1e6; the binding isclock() - tick, byte-identical to Enceladussrc/luatimer.cpp). The UI historically treated that value as milliseconds, so every comparison against a_ms-named constant cleared its gate every frame and UP/DOWN auto-repeated ~60ร/sec ("one click = 5 lines", up/down only, on ANY device including a keyboard โ nuno6573 / LVD14 #504). The canonical Enceladus idiom is frame-counting: the sibling launchers OSDMenu-Configurator (ui_common.lua) and RETROLauncher (funciones.lua) both frame-count nav and never read the wall clock.UI.Pad.Listenruns once per vblank-paced frame, so one increment == one frame: frame-rate independent and unit-safe. - Implications: Nav feel is correct and identical in real seconds on PAL (50Hz) and NTSC (60Hz). HW-CONFIRMED 2026-06-21 (oldman63: up/down + analog-stick nav land on individual items, continuous scroll fine). The intermediate "treat the ยตs value as ยตs" stopgap (
adaa8ee) was superseded by this frame-count approach. Still on the ยตs-as-ms footing but parked (masked by a per-framemax_stepclamp, so not visibly broken): theMIN_ACTION_MSaction debounce (UI.InputConfig.MIN_ACTION_MS = 220,ui.lua:622) and the transition / carousel timers โ anos.clock()sweep is the proposed future fix (see the parked-decision entry below). - Evidence:
bin/POPSLDR/ui.luaresolve_nav(~line 4583),nav_fps/NAV_DELAY_FRAMES/NAV_RATE_FRAMES(4580โ4582),UI.Pad.NavHoldFrames(4430).src/luatimer.cpplua_time(clock() - tick, line 41). Commitd128779. See STATE.md > Hardware Status.
2026-06-21 โ os.clock() Sweep for the Remaining ยตs-as-ms Timers PARKED (not done)
- Decision: Do not convert the remaining wall-clock timers (the
MIN_ACTION_MSaction debounce and the transition/carousel timers) in this session. Recordos.clock()as the intended fix and leave it parked. - Rationale: Those sites still compare
Timer.getTime()ยตs against ms-named constants, but a per-framemax_stepclamp masks the 1000ร error so they are not visibly broken.os.clock()(stock Lua, returns seconds) is the only pre-converted Lua time source and is currently unused inbin/POPSLDR/*.lua; a future sweep should move the remaining_mssites to eitheros.clock()seconds or frame-counting (the nav + desc-scroll paths are already frame-counted). Doing it now would be unverifiable churn outside the BETA-13 nav fix. - Implications: A known, bounded debt โ not a regression. Any future edit to debounce/transition timing should land the
os.clock()(or frame-count) conversion rather than perpetuate the ยตs-as-ms pattern. - Evidence:
bin/POPSLDR/ui.luaUI.InputConfig.MIN_ACTION_MS(622) consumed byemit_action(now - UI.Pad.LastActionEventMs < MIN_ACTION_MS, ~4547); noos.clock()call exists inbin/POPSLDR/*.lua.
2026-06-21 โ Description Right-Stick Scroll Is Frame-Counted Too (c8fce10)
- Decision: Gate the game-description right-stick scroll on a frame counter, not the wall clock. In
bin/POPSLDR/ui.luathe scroll steps once everystep_frames = ceil(_secs * fps)frames (fps = (UI.SCR.Y>=512) and 50 or 60), with speeds expressed in seconds: slow (default)0.9s, medium0.3s, fast0.15s, read from the "Description scroll speed" setting (PLDR.DESC_SCROLL_SPEED). The per-frame counter isUI.GameList.DescScrollFrames. - Rationale: The old gate compared
Timer.getTime()(ยตs) against a_msvalue, so it was sub-frame and scrolled every frame regardless of the setting โ all three speeds felt identical (oldman63). Frame-counting makes the Fast/Med/Slow setting actually take effect and keeps the feel identical in real seconds on PAL and NTSC. Same root cause and fix shape as the nav auto-repeat decision above. - Implications:
DescScrollAt(the old wall-clock timestamp field) is replaced byDescScrollFrames. Desc-scroll feel is still pending a hardware eyeball (keep as pending, not broken). - Evidence:
bin/POPSLDR/ui.luadesc-scroll block (~2669โ2699):step_frames = math.ceil(_secs * fps),_secs= 0.9 / 0.3 / 0.15,UI.GameList.DescScrollFrames,PLDR.DESC_SCROLL_SPEED. Commitc8fce10.
2026-06-21 โ Gate the Analog-StickโD-Pad Fold on Real Analog Mode + Hysteresis (d233069, 9b8a38e)
- Decision: Fold the left analog stick into the d-pad direction bits in
UI.Pad.Listen(bin/POPSLDR/ui.lua) only whenPads.getMode()reports analog/DualShock (md == PAD_ANALOG or md == PAD_DUALSHOCK), and apply a per-axis hysteresis latch (assert a direction at|v| > 64, release only below|v| < 40, latch the asserted side). When not analog this frame, drop any latched stick direction (UI.Pad.StickV/StickH = 0). Add a new C bindingPads.getMode()=padInfoMode(port, 0, PAD_MODECURID, 0)โ the live negotiated controller mode. - Rationale: An ungated fold read a digital/non-analog pad's stale/zero analog bytes (
getLeftStick=ljoy - 127โ -127), injecting a phantomPAD_UP|PAD_LEFTevery frame and breaking up/down nav (Nuno6573 #BETA-13). The fix mirrors OPLsrc/pad.c:201(if ((pad->buttons.mode >> 4) == 0x07)). The pre-existingPads.getType()is miswired toPAD_MODETABLE(a capability-table entry) and is unusable for this gate;PAD_MODECURIDis the only authoritative live-mode source. Hysteresis stops a stick parked at the deadzone boundary from dithering across the threshold and edge-spamming the immediate-fire nav branch.lua_getleft/lua_getrightwere also hardened (zero-init + neutral 127 default, gate onpadRead's return) so an unread/failed frame yields centered (0,0) instead of stack garbage or a phantom -127. - Implications: Digital pads and keyboards navigate correctly; analog sticks still fold. HW-CONFIRMED 2026-06-21 (oldman63: analog-stick nav lands on individual items).
Pads.getModeis the binding to use for any future analog-mode gate; do not usePads.getTypefor that. The C hardening only removes uninitialized/failed-read phantoms โ a successful read on a digital-mode pad still returns a phantom -127, which is why the LuagetModegate is load-bearing. - Evidence:
src/luacontrols.cpplua_getmode(PAD_MODECURID, line 78; registeredgetMode, 278),lua_gettype(PAD_MODETABLE, 17;getType, 277),lua_getleft/lua_getrightneutral-default hardening (99โ146),PAD_ANALOG/PAD_DUALSHOCKglobals (PAD_TYPE_ANALOG/PAD_TYPE_DUALSHOCK, 342โ346).bin/POPSLDR/ui.luafold gate + hysteresis (4470โ4511). Commitsd233069,9b8a38e. See STATE.md > Hardware Status.
2026-06-21 โ Boot Sound On/Off Setting (6eba9e7, save fixed 9b8a38e)
- Decision: Add a Boot sound On/Off setting (default On) in Settings that gates the splash chime, persisted through the full settings chain.
- Rationale: Let users silence the boot chime; it's a low-cost, expected toggle.
9b8a38efixed the save path (the #501 regression where the toggle did not persist). - Implications: New persisted settings key
BOOT_SOUND(plumbed throughEncodeSettings,PLDR.CommitSettingsChangesvianext_boot_sound/next_state.boot_sound, and parse-back inLoadSettings). HW-CONFIRMED 2026-06-21 (oldman63: boot-sound saves and survives reboot). See STATE.md > Settings for the canonical key list. - Evidence:
bin/POPSLDR/ui.lua"Boot sound"AddCycle(3624โ3630),UI.BootSound(2307), boot-chime gate (UI.BOOT_SOUND.ENABLED/PLDR.BOOT_SOUND, 1103โ1104).bin/POPSLDR/system.luaEncodeSettingsBOOT_SOUND=(3098),CommitSettingsChangesnext_boot_sound(3566โ3567) /next_state.boot_sound(3592), parse-back (3436, 3506). Commits6eba9e7,9b8a38e.
2026-06-21 โ OPL-Style Overscan (CRT Inset): Identity Render-Transform at Permille 0 (1ac15ad C core, 6f0d742 UI, 73f0933 reset)
- Decision: Add an OPL-style render-coordinate overscan inset.
src/graphics.cppwraps itsgsKit_prim_*draw sites inOVX()/OVY(), which scale the whole UI uniformly toward screen center by an overscan permille; at permille 0 the transform is the identity, so the default render is byte-for-byte unchanged. New C bindingsScreen.setOverscan(permille)/Screen.getOverscan()(src/luaScreen.cpp). A Settings entry "Overscan (CRT inset)" is a live ยฑ5-step adjuster (Off/value, live preview viaScreen.setOverscan; discard restores). Reset Defaults zeroes it. - Rationale: CRT users lose UI to overscan; OPL solves this with a render inset and we adopt its exact math (
margin = W*permille/2000per edge,scale = 1 - permille/1000,OVX(x) = x*scale + margin, matching OPLrenderman.c rmSetOverscan). Making permille 0 the identity guarantees a zero-risk default โ nothing changes unless the user opts in. - Implications: New persisted settings key
OVERSCAN(throughEncodeSettings,CommitSettingsChangesnext_overscan/next_state.overscan, parse-back). NOT yet hardware/CRT-eyeballed โ keep as pending, not broken. See STATE.md > Settings and Hardware Status. - Evidence:
src/graphics.cppOVX/OVY+recompute_overscan/set_overscan/get_overscan(1128โ1166), wrapping thegsKit_prim_*draw sites.src/luaScreen.cpplua_set_overscan/lua_get_overscan(64โ74; registeredsetOverscan/getOverscan, 162โ163).bin/POPSLDR/ui.lua"Overscan (CRT inset)"AddCycleยฑ5 step (3631โ3643), live apply (3071โ3072), Reset Defaults zero (3356โ3358).bin/POPSLDR/system.luaOVERSCAN=(3099),next_overscan(3568โ3569)/next_state.overscan(3593), parse-back (3437). Commits1ac15ad,6f0d742,73f0933.
2026-06-21 โ Layered Cover-Art Placeholder Replaces the Single MISSING.png (56a5ad5)
- Decision: Replace the single combined no-cover placeholder image with two layered assets:
bin/POPSLDR/IMG/cover_default.png(base) +cover_missing.png(overlay). In the game-list preview box (bin/POPSLDR/ui.lua): a live cover draws unchanged in its ownCOVER_Winset; with no live cover,cover_default.pngis drawn in the frame's aspect-corrected, right-anchored rect, andcover_missing.pngis overlaid only when the preview is enabled (cover_enabled) and the game has no cover. The old "Cover disabled" text label is removed โ preview-off now shows the plain default cover. The default, the overlay, andframe.pngall share the same right-anchored rect so they register with the jewel-case window on both NTSC and PAL. - Rationale: A layered default+overlay reads better than one combined image and cleanly distinguishes "preview off" (plain default) from "preview on, no art" (default + missing mark), without a text label.
- Implications:
MISSING.pngis fully removed (-62KB ELF:bin/POPSLOADER.ELFis now 1739428 bytes, down from 1802052). Any doc that namesMISSING.pngas the cover placeholder, or the "Cover disabled" text label, is stale. This supersedes the 2026-05-19 "default cover is optional,MISSING.pngrequired" decision (see that entry's superseded-by note). The frame-registration alignment was pre-existing-since-#496 (BETA-12), not a new BETA-13 regression (fa2d899). Cover registration on NTSC+PAL is still pending a hardware eyeball. - Evidence: assets
bin/POPSLDR/IMG/cover_default.png,cover_missing.png(andframe.png).bin/POPSLDR/ui.luaplaceholder block (2577โ2592):IMG.cover_default,if cover_enabled and IMG.cover_missing,IMG.frame. Commit56a5ad5. See the 2026-06-21 "Embedded-Asset Add/Remove" entry below for the removal mechanics.
2026-06-21 โ Embedded-Asset Add/Remove Is Three Explicit Coordinated Places (not auto-glob)
- Decision: Document and follow the rule that embedding (or dropping) a runtime asset touches three explicit, hand-coordinated locations โ there is no glob: (a)
Makefileโ aBIN2Srule forasset_<name>_png.cplus the.olisted inEMBEDDED_RSC; (b)src/embed_assets.cppโ anexterndeclaration plus anASSET_ENTRYin both the bare-name table and thePOPSLDR/IMG/-prefixed table; (c)bin/POPSLDR/images.luaIMG_REGISTRATIONS, which looks up the bare filename. - Rationale: The
MISSING.pngremoval and thecover_default/cover_missingadditions both had to hit all three places in lockstep; missing any one leaves a dangling extern, an unembedded asset, or a nilIMG.x. Recording the contract keeps future asset edits from half-landing. - Implications: Reusable checklist for ARCHITECTURE / CONTRIBUTING / AGENTS.
MISSING.pngwas cleanly removed from all three (no leftover.o, extern,ASSET_ENTRY, orimages.luaregistration; the only remaining "MISSING" strings are comments referencingcover_missing.png). - Evidence:
MakefileBIN2Srules (179โ182) +EMBEDDED_RSC(96โ101,asset_cover_default_png.o asset_cover_missing_png.o).src/embed_assets.cppexterns (62โ63) +ASSET_ENTRYin bare-name (120) andPOPSLDR/IMG/(167) tables.bin/POPSLDR/images.lua{"cover_missing", "cover_missing.png"}(36).
2026-06-21 โ Steer the HDD Game Scan Off the Boot PFS Slot (Proposal A, b159a43; save-after-scan liveness 8d1e67a)
- Decision: When POPSLoader is HDD-resident, the game-partition scan must never mount onto the live boot PFS slot.
GetHddGameSlotCandidates(bin/POPSLDR/system.lua) starts from the historical order{HDD_SLOT_GAME=1, HDD_SLOT_BOOT=0}, drops whatever slotGetBootHddMountSlot()reports as the live boot slot, and appendsHDD_SLOT_COMMON=2as the off-boot fallback (so for a slot-1 boot the scan uses slots{0, 2}). On a non-HDD bootGetBootHddMountSlot()is nil and the full historical list is returned unchanged so the scan still fires. Separately (8d1e67a), the settings save path liveness-validates the boot RW mount viadoesFolderExistbefore trusting theBOOT_PARTITION_RWflag. - Rationale: The HDD boot/settings partition stays mounted on its boot slot (pfs1: per
boot.lua:48"NEVER USE IT FOR ANYTHING ELSE"). Mounting a game partition onto that occupied slot fails at the C layer (luaHDD's warm single-attempt mount) and collaterally unmounts the boot partition, stranding the settings cwd โ so after a scan a settings save failed "...may be read-only" with a never-clearedBOOT_PARTITION_RWflag (Nuno 2026-06-20). ThedoesFolderExistliveness check fixes the immediate save failure; Proposal A removes the root cause by keeping the scan off the boot slot. Do not re-introduce the revertedbec5e90gate on whether the scan fires based on boot source. - Implications: Settings save survives an HDD game scan. Proposal A still wants a deliberate HDD hardware test (Nuno to verify game partitions still mount/list when forced off the boot slot) โ NOT yet hardware-tested. See STATE.md > Known Issues / Behavioral Invariants and the HDD pfs-slot-model memory note.
- Evidence:
bin/POPSLDR/system.luaslot constantsHDD_SLOT_BOOT=0/HDD_SLOT_GAME=1/HDD_SLOT_COMMON=2(261โ263),GetBootHddMountSlot(271โ277),GetHddGameSlotCandidatessteering (750โ787),EnsureBootPartitionWritableBOOT_PARTITION_RWliveness (2159โ2170),doesFolderExistsave-path guard (2001). Commitsb159a43,8d1e67a.
2026-06-21 โ Codex BETA-13 Audit: 6 Findings Verified Real and Fixed (ec81de3)
- Decision: Accept and fix all six Codex BETA-13 audit findings (each independently verified real):
PromoteTmpToDestrequires the backup to exist before truncating the destination; BMP pixel-size/stride validation; a PNG dimension cap; stalemc0:probe cleanup; twoSystem.writeFilefull-byte-count checks; and no R3 success-toast on a failed save. - Rationale: All six were confirmed against source rather than taken on the report's word (consistent with the project's "Codex reports are reference, not fact" stance); none changed intended behavior, they hardened crash/leak/corruption edges.
- Implications: Report lives at
docs/AUDIT_CODEX_2026-06-20.md. Earlier graphics-decoder hardening (loadpng/loadjpeg/loadbmp NULL-checks + leak/overflow fixes) is BETA-12-era and already documented; do not re-attribute it to this audit. - Evidence:
docs/AUDIT_CODEX_2026-06-20.md; commitec81de3(and report commitafe4227).
2026-06-21 โ POPSTARTER.ELF Ships in Both Release Zips
- Decision: Ship the redistributable
POPSTARTER.ELFhomebrew launcher in both release packages.rolling-release.ymlplaces it at the zip root next toPOPSLOADER.ELFand underPOPS/. The formal install zip (compilation.yml) ships it underPS1_POPSLOADER/(next toPOPSLOADER.ELF) andPOPS/. ThePOPSTARTER/SMB/BDMA pack folder andPOPS/PATCH_5.BINalso ship at the rolling root. - Rationale:
POPSTARTER.ELFis redistributable (the POPS engine binaries are not), so bundling it makes the rolling/install zips self-sufficient for the launcher side. - Implications: Testers and installers get
POPSTARTER.ELFwithout sourcing it separately; the POPS engine binaries still must be supplied by the user. See STATE.md > CI / release. - Evidence:
.github/workflows/rolling-release.yml(POPSTARTER pack at zip root ~168โ174;bin/POPSLDR/POPSTARTER.ELFcopied to zip root ~177โ182)..github/workflows/compilation.yml(PS1_POPSLOADER/+POPS/). Commits6631a57,44ea070,91315a0,61fb2a2/915c4ce/aeb2469.
2026-06-17 โ PLDR.HDD Attach-Before-Init Load-Order Boot Fix (d4b04be)
- Decision: Move the
PLDR.HDD.*method definitions so they run afterPLDR.HDDexists (after the table merge/init), unbricking cold boot. - Rationale: The recent HDD-feature rolling builds defined
PLDR.HDDmethods beforePLDR.HDDitself existed, so every cold boot hit a runtime error (system.lua:953, reproduced under PCSX2) โ the.hide/ HDD-cwd settings rolling builds were never actually cold-bootable. This is a load-order error:luac -pand the new CI syntax gate parse it as valid Lua, and markers can't see it; it is only fatal at runtime. CI / luac structurally cannot catch load-order (attach-before-init) bugs. - Implications: HDD-feature builds boot again. The HDD features need a full hardware retest once boot is confirmed, because they were never reachable on the bricked builds. See STATE.md > Known Issues ("Load-order boot brick", Recently resolved) and the embedded-Lua load-order trap note in MEMORY.
- Evidence: commit
d4b04be(2026-06-17),bin/POPSLDR/system.lua.
2026-06-17 โ BDMA Marker File Renamed .pldr_bdma_mode โ bdma_mode.txt (Legacy Names Still Read)
- Decision: Write the installed BDMA mode key to a plain-text marker named
bdma_mode.txtin the POPSTARTER pack folder. The legacy.pldr_bdma_mode/.pldr_bdmanames remain read for back-compat; the current name is always written. - Rationale: A clearer, shared, non-dotfile name (mSAS reads the same file). BDMA mode keys are
FAT32/USBEXFAT/MX4SIO/MMCE. - Implications: Existing installs keep working (legacy read path); new writes go to
bdma_mode.txt. See STATE.md > "BDMA mode / POPSTARTER memory-card folder". - Evidence: commit
7afdac3(2026-06-17),bin/POPSLDR/system.lua.
2026-06 โ POPSTARTER Memory Card Folder Toggle + BDMA Interlock
- Decision: Add a POPSTARTER Memory Card Folder toggle in Settings (below Discard). Disabling it deletes
mc:/POPSTARTERbehind a destructive-action confirm. Add a BDMA โบ folder interlock: the folder can't be disabled while BDMA mode is on, and BDMA can't be enabled while the folder is off. Persisted via the newPOPSTARTER_MC_FOLDERsettings key. - Rationale: BDMA (mass-storage backend) depends on the POPSTARTER MC folder being present; letting a user delete the folder while BDMA is on, or enable BDMA after deleting it, would leave an inconsistent install. The interlock makes the dependency unbreakable from the UI.
- Implications:
EncodeSettingsnow carriesPOPSTARTER_MC_FOLDER(one of the persisted settings keys; the schema has since grown further โBOOT_SOUNDandOVERSCANwere added in the BETA-13 session โ see STATE.md > Settings for the canonical key list and count). New behavioral invariant โ see STATE.md > Behavioral Invariants (#9, BDMA โบ folder interlock). STATUS: implemented / boots on PCSX2 / validating on hardware. - Evidence:
bin/POPSLDR/system.lua,bin/POPSLDR/ui.lua. See STATE.md > "BDMA mode / POPSTARTER memory-card folder".
2026-06 โ PAL Renders Natively at 640ร512 (Supersedes the 640ร448 PAL Layout)
- Decision: On PAL the UI now renders natively at 640ร512 so it fills the screen with no letterbox (NTSC stays 640ร448). The display-change confirm prompt auto-reverts if not confirmed (the OPL pattern); hold START during boot to skip past a bad video mode; the boot screen is centered. Video Standard gains an Auto default (matches the console region) alongside NTSC / PAL.
- Rationale: The old 640ร448-on-PAL layout (2026-03-26 decision) deliberately kept the NTSC raster to avoid vertical squish, but left PAL letterboxed rather than filling the screen. Rendering natively at 640ร512 fills the PAL frame; the auto-revert confirm protects against a bad mode locking the user out.
- Implications: Supersedes the 2026-03-26 "PAL UI uses 640ร448" decision (see that entry's superseded-by note). STATUS: implemented / boots on PCSX2 / PAL hardware validation pending. See STATE.md > "Video standard".
- Evidence:
bin/POPSLDR/system.lua,bin/POPSLDR/ui.lua.
2026-06 โ HDD Boot-Partition RW Take-Over for Settings + In-App .hide (EnsureBootPartitionWritable)
- Decision: HDD-installed POPSLoader now persists settings on the HDD boot partition itself, and writes per-game
.hidemarkers in-app on HDD too.PLDR.HDD.EnsureBootPartitionWritabletakes over the launcher's boot pfs mount โ it explicitly unmounts the boot pfs slot and remounts the same partition read-write at the same slot ("own your mount", the OPL pattern) โ so the.pldrssidecar and.hidefiles are written on-HDD. There is nomc0:fallback for an HDD-cwd install. Single-device parity with USB / MX4SIO / MMCE. In-app hide is now L3 (toggle hide/show) on every device page including HDD, with unhide via Settings โ Game List โ Hidden games โ Visible (manage); the "add the.hidefrom a PC" message is now only a write-failure fallback. - Rationale: This directly supersedes the 2026-05-27 PR #466 premise ("HDD saves to
mc0:because the bundledps2hdd-osd.irxcan't reliably write PFS, and an IRX swap would risk D-10"). The boot-partition remount take-over achieves HDD read-write without an IRX swap, so it doesn't touch the D-10 launch path. provato confirmed the HDD is RW-writable on real hardware, which validated the approach. - Implications: New load-bearing preservation contract โ
EnsureBootPartitionWritable(the boot pfs-slot unmountโremount-RW take-over) must not be broken by any future launch-path / mount change; see STATE.md > Preservation Contracts. New invariants: settings persistence is per-device including HDD (nomc0:HDD exception), and.hideis in-app on every device (STATE.md > Behavioral Invariants #2, #10). The old "ps2hdd-osd.irxโps2hdd.irxdriver-swap probe" investigation item is retired. STATUS: implemented / boots on PCSX2 / HDD RW confirmed on hardware (provato) / full settings +.hideflow validating on hardware (not yet broadly hardware-confirmed). NOTE: the recent HDD-feature rolling builds that carried this work were bricked by an unrelated load-order bug untild4b04be(see the 2026-06-17 entry above), so the on-hardware full-flow validation post-dates that fix. - Evidence:
bin/POPSLDR/system.lua(PLDR.HDD.EnsureBootPartitionWritable,ComputeSettingsSidecarPath, HDD.hidewrite path),bin/POPSLDR/ui.lua(L3 hide-toggle handler; R3 reveal/hide-toggle handler at ui.lua:2816, GLOBAL_HIDE toggle ui.lua:2820). provato hardware confirmation 2026-06 (HDD RW-writable). See STATE.md > "Settings (single-device parity)" and "Per-game hide layer".
2026-06 โ Embedded-Lua Syntax Gate (luac5.4 -p) Is Now Live in CI
- Decision: The CI workflows now
apk add lua5.4and runluac5.4 -poverbin/POPSLDR/*.lua+etc/boot.lua, hard-failing on a syntax error. - Rationale: The gate had been silently skipping because the pinned
ps2devimage ships noluacโ every "syntax check" was a no-op. Installing lua5.4 in the workflow makes it actually run. - Implications: SYNTAX errors are now caught pre-merge. It catches syntax only โ runtime nil-global / type / load-order errors stay invisible to CI (the
d4b04beattach-before-init boot brick was exactly such a case and passed the gate). See STATE.md > "CI / release". - Evidence:
.github/workflows/compilation.yml/rolling-release.yml(lua5.4 install +luac5.4 -pstep). Run #492 green.
2026-05-31 โ U-10 (BOOT.ELF from HDD-Booted POPSLoader) Resolved by PR #479 (reboot_iop=0)
- Decision: Mark U-10 as PASS / resolved. Launch BOOT.ELF from an HDD-booted POPSLoader with
reboot_iop=0(PR #479). - Rationale: This supersedes the 2026-05-28 PM late walk-back below, which (correctly for that build) returned U-10 to known-broken after the partial-sweep PASS was reversed. PR #479 then actually fixed the path: launching BOOT.ELF without rebooting the IOP from the HDD-booted context avoids the post-reset SIF-handshake hang that black-screened the HDD-launch case. Nuno confirmed PASS on hardware 2026-05-31. (Recorded here as the latest U-10 decision so this log's most-recent U-10 state is the resolution, not the walk-back-to-broken entry.)
- Implications: U-10 leaves the known-broken list and becomes a preservation contract; the related Class-A HOSDmenu / some-wLE launch failures were separately maintainer-confirmed resolved 2026-06-15. See STATE.md > Preservation Contracts and the Reported Hardware Status table (U-10 PASS, 2026-05-31, PR #479).
- Evidence: PR #479 (
reboot_iop=0for HDD-booted BOOT.ELF);QA_REGRESSION_MATRIX.mdU-10 row; Nuno 2026-05-31. Investigation chain preserved indocs/archive/U10_INVESTIGATION.md.
2026-05-29 โ MX4SIO Settings Save Fixed by PR #476 + PR #477 (Hardware-Confirmed)
- Decision: Merge PR #477 to lock in the MX4SIO settings save fix. PR #476 (already merged) added the boot.lua mass-slot scan via dynamic ioctl driver lookup (sdc/mx4); PR #477 wrapped the scan in a 3-attempt retry pattern with sleep(1) between failures, plus a
BOOT_MX4SIO_PROBE_RESULTdiagnostic trace appended to the settings save error toast for future debugging. - Rationale: PR #476's single-shot scan didn't catch the MX4SIO SD card on real hardware (Nuno 2026-05-28 PM retest).
PLDR.InitMX4SIOPopsRootwas already using a 3-attempt retry pattern for the same MX4SIO double-ping reason; the boot.lua scan needed to mirror that. Nuno confirmed PASS on 2026-05-29 02:58Z after PR #477 was merged. - Implications: MX4SIO-rooted POPSLoader now saves settings to the per-device sidecar at
<mass*>:/<install path>/.pldrslike USB / MC / MMCE installs do. If the retry ever misses on future hardware, themx4sio probe: a1:...;a2:...;a3:...diagnostic appears in the error toast and tells us what attempts hit what slot/driver. - Evidence:
etc/boot.luaMX4SIO branch + retry loop;bin/POPSLDR/system.luaResolveAppDirLocalmx4sioโmass override;bin/POPSLDR/ui.luaerror toast diagnostic append. PR #476 merged 2026-05-28; PR #477 merged 2026-05-29 02:58Z.
2026-05-28 PM late (correction 2) โ Class A vs Class B: HOSDmenu/wLE Fail to Launch POPSLoader is Distinct from U-10
- Decision: Document the broken launcher scenarios as TWO distinct failure classes, not one. Update STATE.md / README.md / ROADMAP.md / TRUTHSHEET.md / QA matrix / U10_INVESTIGATION.md accordingly.
- Rationale: Maintainer's correction after closer reading of Nuno's Discord report. "HOSDmenu โ POPSLoader = black screen, we don't even get to the splash" is fundamentally different from U-10. U-10 is about exit-to-BOOT.ELF from a successfully-running HDD-launched POPSLoader; the HOSDmenu / some-wLE failures are about POPSLoader never starting in the first place.
- Implications: Class A (POPSLoader fails to launch under HOSDmenu / some wLE builds) is the same class as the CosmicScale 2026-05-25 wLE issue that PR #458 Layer A targeted. Layer A's
_ps2sdk_memory_initfileXioteardown resolved Layer A for the common case but didn't fully resolve it for HOSDmenu or specific wLE builds. Class B (U-10) is unchanged from prior status. Documenting them separately gives future investigations a cleaner target (Class A = parent-launcher IOP-state hygiene before POPSLoader main runs; Class B = POPSLoader's own IOP-state hygiene before BOOT.ELF ExecPS2). Workarounds: Class A โ use a different launcher; Class B โ Exit โ OSDSYS or reboot. - Evidence: Discord screenshot showing Nuno's tests: "Mmm... loaded from hosdmenu popsloader in hdd and exit to boot.elf give me black screen" (POPSLoader never reached splash); "Loading popsloader (hdd) from wle also results in black screen" (same); "Other devices memory card, usb and mx4sio exit boot.elf OK" (Class B test conditions met, BOOT.ELF works from those); maintainer correction "HOSDmenu black screens when launching POPSLoader, we don't even get to the splash" and "Some builds of wLE blackscreen as well when booting POPSLoader."
2026-05-28 PM late โ U-10 PASS Claim Walked Back; Remains Known-Broken Accepted
- Decision: Reverse the earlier 2026-05-28 PM doc walk-down that marked U-10 (BOOT.ELF from HDD-booted POPSLoader) as PASS. U-10 remains known-broken-accepted in BETA-10-5, same as before, with the workaround Exit โ OSDSYS or reboot.
- Rationale: Nuno's earlier 2026-05-28 PM "BOOT.ELF working across the board" report turned out to be a partial hardware sweep that didn't explicitly cover the HDD-booted launch context. A fuller sweep later that day (with the cat keeping him company while his wife was on night shift) explicitly retested HOSDmenu โ HDD-POPSloader โ BOOT.ELF and wLE โ HDD-POPSloader โ BOOT.ELF โ both black-screen. MC / USB / MX4SIO-rooted BOOT.ELF still work. The maintainer's "U-10 unexpectedly PASS" doc commit (in PR #475) was based on the partial sweep and is hereby reversed.
- Implications: U-10 stays in
STATE.md/README.md/ROADMAP.md/TRUTHSHEET.mdknown-broken sections.docs/U10_INVESTIGATION.mdstatus reverted from "PASS unexpectedly resolved" to "known broken โ accepted". The hypothesis catalog in that doc is preserved. Maintainer hypothesis to investigate next (post-walkback): "maybe I need to reset IOP on boot or something before anything else" โ aligns with H1 (DEV9 state survivesSifIopReset) and H5 (stalepfs1:mount blocks the reset).claude/diag-u10branch is preserved if a future agent wants to ship a diagnostic build. - Evidence:
QA_REGRESSION_MATRIX.mdrow "2026-05-28 PM late | Nuno (full hardware sweep)". Maintainer Discord/chat exchange 2026-05-28 PM: Nuno reported HDD-booted BOOT.ELF failures while testing exit conditions across devices ("Other devices memory card, usb and mx4sio exit boot.elf OK").
2026-05-28 PM โ U-10 Unexpectedly Resolved; Known-Broken List Reduced to DKWDRV-HDD-Custom (WALKED BACK 2026-05-28 PM late โ see entry above)
- Decision: Mark U-10 (BOOT.ELF from HDD-booted POPSLoader) as PASS in STATE.md, README.md, ROADMAP.md, TRUTHSHEET.md, and
QA_REGRESSION_MATRIX.md. Remove the known-broken-accepted entry for U-10. Preservedocs/U10_INVESTIGATION.mdandclaude/diag-u10branch in case of regression. - Rationale: Nuno's 2026-05-28 PM hardware test on the rolling-release artifact built post-PR-#470/#472/#473 reports BOOT.ELF exit working "across the board" including HDD-booted POPSLoader. None of those PRs architecturally touch the U-10 path; the cause is not obvious. Candidate explanations: (a) C-layer
EnsureUsbMass-first ordering in PR #472 incidentally cleans IOP state; (b) earlier "U-10 fails" reports were partially obscured by the now-fixed PR #473 boot crash; (c) test environment shifted. None of these is conclusive without further investigation. - Implications: The only confirmed-broken edge case in BETA-10-5 + post-release work is DKWDRV from custom HDD path (re-confirmed broken by Nuno same session). HDD-install settings save to MC fallback is by design (PR #466), not a bug. The previously-noted wLE โ USB POPSLoader โ BOOT.ELF latent failure is covered by the "across the board" PASS.
- Implications (continued): Since the root cause of the resolution is not understood, treat U-10 as conditionally PASS. Any future change touching IOP teardown, mass-backend ordering, or BOOT.ELF launch routes should retest U-10 explicitly.
- Evidence:
QA_REGRESSION_MATRIX.mdrow "2026-05-28 PM | Nuno on rolling-release". Maintainer report 2026-05-28 PM: "Everything is working except saving settings, and hdd custom path dkwdrv."
2026-05-28 PM โ PR #470, #472, #473 Hardware-Verified
- Decision: Promote PR #470 (LAUNCH_ARGS consumers), PR #472 (MX4SIO evidence-based classification), and PR #473 (forward-reference hotfix) from
Unknown (verify on hardware)to PASS in STATE.md and TRUTHSHEET.md. - Rationale: Nuno's 2026-05-28 PM hardware test on the rolling-release artifact (commit
860ae26from PR #471 branch, includes all merged BETA-12-PLAY changes plus Layer C mmceman defer) confirms: MX4SIO and USB working as intended, BOOT.ELF exit working across the board, DKWDRV from MC working regardless of POPSLoader boot source, USB sidecar settings save working. No boot crash. - Implications: The post-release backbone is now hardware-validated. PR #471 (Layer C mmceman defer, currently DRAFT) is indirectly hardware-PASS for general boot + pad input; MMCE-specific device access from deferred-load state is the one untested case. Recommend an explicit MMCE test before promoting PR #471 from DRAFT.
- Evidence:
QA_REGRESSION_MATRIX.mdrow "2026-05-28 PM | Nuno on rolling-release".
2026-05-28 โ Layer C mmceman Lazy Load (PR #471, DRAFT)
- Decision:
mmceman.irxis eagerly loaded at boot only whenboot_device_hint == "MMCE"; for USB / MC / MX4SIO / HDD boots (all HDD root variants โhdd*,pfs*,ata*,apa*) the IRX is deferred and loaded on demand viaSystem.ensureMmcemanfromPLDR.EnsureMmceReadyOnce. - Rationale: MMCE third-party adapters (
mmce0:/,mmce1:/) are a small subset of installs; loading the IRX eagerly for all boot types wastes ~50-100ms. MC (mc0:/,mc1:/, standard PS2 memory cards) is a distinct device handled bymcman/mcservwhich are always loaded โ MMCE and MC are not the same device. - Implications: PR #471 is DRAFT. Hardware test must verify pad input survives on USB / HDD / MC boots (no IRX load order regression) and that MMCE-page entry from a deferred state correctly lazy-loads.
- Evidence:
src/main.cppline 446+,src/luasystem.cppEnsureMmceman/MarkMmcemanLoaded,bin/POPSLDR/system.luaPLDR.EnsureMmceReadyOnce. PR #471, branchclaude/curious-noether-3f2a8.
2026-05-28 โ Lua Forward-Reference Hotfix (PR #473)
- Decision: Move
local function ClassifyMassRootDriverdeclaration aboveClassifyStartupMassTargets. Remove the duplicate later declaration. - Rationale: Lua's
local function f()is sugar forlocal f; f = function()...end; the local doesn't exist in scope until its declaration line. A caller declared above can't capture it as an upvalue and falls through to a global lookup (nil) at call time. Hardware regression 2026-05-28: rolling-release crashed on Enceladus boot withattempt to call a nil value (global 'ClassifyMassRootDriver'). Source-valid; runtime-broken; Lua syntax check did not catch it. - Implications: Body unchanged. No behavior change other than the crash going away.
- Evidence:
bin/POPSLDR/system.luapost-PR #473 hasClassifyMassRootDriverat line 3135,ClassifyStartupMassTargetsat line 3146. PR #473 (claude/hotfix-classify-mass-root-driver), merged 2026-05-28T17:20Z.
2026-05-28 โ MX4SIO Evidence-Based Mass: Classification (PR #472 + refinement 7b587fe)
- Decision: At boot,
mass:/-prefixed devices are classified by the ioctl driver name returned bySystem.getMassMountDriver.sdc/mx4โ MX4SIO; any other non-empty driver โ USB; empty ioctl โ fall through to legacy markers, then USB default.mx4sio_bd.irxis only loaded on explicit MX4SIO evidence:mx4sio:/prefix,sdc/mx4ioctl driver, or.boot_mx4siomarker.AutoInitStartupBackendsloads onlyusbmass_bdbefore the first classification pass;mx4sio_bdloads conditionally on the second pass when an ambiguous mass slot remains (mass_probe_needed). - Rationale: Per maintainer 2026-05-28: "If ioctl/devctl is ANYTHING OTHER THAN
sdcormx4, and it's a mass device, then it is USB; if a mass device issdc/mx4on ioctl/devctl, then it must be MX4SIO." "MX4SIO should only init on startup if it came frommx4sio:/ormasswithsdcdevctl." Mass mounting is volatile โ the same path (mass:/,massN:/,mx4sio:/,usb:/) can be either backend depending on hotplug + IRX load order, and the ioctl driver name is the only authoritative classifier. - Implications: USB boots never load
mx4sio_bd. MX4SIO boots loadmx4sio_bdconditionally after the ambiguity is detected. The.boot_mx4sio/.boot_usbmarkers remain as legacy fallback for extreme hardware quirks where ioctl never returns a driver. - Evidence:
bin/POPSLDR/system.luaclassify_mass_boot,AutoInitStartupBackends,ClassifyStartupMassTargets. Maintainer refinement commit7b587fe. PR #472 merged 2026-05-28T11:22Z.
2026-05-28 โ mx4sio_bd Depends on usbmass_bd (Enforced at C Layer)
- Decision:
lua_mx4sio_initinsrc/luasystem.cppcallsEnsureUsbMass()before loadingmx4sio_bd.irx. The order is unviolatable from Lua. - Rationale: Per maintainer 2026-05-28: "mx4sio will need the usb drivers to activate before it with it. USB will never need MX4SIO drivers." Multiple Lua call sites previously could load
mx4sio_bdwithout first ensuringusbmass_bdwas up; enforcing the order at the lowest level removes a class of bugs. - Implications: Pure USB boots still never load
mx4sio_bd(per the evidence-based classification rule above). Whenmx4sio_bddoes load,EnsureUsbMass()is a cheap idempotent call (gated byusbmass_irx_loaded). - Evidence:
src/luasystem.cpplua_mx4sio_init. PR #472 commit357e3b8.
2026-05-28 โ PLDR.LAUNCH_ARGS.game and -debug Consumers Wired (PR #470)
- Decision: Add
PLDR.AutoLaunchFromLaunchArgs()โ when both-page=<kind>and-game=<selector>are set, auto-launch viaRunPOPStarterGameafterAutoInitStartupBackends. Supports HDD (PARTITION|relpath), USB (FILE.VCDrelative tomass:/POPS), MX4SIO, MMCE. AddPLDR.SurfaceLaunchArgsDebug()โ when-debugis set, queue a boot-context toast showing kind, boot_path, sidecar_path, settings, and parsed launch args. - Rationale: PR #458 / PR #462 shipped the LAUNCH_ARGS infrastructure (parser + carousel auto-nav) but the
gameanddebugvalues were parsed without consumers. This PR completes the loop so NHDDL-style shortcuts (e.g. wLaunchELF entries launching POPSLoader with a specific HDD game) work end-to-end. - Implications: On launch failure,
AutoLaunchFromLaunchArgsfalls through to the main menu with an error toast โ default behavior unchanged when no-game=is passed.-debugis a low-cost runtime diagnostic that doesn't require rebuilding withDPRINTFenabled. - Evidence:
bin/POPSLDR/system.luanear line 5087+. PR #470 merged 2026-05-28T10:12Z.
2026-05-28 โ Rolling Release Workflow Operational
- Decision:
.github/workflows/rolling-release.ymlpublishes aPOPSLOADER-rolling-release.zipasset to the canonicalrolling-releaseGitHub Release on every push toBETA-12-PLAYand on every pull-request event (opened,synchronize,reopened,ready_for_review). The tag floats; testers grab the latest asset URL. - Rationale: Testers (Nuno, CosmicScale) consume builds from a single stable URL. Manual artifact hosting is fragile. Last-write-wins semantics are acceptable because tester traffic is coordinated.
- Implications: PR builds AND the tracked-branch push builds both overwrite the same asset. Multiple in-flight PRs can churn the asset; the maintainer must coordinate which PR's build is current when sending a tester to the URL. Per-PR per-tag releases were considered and rejected in favor of the single-URL workflow.
- Evidence:
.github/workflows/rolling-release.yml(added commit761129a). Rolling Release URL: https://github.com/NathanNeurotic/POPSLoader/releases/download/rolling-release/POPSLOADER-rolling-release.zip. - UPDATED (2026-06-21): the push trigger was repiped from
BETA-12-PLAYtoBETA-13-PLAY(.github/workflows/rolling-release.ymlon.push.branches), sinceBETA-12-PLAYis now archival/frozen. The PR-event triggers (opened/synchronize/reopened/ready_for_review) and the single-asset/floating-tag mechanics are unchanged.
2026-05-28 โ BETA-10-5 Hardware Confirmation (Nuno)
- Decision: BETA-10-5 release artifact at commit
9a0ebe2is hardware-confirmed clean. - Rationale: 2026-05-28 09:04 AM hardware test by Nuno verified: (a) BOOT.ELF exit OK, (b) HDD games load OK (D-10), (c) settings save from HDD-installed POPSLoader to MC works (sidecar fallback by design), (d) DKWDRV launch from default MC path OK. Known-broken items match documented expectations (DKWDRV-from-HDD-custom-path, U-10 BOOT.ELF-from-HDD-boot).
- Implications: D-10, D-14, D-15, DKWDRV-MC, BOOT.ELF (USB-booted), and HDD-install-settings-to-MC are now preservation contracts. Any future work must not regress these.
- Evidence:
QA_REGRESSION_MATRIX.mdrow dated 2026-05-28.
2026-05-27 โ BETA-10-5 Release Cut (v1.0.0-rev5, commit 9a0ebe2)
- Decision: Tag the stable backbone of post-March work as
BETA-10-5(v1.0.0-rev5). Publish GitHub release at https://github.com/NathanNeurotic/POPSLoader/releases/tag/BETA-10-5. - Rationale: D-10/D-14/D-15 hardware-passing (B2 fix), DKWDRV-MC hardware-passing, BOOT.ELF (USB-booted) working via V2 route, per-device settings sidecar working for USB/MC/MMCE. The remaining items (DKWDRV-HDD-custom, U-10 BOOT.ELF-from-HDD-boot) had been investigated repeatedly without a stable fix; pragmatically accept them with workarounds rather than block the release.
- Implications:
bin/changelogv1.0.0-rev5 entry;etc/boot.luaPOPSLDR_VER bumped; STATE.md known-broken list refined. - Evidence: PR #468 merged 2026-05-27. Tag
BETA-10-5at9a0ebe2.
2026-05-27 โ HDD Sidecar Disabled; Known-Broken Edge Cases Accepted (PR #466)
- Decision: HDD-installed POPSLoader saves settings to
mc0:/POPSTARTER/.pldrsrather thanpfs1:/<install dir>/.pldrs.ResolveBootContextreturnssidecar_path = nilfor any HDD-rooted APP_DIR (pfs%d*,hdd%d*,ata%d*,apa%d*). Document DKWDRV-from-HDD-custom-path and U-10 BOOT.ELF-from-HDD-boot as known-broken accepted for the release. - Rationale: PR #459 had added
pfs1:write support assumingetc/boot.lua's RW mount worked; Nuno's 2026-05-27 hardware test on PR #464 confirmed thatpfs1:/.../.pldrswrites still fail with "may be read-only" despite the boot.lua path normalization. The bundledps2hdd-osd.irxdriver has read-write limitations we can't reliably work around without an IRX swap that risks regressing D-10. Per Nuno + maintainer 2026-05-27: "rollout dkwdrv; vast majority of users will have other devices for DKWDRV, small percentage have HDD installs". Shift from chasing edge-case bugs to shipping the stable backbone. - Implications: HDD-installed POPSLoader is back to pre-PR-#459 settings-save behavior (no regression vs. legacy). Non-HDD installs (USB / MX4SIO / MMCE / MC) keep per-device sidecar. In-flight diag PR #463 and HDD r/w probe PR #465 closed as superseded; branches preserved.
- Evidence:
bin/POPSLDR/system.luaResolveBootContext(lines 1764+), PR #466 merged 2026-05-27. - SUPERSEDED-BY (2026-06): the "HDD saves to
mc0:" premise no longer holds. HDD installs now persist on the HDD boot partition via theEnsureBootPartitionWritableRW take-over (nomc0:fallback); provato confirmed HDD RW on hardware. See the 2026-06 "HDD Boot-Partition RW Take-Over" entry above and STATE.md > "Settings (single-device parity)".
2026-05-25 โ NHDDL-Style Launch Argument Parsing (PR #458)
- Decision:
main.cpp parseLaunchArgs()recognizes-page=<kind>,-mode=<kind>(NHDDL alias),-game=<selector>,-debug.System.getLaunchArgs()exposes parsed values to Lua.PLDR.LAUNCH_ARGS = {page, page_raw, game, debug}is normalized at module load. - Rationale: CosmicScale requested
-mode=atafor NHDDL parity. Generalizing to-page=/-game=lets users wire wLaunchELF entries directly to a specific device page or game. Page-only consumer (carousel auto-nav) landed in PR #462; game and debug consumers landed in PR #470. - Implications: Default behavior unchanged when no flags are passed.
- Evidence:
src/main.cppparseLaunchArgs,src/luasystem.cpplua_getLaunchArgs,bin/POPSLDR/system.luaLAUNCH_ARGS parser. PR #458, #462, #470.
2026-05-25 โ Unified ResolveBootContext Resolver (PR #458)
- Decision: A single canonical Lua resolver
ResolveBootContext()combines the C-side argv[0] classification hint (detectBootDeviceHintFromArgv0/System.getBootDeviceHint), Lua-side prefix matching (mass/mmce/mx4sio/pfs/hdd/smb/host/usb/ata/apa), and the mx4siomass:/driver-identity refinement.DetectBootDevice,PLDR.GetBootContext,PLDR.GetBootKind, and the settings sidecar path all consume this one resolver. - Rationale: Previously three near-duplicate detection paths drifted out of sync. Unification eliminates a class of "boot detection says X but settings think Y" bugs.
- Implications: Future device-detection changes happen in one place. New ps2sdk device-kind prefixes (
usb,ata,apa) added without breaking the legacymass/mmce/mx4sio/pfs/hdd/smb/hostdetection. - Evidence:
bin/POPSLDR/system.lualines 1694+,src/main.cppdetectBootDeviceHintFromArgv0,src/luasystem.cpplua_getBootDeviceHint. PR #458.
2026-05-25 โ Per-Device Settings Sidecar with First-Run Migration (PR #459, PR #462)
- Decision: Settings persist at
PLDR.SETTINGS_PATH, resolved at load time byLoadSettingsNonFatal. Sidecar atAPP_DIR_LOCAL/.pldrsis preferred; legacymc0:/POPSTARTER/.pldrsis fallback. First-run migration: when settings load from MC but a sidecar path is computable, pinPLDR.SETTINGS_PATHto the sidecar so the next save migrates. HDD-rooted APP_DIRs are excluded per the PR #466 decision above. - Rationale: Old design hardcoded
mc0:/POPSTARTER/.pldrsregardless of where POPSLoader was installed. Users with USB / MX4SIO / MMCE installs got their settings written back to the boot card unconditionally. - Implications: USB / MX4SIO / MMCE installs keep their own settings without ever touching
mc0:/POPSTARTER. HDD installs fall back to MC by design. - Evidence:
bin/POPSLDR/system.luaLoadSettingsNonFatal(migration viamigrate_to_sidecar),SaveSettingsAtomic,ResolveBootContext(sidecar path computation). PR #459, #462.
2026-05-22 โ Minimal B2 Dynamic PFS Unmount Production Fix
- Decision: Apply the B2 dynamic PFS unmount fix to the child loader pre-ExecPS2 sequence for HDD-backed POPSTARTER paths, bypassing the child loader's post-load IOP reset and re-initialization.
- Rationale: Target-side diagnostic sentinel testing proved that resetting the IOP post-load in the HDD-backed loader environment causes subsequent RPC/SIF handshake initialization (SifInitRpc) to hang due to the target partition remaining mounted. Dynamically unmounting only the exact target PFS mount prefix (e.g.,
pfs0:) resolved the hang and restored the handshake without needing a loader-side reset. - Implications: HDD-backed POPSTARTER launches proceed with the standard SifLoadElf path but with PFS slot cleanup before ExecPS2. Non-HDD paths remain completely unaffected.
- Evidence:
src/elf_loader/src/loader/src/loader.c, B2 diagnostic variant (artifacts/local-d10/20260522-122000-popsloader-hdd-preexec-unmount-target-pfs/POPSLOADER.ELF).
2026-03-06 โ Lua runtime is embedded-only at boot
- Decision: boot and required runtime Lua modules are loaded from embedded blobs, not loose filesystem Lua files.
- Rationale: deterministic startup and fewer layout-dependent failures.
- Implications: editing runtime Lua requires rebuilding the ELF.
- Evidence:
src/luaplayer.cpp,etc/boot.lua,Makefile.
2026-03-06 โ Settings persist as a transaction on Settings/Profile exit
- Decision: settings edits are staged in UI draft state and committed on confirm/leave.
- Rationale: avoid repeated writes while navigating and keep save/apply failure handling explicit.
- Implications: runtime/UI state sync must still happen if save/apply fails.
- Evidence:
bin/POPSLDR/ui.lua,bin/POPSLDR/system.lua.
2026-03-26 โ USB vs MX4SIO identity is authoritative by mount driver
- Decision: mounted mass roots are classified by driver identity, not by path spelling.
- Rationale: real hardware can expose both USB and MX4SIO through
mass*:/. - Implications: startup auto-init, boot-device labeling, and page list building must continue to use mount-driver queries.
- Evidence:
bin/POPSLDR/system.lua,src/luasystem.cpp.
2026-03-26 โ Runtime device locks are no longer enforced
- Decision: the old per-session device lock system is no longer an active runtime constraint.
- Rationale: device access should not be blocked by stale lock state.
- Implications: docs must not claim that switching devices requires restart; future changes must not silently reintroduce that gate.
- Evidence:
bin/POPSLDR/ui.lua(canEnterDevice,setDeviceLock).
2026-03-26 โ Startup backend initialization is path-driven
- Decision: startup backend auto-init considers boot paths and configured executable/profile paths, not just the page the user opens first.
- Rationale: a configured POPSTARTER/DKWDRV/profile path can require backend drivers before any device page is visited.
- Implications: startup docs and validation must cover boot source plus configured paths, and HDD startup targets must run the full HDD module-load path rather than only low-level exec initialization.
- Evidence:
bin/POPSLDR/system.lua(CollectStartupBackendTargets,AutoInitStartupBackends).
2026-03-26 โ PAL UI uses the same 640x448 raster layout as NTSC-authored UI assets
- Decision: PAL mode keeps the UI raster at
640x448instead of stretching the authored layout vertically. - Rationale: reduce PAL squish on menus and authored UI assets.
- Implications: final on-TV proportions still require hardware confirmation.
- Evidence:
bin/POPSLDR/system.lua,bin/POPSLDR/ui.lua. - SUPERSEDED-BY (2026-06): PAL now renders natively at 640ร512 (fills the screen, no letterbox) with an auto-reverting display-change confirm and an Auto Video-Standard default. See the 2026-06 "PAL Renders Natively at 640ร512" entry above and STATE.md > "Video standard".
2026-03-26 โ Release ZIP contract is strict and PATCH_5-based
- Decision: CI package includes exact
PS1_POPSLOADER/*launcher files plusPOPS/PATCH_5.BIN, now includingPS1_POPSLOADER/BUILD_INFO.txt, and rejects legacyPOPS/*.tm2payloads. - Rationale: prevent release drift and ambiguous installation instructions.
- Implications: docs and workflow validation must stay synchronized, and GitHub-built hardware candidates now carry a visible build stamp while CI also checks for expected embedded runtime markers before packaging.
- Evidence:
.github/workflows/compilation.yml.
2026-03-29 โ QA_REGRESSION_MATRIX.md is the detailed run ledger
- Decision: keep per-artifact hardware/CI chronology in
QA_REGRESSION_MATRIX.md, whileREADME.md,STATE.md, andROADMAP.mdsummarize the stable current state and constraints. - Rationale: repeated experiment history had started drifting across the root docs and obscuring the actual current handoff state.
- Implications: root docs should point to the matrix for detailed chronology instead of carrying their own competing mini-ledgers.
- Evidence: repository documentation audit on 2026-03-29.
2026-05-13 โ Separate build/package CI from comment-triggered AI automation
- Decision: treat
.github/workflows/compilation.ymlas the build/package validation source of truth, and document.github/workflows/opencode.ymlseparately as comment-triggered repository automation. - Rationale: the opencode workflow can affect repository collaboration but does not build artifacts, enforce the release ZIP contract, or prove runtime/hardware behavior.
- Implications: source-truth audits should include both workflow files, while CI/package claims should continue to cite only the compilation workflow unless the automation contract changes.
- Evidence:
.github/workflows/compilation.yml,.github/workflows/opencode.yml.
2026-05-19 โ Default cover image is optional at compile time
- Decision:
bin/POPSLDR/IMG/default.pngis optional. CI/artifact builds without it omitasset_default_pngand fall back to the requiredMISSING.pngasset forIMG.default. - Rationale: default cover art should only be embedded when the PNG is present in the checkout used by GitHub Actions.
- Implications: at the time,
MISSING.pngremained required;default.pngcould be removed without breaking artifact builds. - SUPERSEDED (2026-06-21,
56a5ad5โ see the layered cover-art decision above):MISSING.pngwas removed entirely.IMG.defaultno longer falls back to it (IMG_FALLBACKSis now empty);default.pngstays an optional compile-time embed but with no fallback target, and the no-cover / preview-off states usecover_default.png+cover_missing.png. - Evidence:
Makefile,src/embed_assets.cpp,bin/POPSLDR/images.lua,bin/POPSLDR/ui.lua. - SUPERSEDED-BY (2026-06-21):
MISSING.pngis removed entirely (โ62KB). The no-cover placeholder is now the layeredcover_default.png(base) +cover_missing.png(overlay-when-preview-enabled); thedefault.png-optional /asset_default_pngmechanism is unchanged (default.pngis still optional, embedded only when present). See the 2026-06-21 "Layered Cover-Art Placeholder" entry above.
Archived Investigations
The investigations below were active during the MarchโMay 2026 development cycle and are now largely resolved by the BETA-10-5 release. They are preserved verbatim for historical context and to document the diagnostic chain that produced the B2 fix at commit
4ae6679(D-10) and the V2 BOOT.ELF route at commitd23520a(L-07). For the current state, seeSTATE.md(current state, hardware status, and known issues) andQA_REGRESSION_MATRIX.md.
- HDD startup auto-init on HDD boot/configured paths:
- a 2026-03-27 hardware report said booting from HDD did not auto-init the HDD driver stack.
- current code had detected HDD startup targets correctly but only called
EnsureHddRuntimeReadyForExec(), which stops at low-levelHDD.Initialize(). - current source now routes HDD startup targets through
PLDR.LoadHDDModules()so startup uses the same HDD status/partition/cache initialization path as the HDD page. - a later boot-time report showed that using the full HDD page path at startup also caused large HDD libraries to spend boot time scanning partitions and building the games list.
- current source therefore keeps startup HDD auto-init limited to runtime readiness only; partition scanning and game-list building remain page-entry work.
- optional HDD cache writing now reuses the page-built game list instead of rebuilding one during startup.
- later 2026-03-28 hardware reports on that narrowed boot-time split source still said HDD-backed startup/Profile POPSTARTER could disappear after entering the USB page before the HDD page.
- the raw boot
APP_DIRfallback alone did not restore that case. - current source therefore also pre-resolves any HDD-backed startup/configured exec paths immediately after
PLDR.LoadHDDModules()so HDD POPSTARTER/Profile paths are mounted and recorded without reintroducing HDD page work at boot. - current source also routes on-demand HDD path mounts through
PLDR.LoadHDDModules()instead of only the lower-levelEnsureHddRuntimeReadyForExec()gate, so later POPSTARTER/Profile resolution from USB or other pages uses the same runtime init path as HDD page entry. - repo inspection also showed the startup HDD warm-path list had been skipping Profile 1/default relative
POPSTARTER.ELFbecause it only recorded explicithdd:/pfs:paths, so current source now includes that default-relative case when boot source is HDD. - repo inspection of
etc/boot.luaalso showed HDD boot is established on a dedicatedpfs1:mount beforesystem.luaruns, so current source now carries that exact boot partition/slot metadata intosystem.lua, seeds the HDD mount tracker from it, and rebuilds HDD sidecar/partition context from mountedpfs1:candidates instead of assuming low-level HDD readiness alone preserves the boot-side path contract. - user later confirmed on 2026-03-28 that the exact-boot-mount/source-context source restored the USB-before-HDD-page startup/Profile repro on hardware, so the remaining blocker is no longer startup/Profile lookup.
- USB first-entry backend discovery:
- a 2026-03-27 hardware report said the first USB page entry reported no backend, but backing out and re-entering then worked.
- current code already had bounded retry loops for USB root discovery, but all retries happened back-to-back with no yield for the backend to become visible.
- current source now adds a bounded wait only in
BuildUsbIdentityDeferred(), leaving MX4SIO discovery logic unchanged. - user later confirmed the corrected source on hardware.
- HDD
POPSTARTER.ELFwhen launcher/sidecar/CWD is on HDD: - current reported hardware result is still a black-screen hang.
- path/mount/CWD mitigations plus the HDD-backed non-reboot
ExecPS2cleanup are present in current code. - a 2026-03-27 hardware re-test of the current source still black-screened with boot source HDD, default/Profile 1/cwd/sidecar
POPSTARTER.ELFon HDD, and game device HDD. - a second 2026-03-27 hardware report also black-screened while launching a USB game with Profile 2 pointing
POPSTARTER.ELFto HDD, so the remaining failure is now treated as the HDD-backed POPSTARTER exec path itself rather than only HDD game routing. - a later 2026-03-27 hardware report also said booting from another device and launching an HDD title with sidecar
POPSTARTER.ELFon that boot device black-screened, and the user identified it as a regression. - the EE-side HDD direct-load workaround in
src/elf_loader/src/elf.chas therefore been reverted; it did not fixD-10and is no longer supported by hardware evidence. - later 2026-03-27 Memory Card staging, stripped-handoff, CWD/selector, and HDD-init-state experiments in
bin/POPSLDR/system.luadid not fixD-10/D-14and coincided with repeatedD-15failures. - user later confirmed on 2026-03-28 that USB boot + USB sidecar/cwd
POPSTARTER.ELF+ HDD game passes on the narrowed source, so the remaining blocker is again isolated to HDD-backedPOPSTARTER.ELF. - a later 2026-03-28 re-test still black-screened on that narrowed Lua-side source with no visible positive change, so the next shared preservation step tested was the loader's automatic post-load exec-slot keep in
src/elf_loader/src/elf.c. - that loader-side no-auto-exec-slot-preserve source still left non-HDD POPSTARTER HDD-game launches on the restored selector-only handoff, while
R2could still request the explicithdd0:PART:pfs0:/GAME.ELFselector. - a later 2026-03-28 re-test on that loader-side source still black-screened for
D-10on bothXandR2, and the user clarified the other same-day success result was anotherD-15run rather thanD-14. - a later 2026-03-28 re-test on that forced-
reboot_iop = 1source still black-screened forD-10andD-14, so reboot mode alone did not separate the remaining failure. - a later 2026-03-28 re-test on that direct-
hdd0:PART:pfsN:/POPSTARTER.ELFpreference source still black-screened for bothD-10andD-14, so exec-path form alone did not separate the remaining failure. - follow-up repo inspection then found
BuildDirectHddExecPathFromMounted()had been generating malformed direct aliases without the colon afterpfsN, so that earlier direct-path experiment was not a clean control. - inspection of
wLaunchELFandPlayStation2-Basic-BootLoader-Extendedshowed the common HDD ELF pattern is to mount the target partition explicitly and feedSifLoadElfa mountedpfs0:/...path rather than a directhdd0:path. - a later 2026-03-28 re-test on that mounted-
pfs0:embedded-loader source still black-screened forD-10. - broader comparison then showed current HEAD had drifted away from this repo's own March 24 partitioned-loader diagnostics by reintroducing
SifExitRpc()immediately before the parentExecPS2into the embedded loader. - this repo's earlier HDD diagnostics also recorded that the embedded-loader boundary only became stable once EE-side SIF teardown before
ExecPS2was removed from the parent handoff. - comparison against
wLaunchELFandPlayStation2-Basic-BootLoader-Extendedalso showed another remaining mismatch: this repo's embedded loader had no separate original HDD source-context argument, so it could only make reset decisions from the mountedpfs0:/...load target. - follow-up repo comparison then showed the remaining source-context gap was broader than the loader alone: Lua had usually already normalized HDD POPSTARTER to mounted
pfs1:/pfs3:paths before the reboot loader saw it, so the loader was still not being given the original HDD path it needed to force thepfs0:remount pattern used bywLaunchELF. - broader comparison against
wLaunchELFalso showed the remaining ownership mismatch was not just reset policy: the parent/loader boundary there does not pre-unmount PFS state and the embedded loader callsSifLoadElfdirectly afterSifInitRpc(0), withoutSifLoadFileInit/Exit. - a later 2026-03-28 re-test on that exact-boot-mount/source-context source still black-screened for
D-10, so full raw-source reconstruction alone was still not the missing link. - repo comparison against the local
ps2sdktree then showed the remaining drift was structural: the header still documented a partition-aware loader contract, but the live reboot path was still using a custom source-context channel instead of the same partition/load-path/argv split. - current source therefore keeps the restored non-HDD POPSTARTER path, seeds the exact boot
pfs1:mount metadata frometc/boot.luainto Lua-side HDD mount tracking, and replaces that ad hoc reboot handoff with an explicit partition-aware contract acrossbin/POPSLDR/system.lua,src/luasystem.cpp,src/elf_loader/src/elf.c, andsrc/elf_loader/src/loader/src/loader.c. - on that contract, Lua passes exact HDD partition context separately from the mounted load path, normalizes the partition-aware exec filename back to generic
pfs:/..., routes HDD partition-aware launches through cold external-launch prep so the old trackedpfsN:mount is not preserved into exec, and the parent remountspfs0:from that partition while reusing the mounted relpath Lua already resolved. - a 2026-03-29 hardware result on the prior partition-aware source no longer black-screened, but the launcher regained control with
rc=-1 (returned after 22618 ms), which meant that specific artifact had moved the remaining failure past the old black-screen boundary and into the embedded-loader or target-ExecPS2handoff. - a later 2026-03-29 GitHub artifact re-test on that broader partition-aware/current-source line black-screened again, so the returned-rc boundary is not yet stable enough to treat as the new steady state.
- repo history comparison then showed that this partition-aware reboot path had drifted away from the original parent-side embedded-loader jump contract in
src/elf_loader/src/elf.c: it no longer performed the BRAM wipe or the originalSifInitRpc/SifLoadFileInit/SifLoadFileExitandSifExitIopHeap/SifExitRpc/SifExitCmdsequence around that finalExecPS2. - repo history comparison also showed that earlier child-loader
fileXioexperiments had never been retested after the later parent-side jump-contract fixes, so they were not a clean control for the current boundary. - later repo audit also showed that POPSLoader's normal POPSTARTER launch path passes only the selector as target
argv[0], while the newer HDD child-loader path had started prepending a replacement executable path; that changed the HDD-backed POPSTARTER argv layout away from the working non-HDD path. - repo audit also found that selected-profile state and stored canonical profile paths could drift apart, so a chosen Profile 1/default path could still launch another profile's canonical HDD POPSTARTER path without the user realizing it.
- current source restores that original parent-side jump contract, returns the actual embedded-loader
ExecPS2result instead of collapsing it to-1, keeps legacySystem.loadELF(path, reboot_iop, selector)on POPSTARTER's normal one-argument selector contract, aligns the child loader closer towLaunchELF/PlayStation2-Basic-BootLoader-Extendedby removing the post-reset MC module reload and exiting SIF command state before the final targetExecPS2, routes partition-aware HDD launches through the explicitSystem.loadELFWithPartition(path, reboot_iop, partition_context, selector)API and mounted-pfs0:SifLoadElfonce the parent has already remounted the target partition, normalizes stale canonical profile-path state before launch/save, keeps the older iomanX-awarefileXiopath only for directpfs:/hdd:loads with no partition-aware HDD context, and now exposes the real exec filename separately from the probe/open path in the launcher popup. - 2026-05-19 source audit found that several current-source defects must be fixed before treating new hardware results as clean controls: Lua mounted-PFS fallback can leave stale launch context, normal HDD game labels can fail fallback partition parsing, the fallback path can skip the pre-exec gate even when reconstruction failed,
System.loadELF(..., args..., partition_context)can leak the partition context into target argv, the generic embedded-loader default-argv contract is unreachable, and the child loader still usessnprintf/strncatdespite older docs claiming that risk was avoided. Seedocs/archive/HDD_POPSTARTER_HANDOFF.md. - 2026-05-19 source change applies the handoff's recommended fixes (except for the child-loader
snprintf/strncatcleanup, which would require regenerating the embedded loader blob and was intentionally deferred):bin/POPSLDR/system.luaaddsNormalizeHddPartitionLabelForMountsoResolveFallbackMountedPfsExecPathaccepts the bare partition labels (e.g.__.POPS) returned byParseHddGameEntry. Previously the fallback parsed onlyhdd0:-prefixed labels and always failed for normal HDD game entries.RunPOPStarterGamenow resyncscontext.exec_path/exec_partition_context/exec_mounted_path/exec_original_slot/exec_pfs_slot/source_pfs_slot/cold_external_launch/keep_hdd_slots/keep_hdd_slots_after_loadand the matchinglaunch_diagnosticsfields after a successful fallback, soLaunchEngineno longer consumes stale pre-fallback values.- The HDD pre-exec gate is skipped only when the fallback actually reconstructed a partition-aware path; if reconstruction failed in non-strict mode, the gate now runs so its own partition-recovery logic can succeed or fail loudly instead of silently launching with stale context.
src/luasystem.cppexposesSystem.loadELFWithPartition(path, reboot_iop, partition_context, args...). The legacySystem.loadELFtrailing partition_context detection has been removed; partition_context can no longer be copied into target argv.LaunchEnginecalls the new binding whencontext.exec_partition_contextis set.src/elf_loader/src/elf.cExecuteViaEmbeddedLoaderno longer rejectsargc == 0, so the documented child-loader default-argv synthesis (build_default_target_arg0) is reachable. The HDD POPSTARTER-specificargv[0]guard inExecuteHddBackedViaEmbeddedLoaderis intentionally left in place.
- a 2026-05-20 artifact screenshot then showed
D-10returning to the launcher withPOPSTARTER HDD pre-exec gate failed: Cannot resolve HDD partition context,POP:pfs3:/POPS/POPSTARTER.ELF, andAPP:hdd0:+OPL:pfs:/APPS/PS1_POPSLOADER/. - 2026-05-20 source follow-up fixes the argument-shifted failure popup call sites, lets Lua parse
hdd0:PART:partition-context strings, lets shared HDD recovery candidates accept safe bare labels such as__.POPS, and changes mounted-PFS fallback to recover the actual mounted POPSTARTER source partition before falling back to the selected HDD game partition. It does not change the POPSTARTER selectorargv[0]contract. - a 2026-05-20 latest-artifact
D-15report then showed USB boot with USB sidecar/cwdPOPSTARTER.ELFplus an HDD title black-screening. Current source follow-up restores legacySystem.loadELF(path, reboot_iop, selector)one-argument selector behavior for normal/non-HDD POPSTARTER launches and keeps HDD partition context onSystem.loadELFWithPartition(...). - a later 2026-05-20 readable-diagnostic screenshot then showed the pre-exec gate failing on the loader-facing generic exec path
pfs:/POPS/POPSTARTER.ELF; source now derives a real mounted probe path forValidateHddPopstarterExecGate()while keeping the genericpfs:/...exec path for the partition-aware C loader contract. - a later 2026-05-20 report narrowed the non-HDD POPSTARTER regression to default/cwd sidecar resolution: explicit
mass:/POPS/POPSTARTER.ELFlaunches, but defaultPOPSTARTER.ELFcan stop atCant find POPSTARTER ELF. Current source now includes the live current directory in sidecar lookup and derives fallback sidecar paths from boot/app directories instead of blindly appending to raw boot strings. - 2026-05-20 simplification pass addresses the over-engineering risk: normal HDD-backed POPSTARTER launches no longer use Lua partition context, generic
pfs:/...exec rewriting, the Lua HDD pre-exec gate, the mounted-PFS fallback remount path, or the embedded-loader reboot path by default. They keep the resolved executable path and call legacySystem.loadELF(path, 0, selector). - a 2026-05-20 hardware result on that direct non-reboot artifact still black-screened before POPSTARTER debug screens, so the next source pass removes the remaining HDD-only parent-side pre-
ExecPS2cleanup fromLoadELFFromFileExecPS2()(fileXioUmount,SifExitIopHeap,SifExitRpc,SifExitCmd) to match the working non-HDD POPSTARTER handoff more closely. - hardware status after the 2026-05-20 source follow-up remains
Unknown (verify on hardware). The handoff doc verification sequence (D-15first, thenD-10X, thenD-10R2, thenD-14, thenD-12sanity, thenU-10separately) still applies. - current source still keeps the
R2selector-path experiment for HDD game launches, but that remains secondary to restoring and preserving the non-HDD POPSTARTER baseline for HDD titles. BOOT.ELFafter HDD page init:- repo history shows the BOOT.ELF modal originally used a simpler non-reboot
System.loadELF(elf_path, 0, elf_path)path without launch-CWD setup, and later source changed it toreboot_iop = 1plus launch-CWD. - a later 2026-03-29 hardware report said BOOT.ELF still behaved incorrectly once HDD runtime had been initialized, which points more narrowly at carried HDD/IOP state than BOOT.ELF lookup itself.
- current working inference is that this
U-10failure may share the same underlying handoff/state-poisoning boundary asD-10, but that remains an inference rather than a proven shared root cause. - current source therefore keeps the no-launch-CWD rollback, re-enables
reboot_iop = 1for BOOT.ELF only when HDD runtime has already been loaded, and uses a BOOT.ELF-specific cold external-launch prep that clears the exec keep mask and unmounts tracked HDD slots instead of preserving boot PFS state. - current hardware status on that conditional-reboot/cold-prep source is still
Unknown (verify on hardware). - PAL asset proportions:
- code compensates for PAL layout,
- final display result still needs hardware confirmation.
- 2026-05-22 Diagnostic Sentinels & USB Control Test Decision:
- A series of targeted sentinels were executed to debug the post-load/post-reset SIF RPC handshake hang (Teal/Blue freeze) on D-10.
- SifLoadFile (
SENTINEL_SIFLOADFILE_POPSTARTER.ELF) and fileXioInit (SENTINEL_FILEXIOINIT_POPSTARTER.ELF) sentinels passed on hardware, showing that initial target-side setup and fileXio services are fully stable post-handoff. - Post-reset SifInitRpc sentinels (
TARGET-IOPRESET,POST-RESET INITCMD SPLIT, andPOSTINITCMD RPCMODE1) hung, proving the rebooted IOP fails to respond to RPC initialization. - Manual RPCINIT handshake probing and retry sentinels timed out, confirming the IOP does not signal
SIF_SREG_RPCINITafter resetting. - Resetting the IOP with UDNL arguments (
SifIopReset("rom0:UDNL rom0:EELOADCNF", 0)) viaSENTINEL_TARGET_IOPRESET_UDNL_RPCINIT_POPSTARTER.ELFalso timed out (PURPLE/MAGENTA loop), ruling out the blank reset argument as the sole cause of the SIF handshake hang. - The USB control path (USB POPSLOADER.ELF + USB sidecar/CWD POPSTARTER.ELF) was verified on hardware to work successfully, including when selecting games from the HDD listing.
- Source/Device Boundary established:
- Known-good: USB POPSLOADER.ELF + USB sidecar/CWD POPSTARTER.ELF works, including when selecting HDD games.
- Known-bad: HDD POPSLOADER.ELF + HDD sidecar/CWD POPSTARTER.ELF fails. HDD POPSTARTER.ELF fails regardless of selected game device/listing.
- Interpretation: The failure does not follow the selected game device or the HDD game listing by itself. The failure follows the HDD-backed POPSTARTER / HDD-backed loader-origin state.
- Updated Diagnostic Boundary: The next investigation focuses on what differs when POPSTARTER.ELF is resolved, loaded, and executed from HDD versus USB sidecar/CWD, including file-open/mount state, current working directory/source device state, argv/environment passed to POPSTARTER, and IOP module state left behind by loading the target ELF from HDD.