Skip to content

fix: unify section-difficulty fill formula with drawHud's tiers - #79

Merged
carochacs merged 2 commits into
mainfrom
claude/next-issues-f1t8df
Sep 1, 2026
Merged

fix: unify section-difficulty fill formula with drawHud's tiers#79
carochacs merged 2 commits into
mainfrom
claude/next-issues-f1t8df

Conversation

@carochacs

@carochacs carochacs commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

What

The audit in #63 found the documented Section Map integration contract contradicts what's actually implemented — and it's worse than the issue text: verifying against the real code in both repos turned up two independent, distinct problems, not one.

1. The docs describe an architecture that no longer exists. INTEGRATION.md (and feedBack-plugin-sectionmap's own CLAUDE.md) both claim "no direct plugin-to-plugin API — both plugins independently read the same Host getters." That was true at one point, but section_map's glass rendering was since rewritten to consume this plugin's difficulty:sections-updated event exclusively — its current screen.js has no code path left that reads highway.getPhrases()/getMastery() for difficulty data at all. Both docs kept asserting the old architecture as current fact.

2. The two glass renderers actually use different formulas. This plugin's own player HUD (drawHud()) computes a discrete difficulty tier: idxLevel = min(max_difficulty, floor(mastery * (max_difficulty + 1))), fillFrac = idxLevel / max_difficulty. The event it emits for section_map used a continuous formula instead: mastery * maxSectionDifficulty / globalMaxDifficulty. These disagree materially for a lower-depth phrase/section — e.g. mastery 0.6 against a max_difficulty-2 phrase/section (of a song-wide max of 4) lands on discrete tier 1 of 2 (50%), not the continuous formula's 30%. The two were never visible side-by-side (drawHud() already defers to section_map when it's installed), which is likely why this drifted unnoticed.

Fix

  • screen.js — extracted _tierFillFrac(mastery, maxDifficulty) as the single source of truth for the discrete-tier arithmetic. _presentedDifficultyLevel() and drawHud() now delegate to it; calculateAndEmitSectionDifficulties() now uses it too (applied to the section's own aggregated maxSectionDifficulty — the existing max-of-overlapping-phrases rule is unchanged), replacing its old continuous formula.
  • INTEGRATION.md — rewritten to describe the real, current contract: the event is the API, with the load-order assumption (difficulty_ladder < section_map alphabetically, so the capability marker is set before section_map's one-time availability check) and fallback/stale-event behavior documented. The old issue-Verify integration with feedBack-plugin-sectionmap section difficulty visualization #8-era content is kept below a "superseded" marker as historical record rather than deleted outright.
  • Companion fix in feedBack-plugin-sectionmap corrects its CLAUDE.md and removes a related dead check — see that repo's PR.

Fixes #63

Checklist

  • pytest — 137/137 passing
  • node tests/screen.test.js — 60/60 passing (4 new: _tierFillFrac tier boundaries/no-ladder/clamping, and a calculateAndEmitSectionDifficulties test demonstrating the exact 50%-vs-30% case the old formula got wrong)
  • Bumped plugin.json version (0.9.10 → 0.9.11 — bug fix affecting real behavior)
  • Updated CHANGELOG.md

Generated by Claude Code

Summary by Sourcery

Align section-map difficulty visualization with the player HUD and document the current event-based integration contract.

Bug Fixes:

  • Unify per-section difficulty fill percentages with the player HUD’s discrete difficulty-tier calculation, correcting materially inaccurate section-map glass fills.
  • Report zero fill for sections without overlapping difficulty content while preserving full-fill behavior for phrases without a tier ladder.

Enhancements:

  • Centralize difficulty-tier arithmetic in a shared helper used by HUD rendering, section difficulty events, and presented-level calculation.
  • Rewrite the integration documentation to document the event-based section-map contract, availability requirements, fallback behavior, and timing considerations.

Documentation:

  • Update the changelog and integration documentation to reflect the corrected section-map contract and difficulty fill behavior.

Tests:

  • Add coverage for tier boundaries, mastery clamping, invalid or absent difficulty ladders, and the corrected section-level fill calculation.

Chores:

  • Bump the plugin version for the bug fix.

The per-section "glass fill" emitted for feedBack-plugin-sectionmap
(difficulty:sections-updated) used a different formula than this
plugin's own player HUD: drawHud() computed a discrete difficulty
tier (idxLevel/max_difficulty), while calculateAndEmitSectionDifficulties()
used a continuous mastery-scaled fraction. These disagree materially
for a lower-depth phrase/section (e.g. 50% vs. 30% for the same
mastery/difficulty pair) -- the two formulas were only never visible
side by side because drawHud() already defers to sectionmap's
minimap when it's installed.

- screen.js: extracted _tierFillFrac(mastery, maxDifficulty) as the
  single source of truth for the discrete-tier arithmetic;
  _presentedDifficultyLevel() and drawHud() now delegate to it, and
  calculateAndEmitSectionDifficulties() now uses it (applied to the
  section's own aggregated maxSectionDifficulty) instead of its old
  continuous formula.
- INTEGRATION.md: rewritten to describe the real, current contract.
  section_map was rewritten at some point to consume the
  difficulty:sections-updated event exclusively -- it no longer reads
  highway.getPhrases()/getMastery() independently at all -- but both
  this file and section_map's own CLAUDE.md kept describing the old
  Host-getters-only architecture as current fact. Also documents the
  load-order assumption (alphabetical script load puts
  difficulty_ladder before section_map) and fallback/stale-event
  behavior, and flags a dead window.feedBackViz_dynamic_difficulty
  probe left over from the plugin's rename (fixed in the
  feedBack-plugin-sectionmap repo).
- Tests: _tierFillFrac coverage (tier boundaries, no-ladder case,
  mastery clamping) and a calculateAndEmitSectionDifficulties test
  demonstrating the exact 50%-vs-30% case the old formula got wrong.
@sourcery-ai

sourcery-ai Bot commented Sep 1, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR fixes inconsistent section-glass fills by sharing the HUD’s discrete difficulty-tier formula with emitted section updates, adds regression coverage, and rewrites integration documentation around the current event-driven section-map contract while releasing version 0.9.11.

Sequence diagram for unified section difficulty updates

sequenceDiagram
    participant DifficultyLadder
    participant Host as window.highway
    participant FeedBack as window.feedBack
    participant SectionMap

    DifficultyLadder->>Host: getPhrases()
    DifficultyLadder->>Host: getMastery()
    DifficultyLadder->>DifficultyLadder: _tierFillFrac(mastery, maxSectionDifficulty)
    DifficultyLadder->>FeedBack: emit difficulty:sections-updated
    FeedBack->>SectionMap: difficulty:sections-updated payload
    SectionMap->>SectionMap: _smUpdateDifficultyFills()
    SectionMap->>SectionMap: render fillPercentage and glassSize
Loading

File-Level Changes

Change Details Files
Unified all difficulty glass rendering around the discrete tier fill calculation.
  • Added _tierFillFrac(mastery, maxDifficulty) with mastery clamping, tier calculation, and no-ladder handling.
  • Refactored phrase-level HUD rendering and presented difficulty levels to use the shared helper.
  • Changed emitted section difficulty fills to use each section’s aggregated maximum difficulty rather than global difficulty scaling.
  • Added boundary, clamping, and regression coverage for the former 50%-versus-30% discrepancy.
screen.js
tests/screen.test.js
Updated the integration documentation to match the event-driven section-map contract. INTEGRATION.md
Published the behavioral fix as a patch release and documented its impact.
  • Added the section-fill inconsistency fix and contract correction to the changelog.
  • Bumped the plugin version from 0.9.10 to 0.9.11.
CHANGELOG.md
plugin.json

Assessment against linked issues

Issue Objective Addressed Explanation
#63 Establish and document whether Host getters or the difficulty:sections-updated event is the authoritative Section Map integration API, including fallback, timing, and stale-event behavior.
#63 Define and apply one canonical discrete difficulty-tier fill formula across the player HUD and section-level difficulty event, while preserving the existing section difficulty aggregation behavior.
#63 Update the relevant implementation, integration documentation, and tests to reflect and verify the chosen integration contract and formula.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Important

Approval pending

CodeRabbit has no unresolved comments, but it has not reviewed the latest commit.

Use the checkbox below to review the latest commit. CodeRabbit will approve the changes if it finds no blocking issues.

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="screen.js" line_range="607" />
<code_context>
+                // used a different, continuous mastery-scaled fraction here,
+                // which could disagree materially with drawHud()'s discrete
+                // tiers for the same mastery/difficulty pair.
+                var fillPercentage = _tierFillFrac(mastery, maxSectionDifficulty).fillFrac * 100;

                 // Determine glass size based on section difficulty
</code_context>
<issue_to_address>
**issue (bug_risk):** A generated empty section can have `max_difficulty: 0` while another phrase in the song has a positive difficulty, as allowed by the generation code. The old section formula emitted a 0% fill for that section, but `_tierFillFrac()` now emits 100%, so an empty/no-difficulty section is rendered as a fully mastered glass.

**Triggers:** When a song contains an empty section phrase alongside at least one positive-depth phrase.

**Suggested fix:** Preserve the section renderer's prior zero-depth behavior by emitting 0% (or omitting the glass) when `maxSectionDifficulty <= 0`, while retaining the fully-filled convention only for the per-phrase HUD if that is required.
</issue_to_address>

Sourcery assessment

Approval pending. 1 finding to address first.

Blocking findings: screen.js:607


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread screen.js Outdated

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ One edge-behavior observation worth confirming — the fix itself is clean and correct.

Reviewed changes

  • screen.js — extracted _tierFillFrac(mastery, maxDifficulty) as the single source of truth for the discrete-tier fill arithmetic, and migrated all three former sites (_presentedDifficultyLevel, drawHud's per-phrase glass, and calculateAndEmitSectionDifficulties' section fill) onto it. Section fill now applies the tier formula to the section's own maxSectionDifficulty instead of scaling against the song-wide max.
  • INTEGRATION.md — rewritten to describe the real, current event-based contract (difficulty:sections-updated is the API), with load-order, fallback, and stale-event behavior documented; the issue-#8 record kept under a superseded marker.
  • plugin.json0.9.10 → 0.9.11 (patch, real-behavior bug fix). CHANGELOG.md updated.
  • tests/screen.test.js — 4 new tests covering _tierFillFrac boundaries/clamping and the 50%-vs-30% section case; 60/60 pass locally and node --check is clean.

I verified the refactor is semantically equivalent at all three migrated sites (the only delta is the deliberate mastery clamp inside _tierFillFrac, which is harmless for in-range inputs), that both INTEGRATION.md globals it names (_ddCapabilities.sectionDifficulty at screen.js:25, __slopsmithSectionMapHooksInstalled at screen.js:976) are grounded in the actual code, and that the new section test genuinely pins the fix (it would still fail with the old continuous formula). No remaining un-migrated tier sites exist in screen.js.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using Big Pickle (free) | 𝕏

Comment thread screen.js Outdated
Sourcery caught a real regression in the previous commit: _tierFillFrac's
"no ladder -> fully filled" convention is correct for drawHud's
per-phrase case (a single-tier phrase reads as already at its one
level), but wrong for the section-level aggregate, where
maxSectionDifficulty === 0 means "nothing overlaps this section at
all" -- e.g. an empty/silent section next to phrases that do have
depth. The old continuous formula always emitted 0% for this case;
routing it through _tierFillFrac's shared zero-difficulty branch
silently flipped it to 100%.

calculateAndEmitSectionDifficulties() now special-cases
maxSectionDifficulty <= 0 to 0%, matching prior behavior, while still
using the shared discrete-tier math for every section that actually
has difficulty content (the real fix this PR is about). Added a
regression test reproducing the exact trigger Sourcery described.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sourcery assessment

Approved.

@carochacs
carochacs merged commit 13888a6 into main Sep 1, 2026
23 checks passed
@carochacs
carochacs deleted the claude/next-issues-f1t8df branch September 1, 2026 03:24
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.

Reconcile Section Map integration contract and fill formula

2 participants