Skip to content

Add isolated Classic Mode support (🐑) - #184

Open
ccccxp wants to merge 75 commits into
Alban1911:mainfrom
ccccxp:main
Open

Add isolated Classic Mode support (🐑)#184
ccccxp wants to merge 75 commits into
Alban1911:mainfrom
ccccxp:main

Conversation

@ccccxp

@ccccxp ccccxp commented Aug 4, 2026

Copy link
Copy Markdown

About this PR

I've been playing a lot of Classic Mode lately and, honestly, I've gotten a little obsessed with it. As a long-time League player, I really enjoy seeing and playing with the older skins again.

I wanted to use them with Rose, so I spent some time adapting the mode properly. This PR adds Classic Mode support while keeping it separate from regular games. Hopefully, it can help move Rose's Classic Mode support forward. I'd also like to bring back a few more legacy skin models in the future.

What's changed

  • Added automatic Classic Mode detection.
  • Added separate Classic carousel, chroma, history, and random-selection plug-ins.
  • Added isolated state, history, and per-champion random preferences for Classic Mode.
  • Added validated carrier-skin handling, ownership checks, and local visual projection.
  • Added isolated Classic resource downloading, package resolution, and injection.
  • Added stale-selection protection during final lock and injection preparation.

Implementation and review notes

  • ROSE-ClassicWheel ports Catcat's validated JADE adapter for Riot's native skin-card carousel. It is not based on ROSE-CustomWheel, which manages third-party mods.
  • ROSE-ClassicChroma, ROSE-ClassicHistoric, and ROSE-ClassicRandom port Catcat's isolated JADE controls as Classic counterparts to Rose's regular chroma, history, and random features. They preserve the corresponding behavior and bridge contracts, but they are not source-level forks of the regular plug-ins.
  • The existing regular plug-ins remain unchanged. Classic state, controls, resources, and history are kept separate from regular matches.
  • I rebuilt the commit history into smaller, feature-focused commits with detailed descriptions so the changes to existing components can be reviewed independently.

Demo video

rose-classic-test_2.mp4

The video shows how the native carousel, chromas, history, random selection, splash-art projection, and final skin injection work in Classic Mode.

Screenshots

Classic Mode skin carousel Classic skin in game

A note about the skin resources

Classic Mode uses different carrier skins, so simply replacing a few files was not enough. I rebuilt the packages with the required assets and redirected the model, weapon, animation, and VFX references to the correct carriers.

The corresponding resource changes are available in Alban1911/LeagueSkins#94.

There may still be a few rough edges, but the full flow is working on my side.

@ccccxp ccccxp changed the title Feature: Add isolated Classic Mode support (🐑) Add isolated Classic Mode support (🐑) Aug 4, 2026
ccccxp added 29 commits August 5, 2026 09:28
Introduce pure conversions between prime champion IDs, JADE champion IDs, raw LCU skin IDs, resource skin IDs, and carrier skin numbers.

Add the versioned 60-champion fallback carrier matrix covering Skin0, Skin301, and Kayle Skin302.
Add normalized mode, prime/mode champion IDs, carrier IDs, projected visual IDs, ownership, history, randomization, catalog, and selection-generation fields to SharedState.

Reset every Classic-only field together so a finished JADE session cannot leak state into the next match.
Prefer gameMode=JADE and use queue 3260 or map 453 as compatibility fallbacks.

Preserve the existing mode detector for regular games and publish one normalized mode decision to shared state.
Extend the existing champion-selection reset boundary to discard the cached Classic catalog, carrier, projection, and mode-specific selection state before the next session.
Reset the detected JADE mode, map, queue, and Classic session cache when the phase handler exits a Classic session. Leave regular-mode phase cleanup unchanged.
Add the minimal LCU client accessor required to validate whether a Classic selection may remain server-visible.
Allow Classic writes only for a skin owned by the current mode champion.

Reject synthetic projected IDs and cross-champion IDs before they reach the LCU selection endpoint, while retaining the regular write path unchanged.
Pass SharedState into LCUSkinSelection so ownership and current Classic champion checks are available at the final write boundary.
Convert JADE champion aliases to their resource champion IDs when reading hover, current selection, and completed pick actions. This keeps the polling thread keyed to the same champion assets as regular mode.
Normalize champion IDs returned by compute_locked before comparing lock maps, so JADE aliases do not create separate lock identities or bypass the existing lock transition flow.
Move local-player skin extraction out of the champion-session loop into _update_selected_skin while preserving the regular selection and base-skin confirmation behavior. Record the raw LCU selection separately for later mode-specific handling.
For JADE sessions, validate the raw mode skin and locked champion before projecting it into the regular resource ID domain.

Fetch the current pickable-skin catalog when the mode champion changes, resolve and cache a valid LCU carrier, classify the projected selection against owned skins, and leave the regular-mode branch unchanged.
Extend the ID helpers with catalog normalization and carrier validation.

Prefer a current champion-owned LCU carrier when valid, then fall back to the versioned matrix without accepting unrelated regular skins.
Add separate raw and resource ID catalog sets plus visual raw-skin tracking to SharedState.

These fields support message validation without mixing the catalog into regular selection state.
Route versioned Classic catalog messages separately from regular bridge traffic.

Validate mode identity, prime and mode champion mapping, catalog skin IDs, carrier metadata, and optional carrier skin number before updating shared Classic state. Reject malformed catalogs and catalogs for a different locked champion.
Register Classic skin-selection messages without changing the regular selection handler.

Validate each raw skin against the cached catalog and champion mapping. Keep owned selections on the official path; retain unowned visual targets locally, restore the validated carrier in LCU, and publish the projected skin state to the UI and injection pipeline.
Route chroma messages through a Classic-only handler while leaving the regular chroma path unchanged.

Rebuild each chroma event as a versioned skin projection using the cached champion, carrier, and catalog contract, then retain the selected Classic chroma for local injection and UI broadcasting.
Add a neutral preference store keyed by normalized mode and prime champion ID.

Enable or clear Classic randomization without overwriting the existing regular-mode preference.
Add separate Classic files for exact skin and chroma targets, select the history scope from shared state, and extend target load, read, write, and clear operations with a regular-by-default scope.

The existing regular target file and callers retain their previous behavior.
Extend primary history load, read, write, and clear operations with the same regular-by-default scope and store Classic entries in historic_classic.json.

Clearing an entry now clears its exact target in the matching scope, preventing Classic history from altering regular match restoration.
Bypass the regular base-skin forcing sequence in JADE and select candidates only from the validated Classic resource catalog.

Exclude the champion base skin and current carrier, resolve display names from the existing cache, and preserve the regular skin and chroma candidate path unchanged.
Persist random-mode enablement per locked champion and restore it through activate_persisted.

When JADE randomization succeeds, store both resource and mode-native skin IDs plus UI projection state; when disabled, clear only the Classic random flag while retaining regular random behavior.
After accepting the first valid JADE catalog, read the locked champion's persisted random preference and activate Classic randomization only when it is enabled and not already running.

Return after activation so the same catalog event cannot also restore a competing selection mode.
On the first accepted JADE catalog, read the champion's Classic history entry and restore it only when the resource skin still exists in the validated catalog.

Mark history detection complete after the attempt and broadcast state only for a valid restored selection.
When a user initiates a Classic skin selection, clear the active regular-facing and Classic history projection fields, mark automatic restoration complete, and broadcast the cleared history state.
Capture the active history scope before the asynchronous skin injection starts and write the resulting skin ID to the matching regular or Classic history file.

Keeping the scope outside the worker prevents a later phase change from redirecting the completed injection into the wrong history.
Apply the active history scope when custom-mod injection records a mod path, exact target, or base skin fallback, keeping all related history values in the same regular or Classic namespace.
Point the default skin source at ccccxp/LeagueSkins and derive both GitHub API and raw-content endpoints from the configured repository URL.

This removes the previous mismatch where changing repo_url still queried Alban1911/LeagueSkins for metadata and files.
Map classic/ repository paths into a sibling local Classic directory while retaining the existing skins/ and resources/ roots.

Bound empty-directory cleanup to the matched resource root, normalize Classic paths during stale-file cleanup, and preserve downloader state files from repository-content deletion.
ccccxp added 3 commits August 11, 2026 16:56
Read the ClassicWheel projection for the history marker and target card instead of treating the transient native carrier as the restored skin.

Validation:
- Verified on a live test machine: restored history targets centered and remained visible during navigation.
- The completed flow displayed one history notice.

Review notes:
- Confirmed user navigation must release automatic history presentation.
- Third-party Mod history feedback is intentionally excluded.

Refines: 97cad9c

Refines: 3004388

Refines: 2592c9f
Anchor the Classic random control and marker to the selected ClassicWheel card, and keep the displayed state synchronized with the backend random preference.

Validation:
- The final random workflow was verified on a live test machine.
- The ClassicRandom plug-in passed JavaScript syntax checks.

Review notes:
- This commit establishes the front-end state contract; later commits isolate persisted state and delay target projection.
- Projected carrier updates must not be treated as manual cancellation.

Refines: 6d8a8af

Refines: a0b7ebf
Keep the Classic catalog, projected target, LCU carrier, chroma, history,
and random state in one shared selection contract. Injection now consumes a
normalized snapshot instead of inferring the target again from transient
front-end card state.

This removes races where the carousel displayed the intended skin while the
injection thread still consumed an older carrier or selection.

Validation:
- The integrated development snapshot passed 31 existing pytest tests.
- Verified on a live test machine through final Classic skin injection.

Review notes:
- Third-party Mod selection, carrier composition, and Mod history state are
  intentionally excluded from this refactor.
- Injection must consume the accepted selection generation, not live card UI.

Refines: c002bb6
Refines: e134850
Refines: f34f298
@ccccxp

ccccxp commented Aug 11, 2026

Copy link
Copy Markdown
Author

I'm hoping this is the last big update for this PR, and I'm sorry for turning
the review into a moving target for a while. I spent my spare time over the
last few days untangling and refactoring the Classic Mode implementation, then
put it through a fairly large round of live-machine testing. It should be much
more mature and stable now. The previous version worked, but the code had
started to look like a bowl of spaghetti held together by increasingly
optimistic assumptions.

  • Reworked ClassicWheel around Riot's native carousel and kept the Classic catalog within finite navigation boundaries.
  • Unified the Classic ID, carrier, projected-selection, history, chroma, and random state flow.
  • Fixed history/random conflicts, delayed random projection until the final countdown, and added random Classic chromas.
  • Preserved the target splash art after automatic navigation passes through an owned skin.
  • Added clearer Classic log tags and injection checkpoints.
  • Reorganized the new work into small, review-focused commits. Each major commit uses Refines: to reference the related commits already present in this GitHub PR.

The carousel, history, random selection, random chromas, splash-art restoration, and final injection paths have been verified on a live test machine.

I am also experimenting with full third-party Mod-loading compatibility for Classic Mode. That work has not been formally validated and is intentionally excluded from this commit series for now.

ccccxp added 14 commits August 11, 2026 17:25
Route Classic browser logs through plugin-log while retaining chroma-log
compatibility. Add searchable checkpoints for catalog loading, selection,
chroma, history, random state, LCU state, game start, and final injection.

Keep this diagnostic change separate from the behavior refactors. Logging for
the excluded experimental third-party Mod plug-ins is not included.

Validation:
- Verified on a live test machine: the new tags identified sampled random
  targets, LCU carriers, projected selections, and final injection IDs.
- Log checkpoints were used during the completed carousel and random tests.

Review notes:
- Keep Classic tags distinct from regular-mode logs.
- Logging for experimental third-party Mod plug-ins is intentionally excluded.

Refines: 34a74e1
Refines: dcc2111
Refines: 97cad9c
Refines: 6d8a8af
Use Riot's native skin carousel as the only visible Classic carousel.
ClassicWheel now loads the Classic catalog, clamps its finite boundaries,
drives native left and right navigation, and publishes the normalized target.

Remove the earlier full-card replacement path that caused black side cards,
disappearing cards, enlarged layouts, and default-skin flashes. Native card
size, focus, animation, and arrow behavior remain owned by LCU.

Validation:
- Verified on a live test machine: history targets centered correctly and
  remained navigable within the Classic catalog boundaries.
- No black or disappearing side cards were observed after the refactor.

Review notes:
- Riot's native cards remain the source of layout, focus, and animation.
- Client DOM changes may require selector updates; do not add a second carousel
  as a fallback.

Refines: 34a74e1
Centralize the JADE 600-offset boundary so raw LCU IDs are used only for
mode and carrier interaction, while resource IDs are used for catalogs,
names, packages, and injection.

Validate the default carrier at the write boundary and reject projected or
cross-champion IDs before they reach LCU. This removes the scattered ID
guesses that previously caused skin0 fallback, incorrect champion binding,
and default-skin rollback.

Validation:
- Verified on a live test machine: Classic targets resolved and injected
  without treating the 600 offset as a resource directory.
- Affected Python modules passed compilation checks.

Review notes:
- Keep raw LCU IDs, resource IDs, and carrier IDs in their declared domains.
- Do not reintroduce per-caller 600-offset arithmetic.

Refines: 35bbdb0
Refines: ce72386
Refines: 7479b98
Refines: 89de0d3
Refines: 198f175
Refines: c002bb6
Separate the server-visible owned carrier from the projected Classic visual
target. Native navigation may still pass through owned skins to trigger LCU
splash transitions, but read and WebSocket projection preserve the intended
target instead of adopting an intermediate owned skin.

Validation:
- Verified on a live test machine: automatic navigation through an owned
  skin restored the final Classic target splash art.
- Final injection continued to use the intended Classic resource target.

Review notes:
- The owned carrier and projected visual target must remain separate values.
- Passing through an owned card must not publish it as a user-selected target.

Refines: 34a74e1
Refines: 7479b98
Refines: f34f298
Preserve the projected Classic resource target when history reaches the
injection boundary. Do not let the injection thread replace it with the
temporarily visible LCU carrier, and resolve the injection label from the
resource target.

Third-party Mod history and feedback are intentionally excluded.

Validation:
- Verified on a live test machine: entering from Classic history injected
  the restored target instead of a previously owned carrier skin.

Review notes:
- Third-party Mod history and feedback are intentionally excluded.
- Preserve the projected resource target until injection preparation consumes
  the accepted selection snapshot.
Refines: 97cad9c
Refines: 3004388
Refines: c1e4c88
Add explicit Classic game-start checkpoints for the projected target, raw LCU selection, carrier, history state, and final injection decision.

Keep these messages under the existing Classic log prefixes so they remain separate from regular-mode diagnostics.

Validation:
- Verified on a live test machine: these checkpoints identified the carrier, target, and final injection ID during game start.
- The affected Python modules passed compilation checks.

Review notes:
- This commit refines the earlier logging isolation and does not change selection behavior.
- Experimental third-party Mod logging is intentionally excluded.

Refines: 34a74e1

Refines: 97cad9c
Restore click audio and local preview fallback behavior for Classic chroma choices while keeping injection IDs sourced from the ClassicWheel selection contract.

Validation:
- The ClassicChroma plug-in passed JavaScript syntax checks.
- Random chroma targets were verified on a live test machine through final package resolution.

Review notes:
- Local preview fallback is presentation-only and must not alter injection IDs.
- Classic-specific Forms remain deferred.

Refines: dcc2111

Refines: e134850
Normalize Classic party champion and skin IDs into the resource domain before
package lookup. Resolve JADE peer skins from the isolated Classic library while
leaving the regular party path unchanged.

Validation:
- The affected Python module passed compilation checks.
- No formal live-machine validation has been completed for Classic party
  injection; keep this limitation visible during review.

Review notes:
- Classic party injection remains unverified on a live machine.
- Regular party package resolution must remain unchanged outside JADE.

Refines: 6f73d1b
Refines: 35bbdb0
Start, stop, replay, and clear Classic chroma rendering with the wheel lifecycle so stale parent or chroma state cannot remain attached to a new target.

Validation:
- The final Classic JavaScript plug-ins passed syntax checks.
- Verified on a live test machine: a random Classic chroma reached final injection.

Review notes:
- Clear stale parent and chroma state whenever the wheel target changes.
- Keep the ClassicWheel replay event as the single restoration source.

Refines: dcc2111

Refines: e134850
Keep Classic random enablement in its own persisted state so history
projection and automatic card movement cannot cancel it. The dice control and
card marker now reflect random mode itself, while a confirmed manual selection
uses one shared cancellation path.

Validation:
- Verified on a live test machine: persisted random mode remained active when
  returning to the same champion and selected a target before game start.
- History and random indicators no longer remained active together.

Review notes:
- Only a confirmed manual skin selection should cancel persisted random mode.
- Projected or carrier-only skin updates must not reset the random preference.

Refines: 6d8a8af
Refines: 6d091be
Refines: a0b7ebf
Refines: 543a189
Do not move the carousel when Classic random mode is enabled. Project the
sampled target during the stable final-countdown window, matching the regular
random workflow and avoiding competition with history presentation.

Validation:
- Verified on a live test machine: enabling random mode did not immediately
  change the selected card, and the sampled target was applied before launch.
- The previously tested early-projection suppression remains unchanged.

Review notes:
- Keep the early-projection suppression that was verified on the live test
  machine; removing it reintroduces history/random indicator conflicts.
- Projection remains tied to the stable final-countdown window.

Refines: 6d8a8af
Refines: a0b7ebf
Choose a Classic parent skin first, then choose between that parent and its
eligible chromas. This preserves the regular random-mode probability model
and avoids overweighting skins that happen to have more chromas.

Reset the selected chroma state when the sampled result is a base skin.

Validation:
- Verified on a live test machine: Classic random mode selected chroma
  variants and resolved their nested packages for final injection.
- Affected Python modules passed compilation checks.

Review notes:
- Select the parent skin first, then choose among that parent and its eligible
  chromas; flattening all chromas would bias the distribution.
- Reset chroma state when the sampled result is the parent skin.

Refines: 6d8a8af
Refines: f26a0b8
Refines: a9d0cad
Keep target splash protection active after automatic projection settles.
Release it only for real user navigation, a context exit, or an explicit new
target. This prevents LCU's final automatic writeback from being mistaken for
a user choice and restoring an intermediate skin splash.

Validation:
- Verified on a live test machine after automatic history and random
  projection; the target splash remained stable.
- The final behavior was manually confirmed as correct.

Review notes:
- Release visual protection only for confirmed user navigation, context exit,
  or an explicit replacement target.
- Do not treat LCU's projection-settle writeback as user input.

Refines: 34a74e1
Replace the earlier carrier matrix description with the implemented JADE boundary: one normalized resource ID namespace, a live-catalog carrier, Riot's native finite carousel, and a separate projected target.

Document the stable history, random, chroma, resource, injection, and diagnostic flows. Keep experimental third-party Mod loading explicitly outside this commit series.

Validation:
- Documentation was checked against the final stable diff and live-machine validation record.
- Classic party lookup is identified as compilation-checked but not formally validated on a live machine.

Review notes:
- Third-party Mod plug-ins, Mod history isolation, and category compatibility are intentionally excluded.
- Classic Forms remain deferred because no required target is confirmed in the current catalog.

Refines: 00f950f
ccccxp added 4 commits August 11, 2026 17:46
Scope global Mod history and active Mod selections to regular or Classic sessions. Drop mismatched in-memory selections before listing, restoring, or injecting Mods so a regular selection cannot leak into JADE and a Classic selection cannot leak back.

Reuse the existing historic_scope_for_state boundary for skin, map, font, announcer, and category Mod persistence while preserving the regular file format and default scope.

Validation:
- Affected Python modules passed compilation checks in the integrated tree.
- Existing tests are run after the complete disabled-front-end integration.
- Classic third-party Mod loading has not been formally validated on a live machine.

Review notes:
- This commit adds state consistency and cross-mode isolation only.
- The Classic Mod JavaScript entry points remain disabled by default.
- Regular Mod history continues to use the existing files and behavior.

Refines: f785763d8f31a94c495023e6b0078217ce3e8edf
Refines: 58becbea7f23c34c8a5bdca2eeb26035d71489a0
Refines: f1b48e4e4cf63e893ec436b4136ed6b5bd7d15b2
Add the Classic counterpart of ROSE-CustomWheel with skin, font, announcer, UI, voiceover, loading-screen, VFX, SFX, and other category support wired to ClassicWheel's normalized selection contract.

Ship the entry point as index.js_ so Pengu Loader discovers the component but keeps it disabled. Hide the regular CustomWheel during JADE sessions and retain ClassicHistoric feedback for a future enabled Classic Mod selection.

Validation:
- The disabled ClassicMods source and affected active plug-ins passed JavaScript syntax checks.
- Pengu Loader's native index.js_ discovery path was verified in the vendored loader source.
- Classic third-party Mod loading and category assets have not been formally validated on a live machine.

Review notes:
- ROSE-ClassicMods is installed but disabled by default; this commit does not expose its button or panel.
- Map, font, announcer, and category compatibility must be validated before enabling the entry point.
- The regular CustomWheel remains unchanged outside JADE.

Refines: f1b48e4e4cf63e893ec436b4136ed6b5bd7d15b2
Add the Classic counterpart of ROSE-CustomSkinSelector. Resolve the active champion, parent skin, and chroma through ClassicWheel and attach only to the selected JADE native card.

Ship the entry point as index.js_ so its query, preview, selection, deselection, and history contracts remain reviewable without enabling the Classic card control.

Validation:
- The disabled ClassicSkinSelector source passed a JavaScript syntax check through standard input.
- Its source matches the completed local development component before the disabled-entry rename.
- Classic card-level Mod selection has not been formally validated on a live machine.

Review notes:
- ROSE-ClassicSkinSelector is installed but disabled by default.
- The regular ROSE-CustomSkinSelector remains available for regular modes.
- Enable this component only together with the Classic Mod backend and compatibility validation.

Refines: f1b48e4e4cf63e893ec436b4136ed6b5bd7d15b2
Document that the Classic Mod front-end sources and scoped backend contracts are present while their Pengu Loader entry points remain disabled through the native index.js_ convention.

Keep category compatibility and SettingsPanel integration explicitly experimental until formal validation is complete.

Validation:
- Documentation was checked against the integrated source filenames and Pengu Loader discovery behavior.
- No Classic Mod control is advertised as an enabled user feature.

Review notes:
- Enabling either entry point is outside this commit.
- README continues to list only enabled Classic controls.
- Classic package categories still require formal compatibility validation.

Refines: 00f950f
@ccccxp

ccccxp commented Aug 11, 2026

Copy link
Copy Markdown
Author

A small follow-up for source consistency: the experimental Classic third-party Mod components and their mode-scoped backend contracts are now included, but the feature is still not enabled. Both Classic JavaScript entry points are committed as Pengu Loader's native disabled index.js_ form, so they do not load or expose any UI. Regular Mod state is isolated from JADE, and the regular CustomWheel is hidden there. Full Classic Mod loading and category compatibility have not been formally validated yet.

ccccxp added 3 commits August 11, 2026 19:44
Prevent the frozen runtime sync from restoring ROSE-ClassicMods or ROSE-ClassicSkinSelector from an enabled state left by a development build. The bundled index.js_ entrypoints now remain authoritative on main, and the existing sanitizer parks any stale index.js copy.

Validation:
- 31 existing tests passed.
- Simulated an enabled Classic Mod runtime followed by the main overlay; the stale index.js was parked and index.js_ remained active.
- Confirmed an ordinary plugin still preserves its user-selected enabled state.
- Python compilation and git diff --check passed.

Review notes:
- Classic Mod source files remain bundled for source consistency but are not loaded.
- Regular CustomWheel and all unrelated plugin enable/disable choices are unchanged.
- Re-enabling the experimental Classic Mod components later requires removing this main-only denylist together with renaming their entrypoints.
Accept a non-default owned skin when the native carousel center and LCU selection settle on the same entry during user navigation, even if recycled JADE card assets predicted a different target. Publishing the authoritative owned selection clears the stale visual fallback instead of restoring the previous unowned card.

Reject chroma choices from a panel whose parent skin no longer matches ClassicWheel current selection. This prevents an open chroma panel from overriding a later owned-skin selection.

Runtime evidence:

- The packaged runtime selected owned Spirit Blossom Evelynn (28032) while the recycled carousel target remained Coven Evelynn (28024).

- A stale Coven Pearl chroma submission (28028) then replaced the owned selection and became the next historic target.

Real-machine validation:

- The source runtime was exercised on the Windows test client after this fix and the follow-up chroma fallback fix.

- Owned-skin navigation, Classic projection, chroma selection, random mode, and historic mode completed without an observed regression.

Validation:

- Both modified plug-ins passed node --check.

- 31 existing tests passed.

- git diff --check passed.

Review focus:

- ROSE-ClassicWheel adaptNativeController(): authoritative owned-selection reconciliation.

- ROSE-ClassicChroma choice click handler: stale parent rejection.

- Historic and random native projection paths remain unchanged.
Keep Classic visual protection active when a projected chroma ID differs from its parent carousel entry. An unowned chroma on an owned parent still uses the Classic default as the real LCU carrier, while the native carousel, selected swatch, and full-client splash remain attached to the intended parent skin.

Runtime evidence:

- Cat-in-the-Box Shaco (35064) was owned and centered correctly.

- Selecting Rose Quartz (35067) enabled protection, but adaptNativeController immediately cleared it because the parent was owned.

- LCU then fell back to 35000, moving the carousel to the default card while retaining the 35067 chroma state.

Real-machine validation:

- Revalidated from source on the Windows test client with owned and unowned Classic skin transitions.

- Parent cards, chroma swatches, LCU splash artwork, random mode, and historic mode remained synchronized.

- No remaining issue was observed in the tested Classic selection flow.

Validation:

- ClassicWheel and ClassicChroma passed node --check.

- 31 existing tests passed.

- git diff --check passed.

Review focus:

- ROSE-ClassicWheel adaptNativeController(): projected chroma protection now considers the child/parent ID difference.

- Base-skin ownership and ordinary user navigation behavior are unchanged.
@ccccxp

ccccxp commented Aug 11, 2026

Copy link
Copy Markdown
Author

As a small extra, I also added a separate test-build branch and portable artifact without changing the PR branch or the LeagueSkins resource PR:

https://github.com/ccccxp/Rose/actions/runs/31488282883/artifacts/9100027653

You're very welcome to try it in Classic Mode and share any feedback. Please don't report issues found in this test build as problems with an official Rose release; reply to this comment or open an issue against my test branch instead. It is still a test build, so there may be a few bugs hiding in it.

I also updated the PR description with a new demo video. It should give a clearer idea of how the carousel, chromas, history, random selection, splash-art projection, and final skin injection currently work in Classic Mode.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant