Skip to content

feat(classic-wheel): stabilize the native Rift Classic skin carousel - #344

Open
ccccxp wants to merge 12 commits into
Alban1911:mainfrom
ccccxp:pr/classic-wheel
Open

ccccxp wants to merge 12 commits into
Alban1911:mainfrom
ccccxp:pr/classic-wheel

Conversation

@ccccxp

@ccccxp ccccxp commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Merge order

This is the base PR for Classic history #345, Classic randomization #346, and Classic chromas #347. Please merge this PR first; the feature PRs contain only their own code and use the wheel API introduced here.

Native carrier support #318 is optional and can be merged independently.

What this changes

This PR stabilizes Riot's native Rift Classic skin carousel instead of replacing it with a custom list.

  • Keeps the native cards, arrows, names, animations, order, and left/right limits.
  • Shows Classic cards as unlocked in the frontend only; account ownership is unchanged.
  • Supports card clicks and arrow navigation and releases automatic control after a manual choice.
  • Keeps the selected card and full-screen champion-select artwork synchronized.
  • Prevents countdown rollback to the default or an owned skin.
  • Provides a stable center-card host for the separate history, random, and chroma controls.

Simply unlocking the pane is not enough: the client can reuse card nodes and report its carrier or fallback skin again while a selection settles. The wheel therefore keeps the displayed resource target separate from the injection carrier until navigation is complete.

Why a separate plugin

Classic uses JADE-specific 600xx IDs and a fixed native carousel. Keeping this behavior in ROSE-ClassicWheel avoids spreading Classic conditions through the regular selector and lets the extra features remain optional plugins.

The plugin activates only for Rift Classic and clears its state when that context ends. Regular game modes keep their existing behavior.

Carrier and resource compatibility

On its own, this PR keeps upstream's Skin0 path and the classic/ package library.

If #318 is also present, Rose automatically matches one of two complete paths:

  1. the champion's live Skin0/Skin301/Skin302 default carrier with classic-native/;
  2. generated 600xx000 Skin0 with the fully retargeted classic/ package.

The carrier and package family are switched together and are never mixed. The wheel continues to work with resource skin IDs, so history, randomization, chromas, card movement, and artwork synchronization do not need separate implementations for the two paths.

Validation

  • All Pengu plugin JavaScript syntax checks passed.
  • The carrier, wheel, history, random, and chroma branches merge together without conflicts.
  • Focused injection, Classic library, history, Pengu bridge, chroma, and countdown checks passed.
  • Repeated live Classic tests covered card and arrow navigation, artwork changes, countdown finalization, and injection without the earlier freeze or card rollback.

Changed files

  • Pengu Loader/plugins/ROSE-ClassicWheel/index.js
  • Classic selection, LCU state, injection, name-resolution, and champion-select lifecycle modules

@ccccxp

ccccxp commented Oct 9, 2026

Copy link
Copy Markdown
Contributor Author

Closing this submission temporarily because the wheel branch still duplicates part of the native-carrier support already under review in #318. I will separate the carrier discovery/default-slot lifecycle from the native carousel and resubmit the wheel-only change with #318 treated as an optional dependency.

@ccccxp ccccxp closed this Oct 9, 2026
@ccccxp ccccxp reopened this Oct 9, 2026
@ccccxp

ccccxp commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor Author

Final compatibility verification: both PRs are functionally independent and the shared integration boundary is conflict-free. Without #318, #344 keeps the upstream Skin0 policy; its standalone fallback explicitly imports the Classic champion ID conversion it uses. Without #344, #318 resolves the native carrier at the final injection write boundary, including when LCU already reports generated Skin0 (600xx000), and restores the cached native carrier if LCU later rolls back to Skin0. With both present, the wheel-provided carrier is reused. The current #344 -> #318 merge completes automatically with no conflict, and all 17 carrier/injection tests pass on both the standalone #318 tree and the combined tree.

ccccxp added 12 commits October 18, 2026 16:50
Build the Classic selector on top of Riot's native fixed carousel and keep the visual resource target separate from the server-visible JADE carrier. Validate every catalog and requested selection against the locked champion and the previously resolved native default slot.

Preserve requested unowned targets through finalization, translate owned selections back to their 600-prefixed LCU IDs, keep the target splash synchronized after native rollback, and reset carousel movement state at every champion-select lifecycle boundary.

Remove the document-wide MutationObserver used during early adaptation. Its callbacks could feed back into carousel mutations and freeze the client; bounded polling plus native phase and lock events cover the required refresh points without that loop.

Source commits consolidated: 285264ad, 5172791a, and f701cc8d. Carrier discovery remains in a22c161 and is intentionally not duplicated here.

Verification: the final observer-free wheel completed live champion-select navigation without the previous freeze. JavaScript syntax and changed Python modules passed static compilation checks.
Bind shared mode state to the LCU selector and reject JADE writes that are neither the live native carrier nor an owned skin for the active champion.

Track the raw LCU skin separately from Rose's resource visual target so carrier confirmations cannot overwrite an unowned, historic, random, or chroma carousel movement. Cancel stale asynchronous Classic injection work when the carousel generation changes.

Reset the raw carrier state with the existing champ-select lifecycle and add CLASSIC:LCU and CLASSIC:INJECT checkpoints for carrier resolution, target preservation, and game-start identity.

Verification: Python compilation passed; Classic regression suite passed; regular-mode selection retains the original unguarded path.
Restore saved Classic random or history state only after the wheel catalog is valid. Give random mode priority, release requested state on explicit user selection, and preserve chroma selection through the shared Classic selection boundary.

This coordination remains optional: missing history or random feature modules leave the base wheel functional.

Validation: covered by final branch syntax and git diff checks. No separate live-client run is claimed for this commit.
Negotiate the wheel carrier with the Python bridge. Use the native Skin301/Skin302 carrier when the carrier feature is available, while retaining the upstream Skin0 policy when this feature branch is applied without the native-carrier PR.

Keep one Classic resource catalog and translate only the carrier slot; history, random, and chroma plugins remain optional consumers of the same wheel API.

Validation: covered by final branch syntax and git diff checks. No separate live-client run is claimed for this commit.
Use the selected native card as the single presentation host, release requested history control on user navigation, and prevent duplicate or enlarged cards during repeated carousel refreshes.

This preserves the target splash and selection state after carousel movement settles without changing the carrier decision.

Validation: covered by final branch syntax and git diff checks. No separate live-client run is claimed for this commit.
Complete historic presentation when the native center reaches the requested target, even if the fixed carousel temporarily reports an unavailable arrow and drops its carousel movement callback.

Expose one stable control host that follows the native center tile immediately. Keep center-card overflow visible so optional Classic controls are not clipped while Riot recycles carousel nodes.

This remains carrier-policy neutral: the wheel uses upstream Skin0 when no carrier policy is installed and accepts a supplied native carrier when the optional carrier change is present.

Validation:

- Integrated live testing confirmed stable Classic navigation before this final center-host correction.

- JavaScript syntax and git diff checks passed.

- Focused history and random regression checks passed (9 tests).
Import the champion ID conversion used by the standalone Skin0 fallback in _injection_carrier_skin_id(). The previous integrated development tree received this symbol indirectly from the native-carrier work, which hid the missing wheel dependency.

Validation:

- Python compilation passes.

- Standalone and native-carrier combination tests are rerun in the final verification.
Call an optional Classic random integration only after the native catalog has been validated and when the champion context is reset. The wheel owns the lifecycle boundary, while the random PR owns persistence, target selection, and mode coordination.

The imports remain optional, so the wheel continues to work by itself, keeps the upstream Skin0 carrier policy when the carrier PR is absent, and does not alter regular-mode randomization.

Validation: Python compilation and git diff checks pass for both changed modules.
Keep Classic chroma routing behind an optional feature module while the wheel owns the validated catalog and projected-target boundary. Also classify canonical Classic chroma IDs in the shared resolver so history, randomization, and protected targets use the correct archive prefix.

Without the chroma PR, the optional bridge is inert and regular ChromaWheel behavior is unchanged. The feature PR supplies only the Classic picker and bridge implementation.

Validation: Python compilation and git diff checks pass for both changed modules.
Keep the wheel on one injection path and load the native-carrier policy only when that optional module is installed. Without the carrier PR, the same path keeps the generated Classic Skin0 ID and upstream package behavior.

Pass the native package flag only when selected, so the wheel remains compatible with the current upstream injection manager. Custom-mod loading stays on the existing path and remains outside this PR series.

Validation: Python compilation and git diff checks pass for the injection trigger.
Return the regular champion's Skin0 ID before loading the optional carrier policy. ClassicWheel remains inert for ordinary games even when the native-carrier PR is installed, so regular injection never probes Classic package roots.

Validation: Python compilation and git diff checks pass for the injection boundary.
Ask the optional carrier module to identify and cache the live default slot before checking either Classic package family. An unresolved carrier now stops at the shared injection boundary instead of allowing package availability to imply a slot.

The import remains optional: ClassicWheel alone still returns the generated Skin0 ID and keeps upstream injection behavior. When the carrier PR is present, both PRs use the same call order and merge without duplicate policy code.

Validation: 23 focused injection, bridge-isolation, custom-mod, chroma, and mode tests pass; ClassicWheel JavaScript syntax and git diff checks pass.
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