Major fixes for UUID/skin reset issues that caused players to lose cosmetics: Core fixes: - Username rename now preserves UUID (atomic rename, not new identity) - Atomic config writes with backup/recovery system - Case-insensitive UUID lookup with case-preserving storage - Pre-launch validation blocks play if no username configured - Removed saveUsername calls from launch/install flows UUID Modal fixes: - Fixed isCurrent badge showing on wrong user - Added switch identity button to change between saved usernames - Fixed custom UUID input using unsaved DOM username - UUID list now refreshes when player name changes - Enabled copy/paste in custom UUID input field UI/UX improvements: - Added translation keys for switch username functionality - CSS user-select fix for UUID input fields - Allowed Ctrl+V/C/X/A shortcuts in Electron Files: config.js, gameLauncher.js, gameManager.js, playerManager.js, launcher.js, settings.js, main.js, preload.js, style.css, en.json See UUID_BUGS_FIX_PLAN.md for detailed bug list (18 bugs, 16 fixed) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
16 KiB
UUID/Skin Reset Bug Fix Plan
Problem Summary
Players experience random skin/cosmetic resets without intentionally changing anything. The root cause is that the UUID system has multiple failure points that can silently generate new UUIDs or use the wrong UUID during gameplay.
Impact: Players lose their customized cosmetics/skins randomly, causing frustration and confusion.
Status: ✅ FIXED - All critical and high priority bugs have been addressed.
Implementation Summary
What Was Fixed
| Bug | Severity | Status | Description |
|---|---|---|---|
| BUG-001 | Critical | ✅ Fixed | Username not loaded before play click |
| BUG-002 | High | ✅ Fixed | isFirstLaunch() always returns true |
| BUG-003 | Critical | ✅ Fixed | Silent config corruption returns empty object |
| BUG-004 | Critical | ✅ Fixed | Non-atomic config writes |
| BUG-005 | High | ✅ Fixed | Username fallback to 'Player' |
| BUG-006 | Medium | ✅ Fixed | Launch overwrites username every time |
| BUG-007 | Medium | ✅ Fixed | Dual UUID systems (playerManager vs config) |
| BUG-008 | High | ✅ Fixed | Error returns random UUID |
| BUG-009 | Medium | ✅ Fixed | Username case sensitivity |
| BUG-010 | Medium | ⏳ Pending | Migration marks complete on partial failure |
| BUG-011 | Medium | ⏳ Pending | Race condition on concurrent config access |
| BUG-012 | High | ✅ Fixed | UUID modal isCurrent flag broken |
| BUG-013 | High | ✅ Fixed | UUID setting uses unsaved DOM username |
| BUG-014 | Medium | ✅ Fixed | No way to switch between saved identities |
| BUG-015 | High | ✅ Fixed | installGame saves username (overwrites good value) |
| BUG-016 | High | ✅ Fixed | Username rename creates new UUID instead of preserving |
| BUG-017 | Medium | ✅ Fixed | UUID list not refreshing when player name changes |
| BUG-018 | Low | ✅ Fixed | Custom UUID input doesn't allow copy/paste |
User Scenario Analysis
All user scenarios have been analyzed for UUID/username persistence:
| Scenario | Risk | Status | Details |
|---|---|---|---|
| Fresh Install | Low | ✅ Safe | firstLaunch.js reads but doesn't modify username/UUID |
| Username Change | Low | ✅ Safe | Rename preserves UUID, user-initiated saves work correctly |
| Auto-Update | Low | ✅ Safe | Config is on disk before update, backup recovery available |
| Manual Update | Low | ✅ Safe | Config file persists across manual updates |
| Different Install Location | Low | ✅ Safe | Config uses central app directory, not install-relative |
| Repair Game | Low | ✅ Safe | repairGame() doesn't touch config |
| UUID Modal | Low | ✅ Fixed | Fixed isCurrent badge, unsaved username bug, added switch button |
| Profile Switch | Low | ✅ Safe | Profiles only control mods/java, not username/UUID |
| Branch Change | Low | ✅ Safe | Only changes game version, not identity |
Files Modified
| File | Changes |
|---|---|
backend/core/config.js |
Atomic writes, backup/recovery, validation, case-insensitive UUID lookup, checkLaunchReady(), username rename preserves UUID |
backend/managers/gameLauncher.js |
Pre-launch validation, removed saveUsername call |
backend/managers/gameManager.js |
Removed saveUsername call from installGame |
backend/services/playerManager.js |
Marked DEPRECATED, throws on error, retry logic |
backend/launcher.js |
Export new functions (checkLaunchReady, hasUsername, etc.) |
GUI/js/launcher.js |
Uses checkLaunchReady API, blocks launch if no username |
GUI/js/settings.js |
UUID modal fixes, switchToUsername function, proper error handling, refreshes UUID list on name change |
GUI/style.css |
Switch button styling, user-select: text for UUID input |
GUI/locales/en.json |
Added translation keys for switch username functionality |
main.js |
Fixed UUID IPC handlers, added checkLaunchReady handler |
preload.js |
Exposed checkLaunchReady to renderer |
Bug Categories
Category A: Race Conditions & Initialization
Category B: Silent Failures & Fallbacks
Category C: Data Integrity & Persistence
Category D: Design Issues
Category E: UI/UX Issues
Detailed Bug List & Fixes
BUG-001: Username Not Loaded Before Play Click (CRITICAL) ✅ FIXED
Category: A - Race Condition
Location:
GUI/js/launcher.jsGUI/js/settings.js
Problem: If user clicks Play before settings DOM initializes, returns 'Player' silently.
Fix Applied:
- launcher.js now uses
checkLaunchReady()API to validate before launch - Loads username from backend config (single source of truth)
- Blocks launch and shows error if no username configured
- Navigates user to settings page to set username
BUG-002: isFirstLaunch() Always Returns True (HIGH) ✅ FIXED
Category: B - Silent Failure
Location: backend/core/config.js
Problem: Function always returns true even when user has data (typo: return true instead of return false).
Fix Applied:
- Fixed return statement:
return true→return false
BUG-003: Silent Config Corruption Returns Empty Object (CRITICAL) ✅ FIXED
Category: B - Silent Failure
Location: backend/core/config.js
Problem: Corrupted config silently returns {}, causing UUID regeneration.
Fix Applied:
- Added config validation after load
- Implemented backup config system (config.json.bak)
- Tries loading backup if primary fails
- Logs detailed errors for debugging
BUG-004: Non-Atomic Config Writes (CRITICAL) ✅ FIXED
Category: C - Data Integrity
Location: backend/core/config.js
Problem: Direct write can corrupt file if interrupted. Silent error logging.
Fix Applied:
- Atomic write: write to temp file → verify JSON → backup current → rename
- Throws error on save failure (no silent continuation)
- Cleans up temp file on failure
BUG-005: Username Fallback to 'Player' (HIGH) ✅ FIXED
Category: B - Silent Failure
Location: backend/core/config.js
Problem: Missing username silently falls back to 'Player', causing wrong UUID.
Fix Applied:
loadUsername()returnsnullinstead of 'Player'- Added
loadUsernameWithDefault()for display purposes - Added
hasUsername()helper function - All callers updated to handle null case explicitly
BUG-006: Launch Overwrites Username Every Time (MEDIUM) ✅ FIXED
Category: D - Design Issue
Location: backend/managers/gameLauncher.js
Problem: If playerName parameter is wrong, it overwrites the saved username.
Fix Applied:
- Removed
saveUsername()call from launch process - Username only saved when user explicitly changes it in Settings
- Launch loads username from config (single source of truth)
BUG-007: Dual UUID Systems (playerManager vs config) (MEDIUM) ✅ FIXED
Category: D - Design Issue
Location:
backend/services/playerManager.js→player_id.jsonbackend/core/config.js→config.json→userUuids
Problem: Two independent UUID systems can desync.
Fix Applied:
playerManager.jsmarked as DEPRECATED- All code uses
config.jsgetUuidForUser() - Migration function added for legacy
player_id.json
BUG-008: Error Returns Random UUID (HIGH) ✅ FIXED
Category: B - Silent Failure
Location: backend/services/playerManager.js
Problem: Any error generates random UUID, losing player identity.
Fix Applied:
- Now throws error instead of returning random UUID
- Retry logic added (3 attempts before failure)
- Caller must handle the error appropriately
BUG-009: Username Case Sensitivity (MEDIUM) ✅ FIXED
Category: D - Design Issue
Location: backend/core/config.js
Problem: "PlayerOne" and "playerone" are different UUIDs.
Fix Applied:
getUuidForUser()uses case-insensitive lookup- Username stored with ORIGINAL case (preserves "Sanasol", "SaAnAsOl", etc.)
- Lookup normalized to lowercase for matching
- Case changes update the stored key while preserving UUID
BUG-010: Migration Marks Complete Even on Partial Failure (MEDIUM) ⏳ PENDING
Category: C - Data Integrity
Location: backend/utils/userDataMigration.js
Problem: Partial copy is marked as complete, preventing retry.
Status: Not yet implemented - low priority since migration runs once.
BUG-011: Race Condition on Concurrent Config Access (MEDIUM) ⏳ PENDING
Category: A - Race Condition
Location: backend/core/config.js
Problem: No file locking - concurrent processes can overwrite each other.
Status: Not yet implemented - would require proper-lockfile package. Low risk since launcher is single-instance.
BUG-012: UUID Modal isCurrent Flag Broken (HIGH) ✅ FIXED
Category: D - Design Issue
Location: main.js - get-all-uuid-mappings IPC handler
Problem: Case-sensitive comparison between normalized key (lowercase) and current username.
// BROKEN:
isCurrent: username === loadUsername() // "player1" === "Player1" → FALSE
Fix Applied:
- IPC handler now uses
getAllUuidMappingsArray()from config.js - This function correctly compares against normalized username
BUG-013: UUID Setting Uses Unsaved DOM Username (HIGH) ✅ FIXED
Category: B - Silent Failure
Location: GUI/js/settings.js - performSetCustomUuid()
Problem: Gets username from DOM input field instead of saved config.
// BROKEN:
const username = getCurrentPlayerName(); // From UI input, not saved!
Risk Scenario: User types new name but doesn't save → opens UUID modal → sets custom UUID → UUID gets set for unsaved name while config has old name.
Fix Applied:
- Now loads username from backend config via
window.electronAPI.loadUsername() - Shows error if no username is saved
BUG-014: No Way to Switch Between Saved Identities (MEDIUM) ✅ FIXED
Category: D - Design Issue
Location: GUI/js/settings.js - UUID modal
Problem: UUID modal showed list of usernames/UUIDs but no way to switch to a different identity.
Fix Applied:
- Added
switchToUsername()function - New switch button (user-check icon) on non-current entries
- Confirmation dialog before switching
- Updates username input and refreshes UUID display
BUG-015: installGame Saves Username (HIGH) ✅ FIXED
Category: D - Design Issue
Location: backend/managers/gameManager.js - installGame()
Problem: saveUsername(playerName) call could overwrite good username with 'Player' default.
Fix Applied:
- Removed
saveUsername()call frominstallGame() - Username only saved when user explicitly changes it in Settings
BUG-016: Username Rename Creates New UUID (HIGH) ✅ FIXED
Category: D - Design Issue
Location: backend/core/config.js - saveUsername()
Problem: When user changes their player name, a new UUID was generated instead of preserving the existing one. User's identity (cosmetics/skins) was lost on every name change.
Symptom: Change "Player1" to "NewPlayer" → gets completely new UUID → loses all cosmetics.
Fix Applied:
saveUsername()now handles UUID mapping renames atomically- When renaming: old username's UUID is moved to new username
- When switching to existing identity: uses that identity's existing UUID
- Case changes only: updates key casing, preserves UUID
- Both username and userUuids saved in single atomic operation
Behavior After Fix:
// Rename: "Player1" → "NewPlayer"
// Before: Player1=uuid-123, NewPlayer=uuid-NEW (wrong!)
// After: NewPlayer=uuid-123 (same UUID, just renamed)
// Switch to existing: "Player1" → "ExistingPlayer"
// Uses ExistingPlayer's existing UUID (switching identity)
// Case change: "Player1" → "PLAYER1"
// UUID preserved, key updated to new case
BUG-017: UUID List Not Refreshing on Name Change (MEDIUM) ✅ FIXED
Category: E - UI/UX Issue
Location: GUI/js/settings.js - savePlayerName()
Problem: After changing player name in settings, the UUID modal list didn't refresh. The "Current" badge showed on the old username instead of the new one.
Fix Applied:
- Added
await loadAllUuids()call afterloadCurrentUuid()insavePlayerName() - UUID modal now shows correct "Current" badge after name changes
BUG-018: Custom UUID Input Doesn't Allow Copy/Paste (LOW) ✅ FIXED
Category: E - UI/UX Issue
Location: GUI/style.css, GUI/index.html
Problem: The body element has select-none class (Tailwind) which applies user-select: none globally. This prevented copy/paste in the custom UUID input field.
Fix Applied:
- Added
user-select: textwith all vendor prefixes to.uuid-inputclass - Overrides the global selection disable for UUID input fields
Translation Keys Added
The following translation keys were added to GUI/locales/en.json:
{
"notifications": {
"noUsername": "No username configured. Please save your username first.",
"switchUsernameSuccess": "Switched to \"{username}\" successfully!",
"switchUsernameFailed": "Failed to switch username",
"playerNameTooLong": "Player name must be 16 characters or less"
},
"confirm": {
"switchUsernameTitle": "Switch Identity",
"switchUsernameMessage": "Switch to username \"{username}\"? This will change your current player identity.",
"switchUsernameButton": "Switch"
}
}
Testing Checklist
After implementing fixes, verify:
- Launch with freshly installed launcher - UUID persists
- Change username in settings - UUID preserved (renamed, not new)
- Config corruption - recovers from backup
- Click Play immediately after opening - correct UUID used
- Manual update from GitHub - UUID persists
- Username with different casing - same UUID used, case preserved
- UUID modal shows correct "Current" badge
- UUID modal refreshes after username change
- Switch identity from UUID modal works
- Profile switching doesn't affect username/UUID
- Custom UUID input allows copy/paste
Architecture: How UUID/Username Persistence Works
Config Structure (config.json):
{
"username": "CurrentPlayer",
"userUuids": {
"Sanasol": "uuid-123-abc",
"SaAnAsOl": "uuid-456-def",
"Player1": "uuid-789-ghi"
},
"hasLaunchedBefore": true
}
Key Design Decisions:
- Username stored with ORIGINAL case (e.g., "Sanasol", "SaAnAsOl")
- UUID lookup is case-insensitive (normalized to lowercase for matching)
- Username rename preserves UUID (atomic rename operation)
- Profile switching does NOT affect username/UUID (shared globally)
- All config writes use atomic pattern: temp file → verify → backup → rename
- Automatic backup recovery if config corruption detected
Data Flow:
- User sets username in Settings →
saveUsername()handles rename logic → saves to config.json - If renaming: UUID moved from old name to new name (same UUID preserved)
- Launch game →
checkLaunchReady()validates username exists - Launch game →
getUuidForUser(username)gets UUID (case-insensitive lookup) - UUID modal → shows all username→UUID mappings from config
- Switch identity → saves new username → gets that username's UUID
Success Criteria
- ✅ Zero silent UUID regeneration
- ✅ Config corruption recovery working
- ✅ No UUID change without explicit user action
- ✅ Username rename preserves UUID
- ✅ Username case is preserved in display
- ✅ UUID modal correctly identifies current user
- ✅ UUID modal refreshes on changes
- ✅ Users can switch between saved identities
- ✅ Copy/paste works in UUID input
Remaining Work
- BUG-010: Verify migration completeness before marking done (low priority)
- BUG-011: Add file locking with
proper-lockfile(low priority - single instance) - Add telemetry for config load failures and UUID regeneration events
- Add translation keys to other locale files (de-DE, fr-FR, ru-RU, etc.)