POPStarter DOCS

STATE — status, invariants & known issues

← POPSLoader docs · view on GitHub ↗

Last updated: 2026-06-21 (BETA-13 rolling candidate; body reflects the 2026-06-20/21 input/nav + cover-art + overscan + HDD-scan-slot session on top of the 2026-06 HDD-RW take-over / PAL-512 / BDMA-folder work). Released line: BETA-12 (2026-06-18; BETA-11 was 2026-06-15). Active dev branch is now BETA-13-PLAY (created off BETA-12-PLAY; BETA-12-PLAY is frozen/archival); rolling-release.yml publishes from BETA-13-PLAY. BETA-13 is the in-progress rolling candidate — not yet cut. Tip moves per push; see git log.

STATE

This is the canonical status doc for POPSLoader. Current runtime state, behavioral invariants, preservation contracts, known issues, and hardware-verification status all live here; the other docs (README, AGENTS, CONTRIBUTING, ROADMAP, ROLLING_NOTES) point here instead of duplicating, so this is the one place to keep current. QA_REGRESSION_MATRIX.md is the detailed run ledger.

Project Identity

POPSLoader is a PS2 launcher for POPStarter built on Enceladus runtime pieces, with behavior primarily orchestrated by embedded Lua modules (system.lua, ui.lua, images.lua, pops_profiles.lua). The Lua is bin2c'd into the EE ELF at build time — so a runtime Lua error (nil global, type error, load-order error) is invisible to luac -p and to CI, and only surfaces on real PS2 / PCSX2.

Repo-Verified Runtime State

Boot and runtime

Settings (single-device parity)

Per-game hide layer

BDMA mode / POPSTARTER memory-card folder

Video standard

Input / navigation (UI.Pad.Listen in ui.lua)

Boot sound

Overscan (CRT inset)

Boot-context resolution

Launch arguments (NHDDL-style)

Backend init / runtime

Launch paths (current routing)

Exit handoff

Cover art (game-list preview box, ui.lua)

Embedded-asset mechanism

CI / release

Behavioral Invariants (must preserve)

(absorbed from the former TRUTHSHEET.md — invariants that changes must preserve unless an explicit migration is planned)

  1. Boot/runtime Lua is embedded-only (src/luaplayer.cpp, etc/boot.lua, Makefile): the embedded searcher is installed, filesystem Lua loaders are disabled, required Lua blobs are embedded.
  2. Settings persistence is transactional and per-device — including HDD. Edits stage in drafts; CommitSettingsChanges runs on confirm/leave. PLDR.SETTINGS_PATH resolves to the per-device APP_DIR_LOCAL/.pldrs sidecar; HDD installs persist on the HDD boot partition via the EnsureBootPartitionWritable RW take-over (no mc0: fallback). (Supersedes the old HDD-to-MC exception.)
  3. USB vs MX4SIO identity comes from the ioctl driver name; mx4sio_bd loads conditionally. Maintainer rule: if a mass device's ioctl/devctl is anything other than sdc/mx4 it is USB; sdc/mx4 means MX4SIO. usbmass_bd always loads before mx4sio_bd. Pure USB boots never load mx4sio_bd.
  4. Startup backend auto-init is path-driven — boot source plus configured POPSTARTER/DKWDRV/profile paths drive which backends init before the first page visit.
  5. Runtime device selection is not hard-locked — the old runtime device-lock subsystem (canEnterDevice/setDeviceLock) was removed (commits a3e04b8, cef61af); any device page can be entered at runtime.
  6. Probe/retry loops are bounded — finite attempt counts and fixed phases (no frame stalls/hangs).
  7. Launch failure feedback must be explicit — missing POPStarter/DKWDRV paths and launch-return failures produce user-visible notifications/screens.
  8. Release package manifest is strict — CI enforces the exact ZIP set and rejects legacy POPS/*.tm2 entries.
  9. BDMA ⟺ POPSTARTER-MC-folder interlock — BDMA can't be enabled while the POPSTARTER MC folder is off; the folder can't be disabled while BDMA is on.
  10. HDD .hide is in-app on every device — the <name>.hide per-game marker is written/removed in-app via the L3 toggle on all device pages including HDD via the RW mount take-over.
  11. Per-frame UI timing is frame-counted, not wall-clockTimer.getTime() is microseconds on PS2, so nav auto-repeat and description scroll count frames (the canonical Enceladus idiom), not the clock. New time-based UI rate-limits must frame-count (or use os.clock() seconds), never treat getTime() as ms.
  12. The analog-stick → d-pad fold must stay gated on Pads.getMode() being analog/DualShock — an ungated fold injects a phantom −127 on a digital pad and breaks up/down nav. Pads.getMode() (PAD_MODECURID, live mode) is the correct source; Pads.getType() (PAD_MODETABLE) is not.
  13. Embedded assets are wired in 3 explicit coordinated places — Makefile (BIN2S + EMBEDDED_RSC), src/embed_assets.cpp (extern + ASSET_ENTRY in both lookup tables), bin/POPSLDR/images.lua (IMG_REGISTRATIONS, bare-filename key). Adding/removing an asset that touches fewer than all three is a build or runtime break.

Intentionally not implemented (must keep reporting that status until feature work lands): HDD (exFAT), SMB (v1), ILINK.

Preservation Contracts (hardware-load-bearing — do NOT regress)

See docs/PRESERVATION_CONTRACTS.md for the detailed code-level contract specs — exact path:line citations, what-breaks-it for each, and how to retest on hardware.
- D-10 HDD POPSTARTER + HDD game — B2 fix 4ae6679 (PFS unmount before ExecPS2).
- D-14 HDD POPSTARTER + non-HDD game — same partition-aware route.
- D-15 non-HDD POPSTARTER + HDD game — keep-mask preserves the boot partition's PFS slot.
- DKWDRV from MC — reboot variant direct path with argv0 synthesis.
- BOOT.ELF from USB-booted POPSLoader (L-07) — V2 route at d23520a.
- EnsureBootPartitionWritable (boot pfs-slot unmount→remount-RW take-over) — now load-bearing for HDD settings save and HDD in-app .hide; any launch-path / mount change must not break it.

Reported Hardware Status

Case Last result Date Notes
D-10 HDD POPSTARTER + HDD game PASS (contract) 2026-05-22, reconfirmed 2026-05-28 (Nuno) B2 fix 4ae6679. Must be preserved.
D-14 HDD POPSTARTER + non-HDD game PASS (contract) 2026-05-22 Same route as D-10.
D-15 non-HDD POPSTARTER + HDD game PASS (contract) 2026-05-22 Keep-mask.
DKWDRV from MC PASS (contract) 2026-05-25, reconfirmed 2026-05-28 (Nuno) Reboot variant + argv0 synthesis.
DKWDRV from HDD custom path PASS (resolved) 2026-06-04/06-06 (Nuno) PRs #486/#487. Was known-broken through BETA-10-5.
BOOT.ELF from USB-booted POPSLoader (L-07) PASS 2026-05-28 (Nuno) V2 route d23520a.
BOOT.ELF from HDD-launched POPSLoader (U-10) PASS (resolved) 2026-05-31 (Nuno) PR #479 (reboot_iop=0).
HOSDmenu → POPSLoader (Class A start) PASS (resolved) maintainer 2026-06-15 Mechanism not pinned; reverify if it regresses.
wLaunchELF → POPSLoader (Class A start, some builds) PASS (resolved) maintainer 2026-06-15 PR #458 Layer A + remaining builds confirmed.
PSBBN / Browser / HOSDMenu / OSDMenu → POPSLoader PASS (contract) CosmicScale 2026-05-25 + Nuno 2026-05-28
Settings save on USB / MC-installed POPSLoader PASS 2026-05-27 (Nuno) Per-device APP_DIR/.pldrs.
HDD is RW-writable on real hardware PASS provato 2026-06 Confirmed the boot-partition RW take-over works; full HDD settings/.hide flow still validating.
HDD-resident settings save + in-app .hide Implemented / boots on PCSX2 2026-06-17 Validating on hardware. Not yet broadly hardware-confirmed.
PAL native 640×512 full-screen render Implemented / boots on PCSX2 2026-06-17 PAL hardware validation pending.
U-06 PAL/NTSC asset proportions Targets the new PAL-512 render Verify the full-screen fill + auto-revert confirm on PAL hardware.
D-12 startup backend auto-init PASS 2026-03-28
D-16 first-entry USB backend discovery PASS after 2026-03-27
Up/down + analog-stick nav (frame-counted repeat; analog fold gated) PASS 2026-06-20 (oldman63) Lands on individual items; continuous scroll fine.
Boot sound On/Off save PASS 2026-06-20 (oldman63) Saves and survives reboot.
Overall latest rolling PASS ("everything working fantastically") 2026-06-21 (Nuno6573) General confirmation, not item-by-item.
Overscan (CRT inset) Implemented / boots on PCSX2 2026-06-20 Not yet CRT/HW-eyeballed.
Cover-art layering (cover_default + cover_missing overlay) Implemented / boots on PCSX2 2026-06-20 Eyeball that both register inside the jewel-case frame on NTSC + PAL.
HDD scan steered off the boot pfs slot (Proposal A) Implemented / boots on PCSX2 2026-06-20 b159a43. Wants a deliberate HW test that game partitions still mount/list off the boot slot.

Known Issues (canonical — the single list; README / AGENTS / ROLLING_NOTES point here)

Open:
- "Failed to load HDD" from a non-HDD boot (config-specific; Nuno 2026-06-14) — when POPSLoader is launched from a non-HDD device (USB / MC) via a launcher, a specific configuration faults while building the HDD game list (most setups list the HDD fine). POPSLoader itself starts normally. Workaround: boot POPSLoader from the HDD, or open the HDD page a few seconds after the menu. Instrument + isolate; do not assert a cause from source — bare-reset hardware disproved the #490 theory. (Distinct from the fixed second-boot cache crash below.)

In testing on hardware (implemented + boots on PCSX2; not yet broadly hardware-confirmed — these are what the current rolling build asks testers to verify):
- HDD in-app .hide (L3 toggle; unhide via Settings → Game List → Hidden games).
- R3 reveal/hide on a device game list — toggles + persists GLOBAL_HIDE and rebuilds the list in place (reuses the R1 refresh path).
- HDD-resident settings save (boot-partition RW take-over; provato confirmed the HDD is RW-writable).
- PAL native 640×512 full-screen render + auto-revert display-change confirm.
- POPSTARTER Memory Card Folder toggle + the BDMA interlock.
- Overscan (CRT inset) — eyeball the inset on a real CRT.
- Cover-art layering (cover_default + cover_missing overlay) — eyeball that both register inside the jewel-case frame on NTSC + PAL.
- HDD scan steered off the boot pfs slot (Proposal A, b159a43) — deliberate HW test that game partitions still mount/list off the boot slot.

Recently resolved:
- Nav auto-repeat flew / all desc-scroll speeds felt the sameTimer.getTime() is µs not ms, so wall-clock gates were sub-frame; nav auto-repeat and description scroll are now frame-counted and the speed setting works. Up/down + analog-stick nav and boot-sound save are HW-confirmed (oldman63, 2026-06-20).
- Phantom analog input broke up/down nav — the analog-stick → d-pad fold is now gated on Pads.getMode() being analog/DualShock with per-axis hysteresis. HW-confirmed (oldman63).
- HDD settings save failed after a game scan ("...may be read-only") — a game scan borrowed the boot pfs slot and a never-cleared RW flag stranded the save path; fixed 8d1e67a (liveness-validate the boot RW mount via doesFolderExist on the save path) and b159a43 (Proposal A: steer the scan to non-boot slots). The latter still wants a deliberate HW test.
- MISSING.png cover placeholder replaced by the cover_default + cover_missing layer; MISSING.png removed (~−62 KB ELF).
- Codex BETA-13 audit — 6 findings, all verified real and fixed (ec81de3): PromoteTmpToDest now requires its backup before truncating dest; BMP pixel-size/stride validation; PNG dimension cap; stale mc0: probe cleanup; two System.writeFile full-byte-count checks; R3 no success-toast on a failed save. Report: docs/AUDIT_CODEX_2026-06-20.md.
- "Failed to load HDD" on the second boot (cache/loadfile crash) — fixed; the HDD list loads every boot, and a real error string now surfaces if it ever fails.
- Load-order boot brickPLDR.HDD methods were defined before PLDR.HDD existed, which made the recent HDD-feature rolling builds un-bootable; fixed d4b04be (2026-06-17). (Invisible to luac -p/CI; only fatal at runtime.)
- U-10 BOOT.ELF-from-HDD-boot — PR #479. DKWDRV from a custom HDD path — PRs #486/#487. Class-A HOSDmenu / some-wLE start failures — maintainer-confirmed 2026-06-15. MX4SIO-rooted settings save — PR #477.

Investigation artifacts archived under docs/archive/: U10_INVESTIGATION.md, LAUNCH_HYGIENE.md, HDD_POPSTARTER_HANDOFF.md.

Known Open Work

  1. Settings UI redesign (Berion mockup) — gated on the outstanding hardware verification (D-10/D-14/U-10 plus the new HDD/PAL features) settling, and on the mockup PNGs landing.
  2. "Failed to load HDD" from a non-HDD / via-launcher boot — the remaining open launch-adjacent issue. Instrument + isolate.
  3. Layer C full lazy IRX loading — only the device-hint precursor shipped; aggressive deferrals (mmceman unless MMCE, ds34bt unless BT, usbd unless USB) queued for a separate PR. High reward for boot time; high risk to input availability.
  4. os.clock() sweep for the remaining µs-as-ms timers — nav and description scroll are now frame-counted, but the MIN_ACTION_MS action debounce and the transition/carousel timers still read Timer.getTime() as ms (µs in reality), currently masked by a per-frame max-step clamp. Convert them to os.clock() seconds (or frame-counting) for unit-correct timing.
  5. HDD (exFAT), SMB (v1), ILINK — intentionally unimplemented.

(The old "ps2hdd-osd.irxps2hdd.irx driver swap probe" item is removed: HDD read-write was achieved instead via the EnsureBootPartitionWritable boot-partition remount take-over, and provato confirmed the HDD is RW-writable on hardware — the IRX swap is no longer the gating path.)

Verification Status