Reference · Updated 2552.09.10.16.15
Profiles — PIE Checklist
The registry logic has 7 automation tests. The wiring between the registry and the per-player subsystem has none — it needs a real UGameInstance, and the headless harness builds a
The registry logic has 7 automation tests. The wiring between the registry and the per-player subsystem has none — it needs a real UGameInstance, and the headless harness builds a ULocalPlayer without one, so those paths never execute in tests. Everything below is what the tests cannot reach.
Run in PIE or Standalone. Reference: PlayerProfiles.md §8.
#⚠ PIE's "Number of Players" is the wrong tool
Each PIE client is its own UGameInstance, so each gets its own registry and claims never interact — every player would happily load the same profile and the test would pass while proving nothing.
Split-screen means extra local players in one game instance. Use the console:
DebugCreatePlayer 1 // add a second local player
DebugRemovePlayer 1 // remove them
#Setup
There is no picker yet, so creating profiles needs a temporary Blueprint hookup — one debug key on the MainMenu level Blueprint is enough:
⚠ Two subsystems, and they own different things. The registry (game instance) owns the profile names; USLPlayerProfileSubsystem (local player) owns the values and does the loading. Load Profile is on the second one — looking for it on the registry is a dead end.
| Node | Target | Gets you |
|---|---|---|
Get Profile Registry Subsystem | world context | Create Profile, Get Profile Names, Delete Profile |
Get For Player Controller ← Get Player Controller (0) | player controller | Load Profile, Get Active Profile Name |
⚠ Get Player Profile Subsystem (Widget) does NOT work in a Level Blueprint. It is DefaultToSelf on a widget, so it only resolves inside a Widget Blueprint. In a Level BP use Get For Player Controller.
Then drag off the subsystem return pin to find Load Profile — context-sensitive search hides it until there is a target of that class, which reads exactly like the function not existing.
Print Load Profile's bool return. That is the refusal signal for checks 6 and 8.
Reset to a clean state by deleting Saved/SaveGames/SLProfileIndex.sav and SLPlayerProfile_*.sav. The next boot then generates a fresh profile, which is check 1. That folder is also how you confirm what actually reached disk.
#Checks
| # | Do | Expect | Failure looks like |
|---|---|---|---|
| 1 | Fresh state, boot | A profile is generated from the name pool and loaded; the list shows exactly that one | Empty list, or a profile called Default (that model is gone) |
| 2 | CreateProfile("Beepers") | Returns Valid; SLPlayerProfile_Beepers.sav appears immediately, before any setting change | No file until you change a setting → it will be culled on next boot |
| 3 | LoadProfile("Beepers"), change FOV, quit, reboot | Boots into Beepers with the FOV you set | Boots into Default → last-used isn't persisting |
| 4 | Reboot after deleting SLPlayerProfile_Beepers.sav by hand | Boots into a profile that exists (generating one if that was the last); Beepers gone from the list | Boots into Beepers with empty settings → reconcile isn't running |
| 5 | DebugCreatePlayer 1 while P1 is on Beepers | P2 lands on a different profile | P2 on Beepers → both write one slot; this is the bug claiming exists to stop |
| 6 | P2 LoadProfile("Beepers") | Returns false, P2 stays put | Returns true → the claim check isn't firing |
| 7 | Change sensitivity on P2, then on P1 | Neither moves the other; both survive a reboot | One overwrites the other |
| 8 | DebugRemovePlayer 1, then P1 LoadProfile the profile P2 held | Succeeds | Refused → release isn't happening on player removal |
| 9 | P2 (not primary) loads a profile, quit, reboot | Boots into P1's last profile, not P2's | Guest choice is overwriting the primary's resume |
#Notes
- Check 2 is the subtle one.
LoadProfileInternalonly ever creates profiles in memory, soCreateProfile
writes the slot itself. If that regresses, a new profile survives the session and disappears at next boot — which reads as a save bug, not a creation bug.
- Nothing is reserved. Every profile can be renamed and deleted, including a generated one. Deleting the
last profile is fine — the next boot generates a new one.
- Set the name table first: Project Settings ▸ System Link ▸ System Link Profiles ▸
ProfileNameTable.
Without it, generated names fall back to "Player 2", "Player 3" — working, just dull.
- Checks 5–9 are the whole point of the branch. 1–4 mostly re-confirm what the automation tests cover.
#Done when
5 through 9 pass with two pads. That is MenusAndOnline.md §9's test gate for the profile half, and it is what makes the picker (step 3) worth building.