Design decisions
← POPSLoader docs · view on GitHub ↗
Last updated: 2026-07-13 (R3Z3N settings-review round 2 — profiles system dropped, START settings menu, region-native confirm button, keyboard rework; see the entry at the top of the log. Earlier: the 1.0.0 release + dev rename; the 2026-07-09 feature wave — Adaptive BDMA semantics, partition-installed games gated on the IMAGE0.VCD probe, the 22.05 kHz boot-chime re-encode; the 2026-07-07 full-repo audit fix waves; selectable cover/details folder (ART_LOCATION), default SMB port 1111, MX4SIO enumeration settle restore; POPSTARTER resolution reorder; exFAT/ATA isolation; frame-counted nav/desc-scroll, analog-fold gate, layered cover art, OPL-style overscan, boot-sound toggle, HDD (exFAT)/BDMA-ATA backend, µs-as-ms timer sweep). Released line: 1.0.1 (2026-07-13; previously 1.0.0 2026-07-10); active dev / rolling branch dev. 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. The current experimental goal state (device-layer rebuild, cover art, defaults) is STATE.md's Experimental channel (EXP32–EXP34) section; entries below that predate it are historical.
DECISIONS
Decision Log Format
Each entry records:
- Date (YYYY-MM-DD)
- Decision
- Rationale
- Implications
- Evidence
Decision Log
2026-07-15 — R3Z3N review round 3: footers advertise less, Credits into Settings, one save menu
- Decision: act on R3Z3N's PCSX2 pass (seconded by Berion on the buttons). (1) Footers carry the primary action plus the way out and nothing else: main menu = Select/Settings/Exit, game list = Launch/Back, Settings drops Hide Text. The bindings are untouched — Square still toggles cover art, START still opens Settings — only the legend clutter goes. (2) Credits moves into Settings (new About section) and the Triangle->Credits binding is deleted everywhere. (3) Back and START open the identical Save Changes / Reset Defaults / Discard & Exit menu; the old X-Save/O-Cancel/Triangle-Don't-Save prompt is deleted. (4) "Carousel Devices" -> "Device List". (5) Keyboard default ABC -> QWERTY (normalizer fallback too). (6) Cover/details folder values spell the device prefix (
device:/POPS/ART). (7) The Hidden-games row gains a dimmed "How to hide a game / L3 on the game list" hint. - Rationale: verbatim tester feedback — "select. settings, exit"; "I would drop credits, settings and game art buttons in games list"; "get rid of hid text here"; "put credits in settings imo"; "the back button prompt is differnt than the start prompt ... they should be the same"; "carousel is actually device list"; "Keyboard layout default should be QWERTY"; "I presume you mean device:/POPS/ART?"; "I dont know how to hide games...". Each is a case of the UI lying, hiding, or shouting.
- Implications: hiding a binding without removing it is a discoverability trade accepted deliberately (the tester asked for exactly that, and README/TESTING still document the bindings). Credits reuses the existing
CreditsReturnScenemechanism, so it returns to Settings with staged drafts intact (returning viaSceneChangedoes not re-runSyncSettingsDraftFromRuntime; only the START-entry handler does). A minimal action row kind came back, but INSIDE a section this time so it obeys the accordion — that placement was the objection to the old Save/Reset/Discard rows, not the concept. The two save routes keep ONE deliberate difference, the save target, because it matches user intent: Back exits to where it came from and tolerates a failed save (the user was already leaving), START stays put on failure so drafts survive a retry.labels.triangle,labels.select_toggleandModal.OpenSaveSettingsare deleted as dead. i18n: +4 keys x 5 languages, -35 dead keys, guarded by a checker that refuses to drop a key still referenced in code (comments excluded). - Evidence:
bin/POPSLDR/ui.lua,bin/POPSLDR/system.lua; Lua parse-check + host harness 18/18 (T16 extended to pin the new keys) + a real ps2dev/ps2dev:v2.0.0 build with the identity markers passing, the new strings present in the ELF and the dropped ones absent. Two injection traps hit and fixed: a globally-deduped inserter put all 4 keys in French only, andjson.dumpsdefaults toensure_ascii=True, emitting\uXXXX— valid JSON, INVALID Lua (Lua needs\u{XXXX}with braces, and this table stores raw UTF-8; verified the repaired bytes are real UTF-8, c3 80 for A-grave). NOT hardware-tested.
2026-07-13 — R3Z3N settings-review round 2: profiles dropped, START menu, region-native confirm, keyboard rework
- Decision: implement R3Z3N's second settings-UI review wholesale (maintainer agreed in the review chat): (1) the 16-preset POPSTARTER profile system is REMOVED —
pops_profiles.luadeleted,PLDR.POPSTARTER_PATHis the single value (""= Automatic = the launch ladder; a set path = custom-first with silent fallback; warn only when nothing resolves anywhere); (2) Save/Reset/Discard moved off the settings list to a START-opened modal menu; (3) region-native confirm:rom0:ROMVERbyte 5 =='J'flips CIRCLE=confirm/CROSS=cancel across the pad map, footer glyphs, modal hints and blocking prompts, and the confirm action renders far-left in every footer; (4) keyboard: number row on top, opens UPPERCASE on the letter row, the in-keyboard layout strip removed (Settings row is the one chooser), Case/Symbols label names the target state, held-CONFIRM keeps the key pressed; (5) accordion sections reveal over ~6 frames; headers get their own color, read-only rows dim, Hide UI Text reads On/Off. - Rationale: R3Z3N: the profile presets duplicated what the resolution ladder already does, exposed pfs partition numbers users should never see, and confused the CWD case; the inline Save/Reset rows sat oddly above Memory Card; "confirm should always be far left" and Japanese consoles expect circle-confirm; the keyboard duplicated the layout setting and mislabeled the case toggle.
- Implications:
PROFILE=/POPSTARTER_MODE=config keys are no longer written; on load, an empty path + a legacyPROFILE=Nin the mapping table migrates to that preset's absolute path — the adversarial review refuted the initial "no migration needed" claim. The load-bearing entries are thehdd0:__commonpresets (2-5): an HDD-resident POPSTARTER driving removable-device games is the supported D-14 setup, and the Automatic ladder probes__commononly for HDD-policy launches, so those users would have lost their POPSTARTER; the cross-device removable presets (6-12) likewise. The oldmc?:/POPSpresets 13/14 are deliberately NOT migrated (a memory card never carries aPOPSfolder — maintainer, 2026-07-13 — so migrating them could only pin a phantom path); they land on Automatic like preset 1. The mc:/POPSTARTER presets 15/16 migrate faithfully (real locations, also ladder-covered).ResolveLaunchPopstarterPathand the D-10/D-15 partition machinery are untouched (UI/config layer only). The C-side embed tables/Makefile/CI Lua lists droppedpops_profiles.lua. The harness gained T17 (path round-trip + legacy-key ignore) and a false-PASS fix:check()treated Luafalse, "reason"tuple returns as truthy, so two-value failures were reported as PASS — discovered because T16 kept passing after its hint keys were deliberately removed. - Evidence: the 2026-07-13 commit run on
dev(settings polish → accordion animation → ROMVER confirm swap → keyboard rework → profiles drop);tools/host_harness.py18/18; R3Z3N review chat 2026-07-12.
2026-07-10 — 1.0.0 released; dev/rolling branch renamed BETA-13-PLAY -> dev
- Decision: the BETA-13 candidate graduated straight to a 1.0.0 public release (tag
1.0.0atbb15a45, a tree-adopting merge of BETA-13-PLAY onto master; GitHub Release live + latest withPOPSLOADER-1.0.0.zip). The dev/rolling branch was then renamedBETA-13-PLAY->dev(generic name; the version-suffixed*-PLAYconvention was tied to a specific beta and no longer fits post-1.0.0). - Rationale: the feature set is broadly hardware-confirmed on the core (multiple testers) and the newer opt-in features degrade gracefully, so 1.0.0 was the maintainer's call for release momentum. Renaming to a version-independent
devmeans the rolling branch never needs renaming again per release. - Implications: the ONLY load-bearing reference was
.github/workflows/rolling-release.yml'spushtrigger (nowdev); CI (compilation.yml) triggers on"*"so it is name-independent,masteris the default branch (unaffected), and the MEGA badge + rolling-download point at the MEGA folder + therolling-releasetag (not the branch). Forward-looking doc references were retargeted todev; dated/historical mentions ofBETA-13-PLAY(changelog, this log's older entries, the QA ledger rows,docs/archive/RELEASE_BETA13.md) are left accurate. Backups pushed before the cut:backup/master-pre-1.0.0(10777a9),backup/BETA-13-PLAY-pre-1.0.0(9441b7d). - Evidence: tag
1.0.0, masterbb15a45, prep62ee7a5;rolling-release.ymltrigger; the rename commit ondev.
2026-07-09 — Adaptive BDMA semantics: saved-mode-as-USB-preference, launch-cancel on staging failure, adaptive-off restage (800ac9c, 3eff270)
- Decision: Adaptive BDMA (issue #509,
PLDR.BDMA_ADAPTIVE, default Off — SUPERSEDED 2026-07-28: the default is now On, see the 2026-07-28 entry below) stages the launched device's BDMA variant at launch with an equipped-check-before-write. Three deliberate semantics: (1) the USB page resolves from the saved BDMA Mode — savedUSBEXFATkeeps the exFAT pair, any other saved mode means FAT32 (modules removed); (2) a staging failure cancels the launch with a warn toast; (3) turning adaptive off restages the user's chosen mode in the same save (equipped-gated). - Rationale: (1) FAT32-vs-exFAT USB is not detectable from the launcher (identical BDM driver name, no fs-type binding), and a user whose drive needs the exFAT modules necessarily has that mode saved — it never worked any other way; (2) nothing queued after exec can render, and launching on wrong/half-written modules black-screens inside POPStarter with zero diagnostics — cancelling returns to the menu loop where the toast is visible (adversarial-review finding); (3) with adaptive on,
bdma_mode.txttracks the LAST LAUNCH, so without a restage the off-transition's marker reconcile would silently clobber the saved preference with whatever launched last (review finding). - Implications: the marker-override in
ReconcileBdmaModeWithEffectiveStateis gated onBDMA_ADAPTIVE ~= true; the marker file format is unchanged (mSAS reads it); no success toast exists — the tester pass signal is both-device launches booting; staging runs AFTER the cheap launch validations so a blocked launch costs no card writes; the POPSTARTER-folder interlock extends to the adaptive flag both ways. - Evidence:
bin/POPSLDR/system.lua(MaybeApplyAdaptiveBdma/ResolveAdaptiveBdmaTarget/IsBdmaModeEquipped, the CommitSettingsChanges ON→OFF arm),bin/POPSLDR/ui.lua(Storage row + interlocks), review workflow wf_563626bc-4fe (8 confirmed findings, all actioned in3eff270).
2026-07-09 — Partition-installed games are gated on an IMAGE0.VCD mount-probe, not partition name alone (198bae5)
- Decision: the HDD (PFS) page lists HDDOSD/PSBBN partition-installed games (
PP.<name>/__.<name>, image atpfs:/IMAGE0.VCD) discovered via the newHDD.ListPartitions()APA enumerator — but a candidate is listed ONLY if a read-only mount-probe findsIMAGE0.VCDat its root. - Rationale: name alone is a proven false-positive trap — HDDOSD apps also ship as
PP.*partitions (e.g. CodeBreaker); the wLaunchELF source carries the same warning. The probe cost (one tracked RDONLY mount per candidate, once per boot under theHAS_CHECKEDgate) buys a list with no phantom entries. - Implications: entries reuse the standard
"PART|rel"encoding so cache/hide/R1/R3/launch routing flow unchanged; display + art/txt base = partition name minus the 3-char prefix (matches POPStarter's own__common/POPS/<name>convention); launch argv0 = the literal partition label +.ELF(case-sensitive, unsanitized); HDD sorts key on the DISPLAYED name (CompareGameEntriesByDisplay) since raw-encoded order put everyPP.*above every__.POPS*. HW-unproven: thePP.<name>.ELFargv0 contract against the shipped r13 beta, and the wiki-documented old-launch-type__commonasset bug (VMC scope). - Evidence:
.research/wiki/hdd-mode.md+.research/firstparty/partition-games.md(R3Z3N);src/luaHDD.cppListPartitions;bin/POPSLDR/system.luaDiscoverPartitionGames.
2026-07-09 — Boot chime re-encoded at 22.05 kHz (e27103f; −190.9 KB, zero code changes)
- Decision:
bin/POPSLDR/boot.adpre-encoded 44.1 kHz → 22.05 kHz mono (390,912 → 195,472 bytes), full 15.51 s length kept. - Rationale: the audsrv APCM header carries the pitch; nothing in the C/Lua chain hardcodes the ADPCM rate, so the swap is code-free. Spectral analysis of the decoded original showed only 0.04 % of its energy above 11.025 kHz — the chime was a 44.1 k container around ~10 kHz content, so the halved rate discards nothing audible. Encoder = a faithful Python port of ps2sdk
adpenc(roundtrip-verified: 34.6 dB codec SNR, flat per-band spectrum, exact frame math). - Implications: replacements need
git add -fpast the*.adpgitignore; keep the asset MONO (audsrv ADPCM is single-voice — a stereo file plays L-then-R sequentially); a hardware EAR check is still the only proof for SPU2 pitch handling — if it sounds wrong on a console, revert this one commit. A/B WAV previews live in the gitignored.research/_audio_preview/. - Evidence:
Makefileembed rule (unchanged);src/sound.cpp/ ps2sdkaudsrvheader semantics; the analysis pipeline in the session scratchpad (adp_pipeline.py).
2026-07-07 — SMB default Port = 1111 is DELIBERATE (rationale for 75e1a84)
- Decision:
PLDR.SMB_FIELDSships PORT default"1111"(changed from 445 in75e1a84at the maintainer's request), and it stays. - Rationale: the maintainer runs SMB servers on custom ports via his PS2-Servers repository; 1111 is the intended ecosystem default for those setups. Standard SMB servers listen on 445 — that is a documentation concern (README's SMB section + TESTING both tell users to set the Port to their server's real port), NOT a reason to change the default. The 2026-07-07 audit flagged this independently 14 times; this entry exists so no future audit re-flags it.
- Implications: ~~
RenderSmbConfigkeeps the 445-implied convention~~ — SUPERSEDED 2026-07-28: the port is now written EXPLICITLY, always, including:445. OnceSMBCONFIG.DATbecame a file POPSLoader READS BACK as well as writes, a suppressed port was a value that silently changed meaning on the round trip. The maintainer's reference format in issue #560 is explicit (192.168.4.47:445 PS2). Connect failures still name the failing step so a port mismatch is self-diagnosable. Do NOT migrate persistedSMB_PORTvalues. - Evidence: maintainer statement 2026-07-06 ("Port 1111 should not be the issue, I use custom ports all the time thanks to my PS2-Servers repository");
bin/POPSLDR/system.luaSMB_FIELDS.
2026-07-07 — The audit fix waves (61 findings actioned across 5 code commits)
- Decision: Execute the
docs/REPO_AUDIT_2026-07-07.mdfix plan in 6 waves, each independently revertable: Wave 1 SMB core (d2496ec), Wave 2 settings correctness (4665b19), Wave 3 SMB UX/guards (a546d45), Wave 4 cwd/pack-root defaults (8809925), Wave 5 runtime traps/polish (7ec708a), Wave 6 docs (this commit). Checkpoint branchcheckpoint/pre-audit-fixes@f5f73ddis the pre-fix restore point. - Headline: the SMB connect root cause (audit U02) —
EnsureNetstood up the EE-side lwip stack whilesmbmanruns on the IOP-side stack, so IP config/DHCP never reached the stack doing the networking; connect failed on hardware regardless of settings. Fixed by binding viaps2ip_init()(the ps2ips RPC), mirroring OPL;-lps2ipdropped from EE_LIBS. Also:System.smbNetUpsplit for per-phase connect progress; honest error mapping (IPCFG_FAIL/PROT_FAIL); GETSHARELIST cache flush; trim + shape validation on SMB fields; OSK symbol shift-map; SMB-modules launch gate; boot-time .DAT heal; Boot Page exFAT wiring; Reset-Defaults/dirty-aggregate completeness; CRLF sidecar normalize; drafts survive failed saves; POPSTARTER pack root resolves mc0→mc1; usb:/smb: boot sidecar exclusion; DKWDRV cwd probe; launch-diagnostic format-crash fix; line-preserving Lua minifier; Exit→OSDSYS pfs teardown;make packagedeleted. - Implications: every fix was pre-checked against the preservation contracts (D-10/D-14/D-15, EnsureBootPartitionWritable, exit/DKWDRV routes, embedded loader, reboot_iop) — none touched. All five code waves are CI+Rolling green. The whole SMB chain plus the wave-2/4/5 behavior changes need the hardware round: connect → browse → launch → disconnect on a real PS2 + SMBv1 share is the headline test.
- Evidence:
docs/REPO_AUDIT_2026-07-07.md(findings U01-U62 with per-fix solutions); commits above onBETA-13-PLAY.
2026-06-30 — Selectable Cover/Details Folder (ART_LOCATION); the open()-vs-fopen() cover theory was DISPROVEN (7f9b5fa, 6641860, BETA-13-PLAY)
- Decision: On REMOVABLE devices the cover
.png+ details.txtlookup folder is user-selectable via a newART_LOCATIONsetting (Settings > Game List > "Cover/details folder"):pops_art(default) tries<device>:/POPS/ART/first,popsuses the game's ownPOPS/folder beside the.vcd,arttries a top-level<device>:/ART/. The game's ownPOPS/folder is ALWAYS appended as a final fallback. HDD/PFS is unchanged (fixedhdd0:__common/POPS/ART/).CoverCache:GetOrLoadalso drops itsdoesFileExist/open()existence pre-probe and letsGraphics.loadImage(fopen) open the file directly. - Rationale: a tester (FifthFox) reported covers not showing from a
POPS/ARTsubfolder on USB/MX4SIO while a cover/.txtbeside the.vcdworked. I first hypothesised anopen()-vs-fopen()difference on nested paths. A source investigation (workflow w0ucpisvc, 4 agents, high confidence) DISPROVED that: in ps2sdkopen()andfopen()both funnel through the SAME libcglue_open(ee/libcglue/src/glue.c->__path_absoluteincwd.c; newlibfopen->_fopen_r->_open_r->_open,open->_open_r->_open), so they resolvemass:/POPS/ART/x.pngbyte-identically. Nested subfolder reads WORK on the BDM/FAT drivers: OPL readsmass0:/ART/<name>_COV.png+mass:/CFG/*.cfgwith plainopen()(.research/oplbdmsupport.c:585/588, textures.c:563, opl.c:2016, util.c:177), and ps2sdk FatFsfollow_path()walks/-segments with no depth cap. POPSLoader'sBuildCoverCandidatesbuilds a correctmass:/POPS/ART/<name>.pngfor USB and MX4SIO. So the pre-probe drop is an INERT cleanup (one fewer redundantopen()before the identicalfopen()), NOT a fix; aPOPS/ARTcover that does not show is a filename/location/build-version issue, not a read-path limitation. The setting stands on its own as a usability feature (users pick where art lives). A directory-listing existence check was rejected (only proves existence; reads work anyway). - Implications: the pre-probe drop is behavior-neutral (loadImage still returns nil for a genuinely missing file, memoized in
CoverCache.failed). Becauseopen()==fopen(), a.txtinPOPS/ARTalso reads fine when present (no separate limitation). To stop this costing test cycles,UpdateSelectionstores the primary missed path (last_cover_probe) and the list view prints a "No cover. Looked for:" caption under the placeholder when preview is ON, text is shown, and no details occupy that space, so a tester self-checks the .pngname/folder.ART_LOCATIONsits betweenDETAILS_ALIGNandHDD_FSinEncodeSettings; persisted-key count is 22 + the appended SMB block. - Evidence:
bin/POPSLDR/ui.luaBuildCoverCandidates(~190) +CoverCache:GetOrLoad(344, no pre-probe, corrected comment) + the missing-cover caption (list-view render);bin/POPSLDR/system.luaART_LOCATIONplumbing (encode3339, default3584, load3703/3770). SDK/OPL proof: workflow w0ucpisvc (newlib mirror fopen.c/openr.c/sysopen.c; ps2sdk ee/libcglue glue.c + cwd.c; OPL.research/opl; fjtrujy/FatFs iop-r0.16 ff.cfollow_path). Setting7f9b5fa, inert pre-probe drop6641860, correction + diagnostic + docs this push.
2026-06-29 — Deterministic Removable-Device POPSTARTER Resolution: custom → cwd → device (BETA-13-PLAY)
- Decision: For REMOVABLE devices (USB / exFAT-ATA / MX4SIO / MMCE),
PLDR.ResolveLaunchPopstarterPathresolves POPSTARTER.ELF in one deterministic order: (1) an explicit user-configured absolute path (the custom-path setting, or any absolute Profile selection) when it actually resolves; (2) the game's own<device>:/POPS/POPSTARTER.ELFwhen it exists (lets a per-device build be used without enforcing it); (3) the launcher's own folder ("cwd") via the newResolveCwdSidecarPopstarter(probesAPP_DIR_LOCAL/ live CWD /BOOT_ARGV0/BOOT_PATH/APP_DIR_RAW, skipping HDD/pfs candidates); (4) the existingmc0:/mc1:/POPSTARTER+ configured-default fallback net. (Initial cut was custom→cwd→device; flipped same-day to device-before-cwd so per-device POPSTARTER builds actually win — see the FifthFox USB-delay report.) This SUPERSEDES theee4cba0"prefer device-local first" precedence — device-local drops from first to step 3, so a configured custom path now wins as intended. Profiles need no UI change: an absolute Profile selection IS the step-1 custom path, so they act as presets that fill the custom field. - Rationale: eliminate new-user confusion / "cover them out of the box" — a launcher + POPSTARTER dropped together (cwd) just works, the per-device
POPS/folder is the catch-all, and an explicit custom path is always honored. Custom is existence-gated so a stale/typo'd path falls through to cwd/device instead of stranding a launch. - Implications: HDD/APA (internal PFS) resolution is INTENTIONALLY UNCHANGED — HDD games hit the
colocated == nilearly return and fall straight into the unchangedResolvePopstarterPath, preserving the D-10/D-15 partition-context + embedded-loader path byte-for-byte (adversarially verified: HDD-equivalence + order-correctness PASS). The prior2070a65games-only-exFAT fix survives (the cwd/net still finds the MC-resident POPSTARTER). NEW behavior for removable devices → needs HW validation across USB/exFAT-ATA/MX4SIO/MMCE; device-local is ordered AHEAD of cwd so a per-device POPSTARTER build (e.g. a USB-delay build on the USB drive, a faster one elsewhere) wins for that device's games, while cwd + the mc-net still cover everyone else — intended (FifthFox USB-launch report). APA/internal-PFS now has its OWN ordered resolution (custom →hdd0:__common/POPS/POPSTARTER.ELF→ cwd → net): the__commonstep resolves through the SAME path as the shipped bare-pfs__commonprofile (#2), so the D-10/D-15 partition-context +reboot_iop=1+ embedded-loader handoff is byte-identical to selecting that profile (3-skeptic verify PASS, no real bug); on a miss it releases the probe's slot-3 mount and falls to the unchanged boot-sidecar+mc resolver. Landed as a SEPARATE commit so it can be reverted alone — NEEDS a real-PS2 D-10/D-15 check before trust. The BDMA "modules located" picker idea was dropped: the driver variants are embedded in the ELF (no external source), and POPStarter reads them only frommc0:→mc1:("Mass is not supported"). - Evidence:
bin/POPSLDR/system.luaResolveLaunchPopstarterPath+ newResolveCwdSidecarPopstarter(~1786). 3-skeptic adversarial verify wf: HDD byte-identical + order-correct PASS; one unqualified-call bug caught pre-push (PopstarterProbeWithEnsure→PLDR.PopstarterProbeWithEnsure) and fixed. Landed onBETA-13-PLAY2026-06-29; CI+Rolling green.
2026-06-22 — HDD (exFAT) / BDMA-ATA Backend (df2eb9d, 6fd2142, afa1c09)
- Decision: Add an internal-HDD exFAT (BDMA-ATA) backend that scans + launches games as a
mass:device, classified by the EXACT ioctl driver-nameata(strcmp==0 && strlen==3, mirroring OPLbdmsupport.c— never substring, so it can't leak into USB/MX4SIO). Carousel opt 3 = exFAT page (sceneGBDMHDD,gamelocation = mass:/POPS); opt 4 stays the classic PFS page.lua_ata_init/initATAloadsata_bd.irxafterEnsureUsbMass(mirrorslua_mx4sio_init); ApplyBdmaMode stages the distinct R3Z3Nusbd.irx.ata/usbhdfsd.irx.ata(stripped) tomc?:/POPSTARTER/. saildot4k credited for the backend (fixes/feedback/oversight); R3Z3N for the ATA BDM Assault drivers. - Implications: CI+Rolling green; NOT yet hardware-tested (needs an exFAT internal drive). STATUS: implemented / validating on hardware.
- Evidence:
bin/POPSLDR/system.luaCAROUSEL_DEVICE_KEYS+ EXFAT/ATA→GBDMHDD;src/luasystem.cppClassifyMassBackendexact-ata+lua_ata_init;Makefileata_bd.o. Commitsdf2eb9d/6fd2142/afa1c09.
2026-06-22 — -page=ata Routes to the exFAT Page, not PFS (3d89631)
- Decision:
NormalizeLaunchPagemapsata/ata0/ataN→EXFAT(exFAT page, opt 3,GBDMHDD);hdd/hdd0/apa/apa0/anypfs→HDD(PFS page, opt 4).page_to_optincludesEXFAT=3, ATA=3, HDD=4.-modeshares the alias path; barebdmastays a no-op page value. Un-deads the previously-unreachable EXFAT/ATA branches inpage_to_opt+AutoLaunchFromLaunchArgs. - Rationale: the exFAT backend (above) had no reachable launch-arg target;
atais NHDDL's token for the internal ATA drive, and maintainer spec isata→exFAT /hdd·apa·pfs→PFS. - Implications: launchers can open POPSLoader straight to the exFAT page; NEW launch path → needs HW (TESTING.md P1). A DS4-over-BT memset fix (
ds34bt.c, fullPS4_11_REPORT_LEN+2zero, no more uninitialized bytes on the wire) rode along. - Evidence:
bin/POPSLDR/system.luaNormalizeLaunchPage(ata→EXFAT),page_to_opt. Commit3d89631.
2026-06-22 — Layer C lazy-IRX: ds34bt/usbd Deferral DECLINED; Layer C Closed
- Decision: Keep
usbd/ds34usb/ds34btEAGER. Both ds34* hard-importusbd, and the boot-device hint is not a pad-transport signal, so deferring them would strand USB/BT controller input unrecoverably for a tiny boot-time gain. Themmcemandeferral (PR #471, shipped) was the only safe Layer-C win. - Implications: Layer C is closed; do not re-propose the ds34/usbd deferral (STATE.md Known Open Work #3, ROADMAP §4 — both marked DECLINED).
- Evidence:
src/main.cppIRX bring-up order (usbd → ds34usb/ds34bt eager);modules/ds34{bt,usb}/iop/imports.lstusbd_IMPORTS.
2026-06-22 — Ruthless Audit: Dead-Symbol Cleanup + Bug Fixes (2735229)
- Decision: Remove verified-dead symbols (
recursive_mkdir,readPad+pad.hextern,max()in fntsys,modulePath/setModulePath,HDD_DIAG_BYPASS,Footer.order_with_r2/order_settings,NormalizeDirPathCompat/APP_DIR_LOCALin pops_profiles,registered_countin images, the sound.cpp commented demo) and land behavior-preserving fixes (lua_dirpath[255]→256, lua_writefile size-clamp, lua_sleep label,GLYPH_PAGE_OKmacro, lua_destroyreturn 0, image-magic fread check,FREADnotO_RDONLY, system.lua boot_sound/overscan un-nested from the gamelist_cache guard). - Implications: No intended behavior change; hardened crash/leak edges. SHIFTED line numbers in luasystem.cpp/ui.lua/system.lua/fntsys.cpp/sound.cpp/pad.cpp — re-verify any
file:lineanchor into those files. From a 13-cluster adversarial find→verify→synth audit; report-only items (elf-loader OOB guards declined, keep_slots array-vs-set, etc.) tracked in memoryreference-code-audit-2026-06-22. - Evidence: Commit
2735229; CI+Rolling green.
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. (Updatea8e61f3, 2026-06-22: the µs-as-ms sweep was later COMPLETED — theMIN_ACTION_MSaction debounce was removed entirely (edge-triggering is the gate) and the transition / carousel / boot-fade timers were frame-paced; nothing remains parked. See the SUPERSEDED parked 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) — SUPERSEDED (a8e61f3, 2026-06-22)
- SUPERSEDED: the sweep was completed, not parked.
MIN_ACTION_MSwas removed entirely (edge-triggeringpressed = GPAD & ~OLDPADis the gate); PathEditor key-flash/caret-blink + the launch-watchdog label were made unit-correct (Timer.getTime()/1000); the scene-fade/boot-fade/carousel were frame-paced (behavior-preserving). No µs-as-ms timer remains. (Original parked rationale kept below for history.) - 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. - Superseded (2026-06-24,
6004bf8): the Fast/Medium/Slow setting was removed (provato + maintainer: Fast is best). The right-stick scroll is now hardcoded to the Fast pace (_secs = 0.15); the frame-counting decision and rationale above still stand.
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.
2026-07-28 — Adaptive BDMA now defaults ON, and -bdma=<mode> can pin the variant
- Decision:
PLDR.BDMA_ADAPTIVEships On, superseding the 2026-07-09 "default Off, user opts in" call. A new launch argument-bdma=fat32|usbexfat|mx4sio|mmce|atapins the staged variant for that session and overrides Adaptive's per-device choice. - Rationale: CosmicScale (2026-07-28) reported that POPSLoader never installs the BDM Assault drivers for ATA. It never installs them for anything:
MaybeApplyAdaptiveBdmareturns immediately unlessBDMA_ADAPTIVEis true, and it shipped false, so the entire feature was inert on a stock install. The machinery was complete and correct — ATA has its.atasuffix and bothasset_usbd_irx_ata/asset_usbhdfsd_irx_ataembeds — it was one boolean away from working, gated behind a setting users had no reason to know existed. The half-created state (folder +.icn/icon.syspresent, drivers absent) reads as a broken feature rather than an unconfigured one. Verified identical between2270cec(1.1.0) anddev, so this was never a regression — it has never worked out of the box. - Implications: every launch now stages a variant unless the user opts OUT (Settings → Adaptive BDMA), including the exFAT pair on a FAT32 USB stick — that pair reads FAT32 too, which is why one variant serves every stick. The maintainer flagged rumours of games (Soul Blade) disliking any BDMA variant; the mitigation is per-game fixes or 32KB clusters, and opting out remains one toggle.
-bdma=is normalised byNormalizeBdmaModeKey, so an unrecognised value is IGNORED rather than pinning something bogus. Reset-to-defaults inui.luanow restores On. - Evidence:
bin/POPSLDR/system.lua(PLDR.BDMA_ADAPTIVE,PLDR.LaunchArgBdmaMode,ResolveAdaptiveBdmaTarget),src/main.cpp(-bdma=parse),src/luasystem.cpp(lua_getLaunchArgs), harnessT48.