Repository navigation
feat(capabilities): add practice-difficulty override domain (#136) - #138
Conversation
|
Diagnosis. The only failing job was Fix. Relabelled the commit in place — Verification. The commit tree is byte-identical to the original ( Task list (5/5 completed)
|
Not up to standards ⛔🔴 Issues
|
| Category | Results |
|---|---|
| Security | 3 critical |
🟢 Metrics 214 complexity · 35 duplication
Metric Results Complexity 214 Duplication 35
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
Adds a core-owned `practice-difficulty` domain so a practice plugin can play part of a chart at a different difficulty than the rest of the song, without a second difficulty slider existing. It is deliberately complementary to `player-difficulty.v1`, which drives the song-wide mastery slider. `getMastery()` keeps reporting that slider unchanged; the new override only affects phrase rendering, by resolving each phrase's level from `_effectiveMasteryAt(p.start_time)` instead of `hwState._mastery`. `getEffectiveMastery(time)` and `bundle.difficultyOverride` expose the value actually in force. Why this shape: - One override per player context, routed through `playerContexts.getHighway()` so a splitscreen panel only ever affects its own highway, and a stale snapshot resolves to no highway at all. - Registrant identity comes from the dispatch caller, never the payload, so a caller cannot claim another's slot. - Conflicts are incumbent-wins and never stack. Because a highway holds a single override slot, a request whose context resolves to a highway another live record owns is refused too, rather than silently overwriting that window while both records claim to be in force. - Lifecycle reconciliation releases records whose context stopped resolving and re-installs records whose highway was rebuilt mid-song, so a chart rebuild cannot leave a record reported as active while the chart renders at the slider. - Diagnostics are count-only; the redacted context ref never exposes raw song/player/profile identity, and registrant labels are bounded and stripped of paths, URLs and credentials. The subject is labelled `core(capabilities):` rather than `feat(capabilities):` to satisfy this fork's commit-labeling policy — the change edits core paths (`static/**`, `AGENTS.md`, `CLAUDE.md`), and docs/fork-maintenance.md Rule 2 requires a `core:`/`hook:`/`sync:`/`fix:` prefix on any such commit.
058c77f to
e71e30c
Compare
cchacs
left a comment
There was a problem hiding this comment.
Review for #136 (practice-difficulty capability). I found no code defect, but the API names and the conflict policy this PR implements have not been approved by a human, so please treat them as open.
Decisions for @itsjustroxi (tagged on #136)
- Names: domain
practice-difficulty(ownercore.practice-difficulty, no.v1), commandsinspect/register-participant/unregister-participant/activate/clear, eventsparticipant-registered/participant-unregistered/override-activated/override-cleared/override-rejected, snake_case payloads. - Conflict attribution: incumbent wins, never stacked. The same registrant updates in place; a second registrant is denied with
held_by. - The naming proposal was posted on #136 by Pullfrog at 20:28 UTC. I see no human reply on the issue before this PR opened, so the PR's "posted for approval before any code was written" is true, but the approval was never given. The PR is marked draft / do not merge, which is the right state until someone signs off.
Checked
node --test tests/js/*.test.js→ 1199 pass / 0 fail. The three directly relevant files (practice_difficulty_domain,highway_difficulty_override,highway_phrase_tiers) → 38/38.- Not run:
pytest,npm run lint, and the plugin-level JS suites the description counts in its 1435 total. I did not audit the whole 543-line domain module, only the activate path and the lifecycle wiring. - Matches #136: one override per player context; clamp to 0–100 with
clamped; half-open[start_time, end_time)withend_time <= start_timedenied; registrant identity taken from the dispatch caller; runtime participant disappearance handled viacapabilities.subscribe('unregistered', …); clears on song/context replacement;getMastery()untouched.
Findings (non-blocking)
- Practice-mode exit is not a clearing condition. #136 and the naming proposal both list "practice-mode exit". The code and the PR's lifecycle list cover
clear,unregister-participant, song replacement and context replacement/departure only, and I found no practice-mode hook. That may be right if exit is the plugin's ownclear/unregistercall, but the PR doesn't say it deliberately drops this criterion. Please state it explicitly. - Acceptance item not done: the recipe is not linked from difficulty-ladder#22. The description says this is outside the token's scope. I'd hold the cross-repo post until the names are approved, so #22 doesn't get names that later change.
- Incumbent-wins has no takeover path. A second practice plugin must wait for the incumbent to clear or disappear. That is deterministic, as required, but it is the behavior @itsjustroxi should confirm.
Not approving or merging.
Generated by Claude Code
|
Addressed the review on #138 (a comment-only review with no inline threads; it found no code defect). Finding 1 — practice-mode exit (acted on). The criterion is now stated explicitly rather than silently dropped: it is implemented at the producer boundary, not as a Host subscription, because core has no consumer-agnostic practice lifecycle to observe — and the one core feature that toggles a practice mode (Section Practice's loop checkbox) drives its own A/B loop rather than the difficulty slot, so honouring it here would clear overrides belonging to other registrants. Documented in Findings 2 and 3 — no code change, now called out for the sign-off. The difficulty-ladder#22 recipe link stays deliberately deferred until the names are approved (that repo is outside this token's scope anyway), and incumbent-wins having no takeover path is recorded in the PR body as one of the decisions requested on #136. The PR body was rewritten to make both explicit; it remains draft / do not merge. Commit Task list (1/7 completed)
|
|
Fixed and force-pushed. The failure was a labeling policy, not a code problem. Diagnosis —
That commit edits Fix — relabelled the commit to Verification
One cosmetic consequence of the rewrite: the tip merge's author/committer now reads as the sandbox bot identity instead of the human author (the message, its Task list (7/7 completed)
|
#136 lists "clears on ... practice-mode exit" as a lifecycle condition, but the domain never implemented a Host-side hook for it and the PR did not say so. It cannot: core has no consumer-agnostic practice lifecycle to observe, and the one core feature that toggles a practice mode (Section Practice's loop checkbox) drives its own A/B loop rather than the difficulty slot, so honouring it here would clear overrides belonging to other registrants. Document the condition where it actually lives - the producer's own clear / unregister-participant call from its teardown path - in the domain inventory and as a consumer rule in the recipe. Also add the missing trailing newline to the domain module. Relabelled from "docs(capabilities):" for docs/fork-maintenance.md Rule 2: this commit also edits static/capabilities/practice-difficulty.js (the trailing newline above), and the exclusion list is per file, so it counts as a core commit however docs-flavoured the subject reads. The org's Conventional-Commit title is retained for the eventual got-feedback/feedBack PR.
Signed-off-by: Carolina <79524656+carochacs@users.noreply.github.com>
366c890 to
a328cbf
Compare
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Files Reviewed (14 files)
Reviewed by free · Input: 110.5K · Output: 27.9K · Cached: 900.6K |

Closes #136. Draft — do not merge; the dependent Section Practice / Difficulty Ladder wiring lands separately, and the API names plus conflict policy still need maintainer sign-off (see Note for maintainers).
What this adds
A core-owned
practice-difficultycapability domain: one time-scoped difficulty override per player context. A practice plugin can now play a stretch of a chart at a different difficulty than the rest of the song (a slowed verse, an isolated tricky bar) without a second difficulty slider existing.How it differs from
player-difficulty.v1Deliberately complementary, not a duplicate.
player-difficulty.v1owns the song-wide mastery slider; this domain owns a transient half-open[start_time, end_time)window and never rewrites the slider.getMastery()keeps reporting the slider unchanged — so Difficulty Ladder's adaptive controller and the slider UI stay consistent and cannot mistake an override for a slider move. The override only affects phrase rendering:_rebuildMasteryFilter()resolves each phrase's level from_effectiveMasteryAt(p.start_time)rather thanhwState._mastery.getEffectiveMastery(time)andbundle.difficultyOverrideexpose the value actually in force.Contract
practice-difficulty/core.practice-difficulty(provider-coordinator, safe)inspect,register-participant,unregister-participant,activate,clearpractice.difficultyparticipant-registered,participant-unregistered,override-activated,override-cleared,override-rejectedplayer_context,difficulty_pct,start_time,end_time,phrase_indices,label)playerContexts.getHighway(), the same private pathplayer-difficulty.v1uses, so a splitscreen panel only ever affects its own highway. A stale snapshot resolves to nothing and returnsno-targetwith no state recorded.difficulty_pctmust be finite and clamps to 0–100 (clamped: truereports it); the window must satisfyend_time > start_time. Vocal (role: 'karaoke') contexts are denied, matching the siblingplayer-difficulty.v1gate.clear,unregister-participant, runtime participant disappearance, song replacement, and context replacement/departure. Reconciliation also re-installs a record whose highway was rebuilt mid-song, so a chart rebuild cannot leave a record reported as active while the chart renders at the slider.clear(orunregister-participant) call from its teardown path. Core has no consumer-agnostic practice lifecycle to observe, and the one core feature that toggles a practice mode — Section Practice's loop checkbox — owns its own A/B loop rather than the difficulty slot, so honouring that toggle here would clear overrides belonging to other registrants. Documented indocs/capability-domains.mdand as a consumer rule indocs/capability-recipes.md.active_overrides,participants,last_outcome). The public surface exposes a hashedcontext-…ref, never raw song/player/profile identity; registrant labels are bounded and stripped of paths, URLs and credentials. Documented explicitly indocs/capability-domains.md: the redaction boundary is the diagnostics surface —dispatchdeliberately broadcasts its full result (labels included) on the sharedcapability:eventbus, the same wayplaybackechoes target titles.Also in this PR
CORE_DOMAIN_REVIEWentry so the Capability Inspector labels this an active core contract rather than "plugin-defined".bundle.difficultyOverridehands out a reused view object, keeping the documented allocation-free render-bundle contract (a per-frame spread would also have changed field identity every frame).getEffectiveMastery()/getDifficultyOverride()getters inAGENTS.md+CLAUDE.md.Validation
pytest— 2939 passed / 4 skipped (full suite).node --test tests/js/*.test.js 'tests/plugins/*/js/*.test.js' 'plugins/*/tests/*.test.js'— 1435 passed, including 22 new domain tests and 10 new highway-seam tests.npm run lint— 0 errors (pre-existingmax-lineswarnings only).reviewfrogbefore commit; its medium findings (shared-highway clobbering, one-way reconciliation, stale-context clear, missing Inspector entry, unreachable participant-disappearance claim, per-frame allocation) are all addressed above.Note for maintainers
practice-difficulty/ the command and event names / snake_case payloads as open.practice-difficulty:override-clearedrather than pre-empting the holder. Deterministic as Section-practice difficulty override 1/4 (core): versioned Host capability and per-highway effective difficulty #136 requires; confirming it is a deliberate choice is one of the decisions requested on Section-practice difficulty override 1/4 (core): versioned Host capability and per-highway effective difficulty #136.DOMAIN_GROUPSabout the newer player domains (player-identity,player-difficulty.v1are also missing there today).big-pickle(free) | 𝕏