Skip to content

feat(highway): render per-note bend curve (bnv) on 2D + 3D (PR-B) - #532

Merged
byrongamatos merged 1 commit into
mainfrom
feat/bend-shape-highway
Jun 20, 2026
Merged

feat(highway): render per-note bend curve (bnv) on 2D + 3D (PR-B)#532
byrongamatos merged 1 commit into
mainfrom
feat/bend-shape-highway

Conversation

@byrongamatos

Copy link
Copy Markdown
Contributor

What

PR-B of the per-note bend-shape feature (feedpak spec §6.2.1). Both highways drew a bend from the scalar bn only; this teaches them to trace the authoritative bnv curve ([{t, v}]) when present, falling back to the existing bn arc/envelope when absent. Builds on the wire/import fields from PR-A (#531).

Render-only — no wire or schema change.

Changes

2D — static/highway.js (drawNote)

  • When a note carries bnv, draw its real shape as a contour above the gem: round-trip rises then falls, pre-bend starts high, release descends — bt is implicit in the point geometry. Arrowhead only when the gesture ends rising.
  • bnvNormalizedPoints maps {t, v} to a normalized 0..1 x span (pure helper).
  • The scalar-arrow path is preserved as the fallback; the peak label is unchanged.

3D — plugins/highway_3d/screen.js

  • bnvSampleAt linearly interpolates the curve (clamped to endpoints); bendSemisAtTime samples it when present, else keeps the synthetic rise→hold→release envelope from bn. Chevron count still derives from the peak.
  • Fixed a stale-scratch hazard surfaced by review: the reused _scrChordNote now resets bnv/bt (omit-when-default fields) after Object.assign, mirroring the existing fhm reset, so a chord note without a curve can't inherit the previous note's contour.

Testing

  • tests/js/highway_bend_curve.test.js (new) — extract-and-eval behavioural tests for both pure helpers: interpolation, endpoint clamping, round-trip up/down, degenerate/empty/duplicate-t inputs.
  • node --check passes on both files; npm run test:js692 passed.
  • Codex preflight (read-only): clean after addressing one P2 (the _scrChordNote stale-bnv fix above).

Notes

  • Live visual verification was not performed — getting a bnv-bearing note onto the highway needs the full native-core + Playwright stack and a curve-carrying pack. Coverage here is the pure-helper unit tests + node --check; the drawing calls mirror the existing arrow path. Worth an eyeball in a build.

Part of #334

🤖 Generated with Claude Code

PR-B of the bend-shape feature (feedpak §6.2.1). Both highways drew a bend
from the scalar `bn` only; now they trace the authoritative `bnv` curve
([{t, v}]) when present and fall back to the `bn` arc/envelope otherwise.

2D (static/highway.js drawNote): when a note carries `bnv`, draw the real
shape as a contour above the gem (round-trip rises then falls, pre-bend
starts high, release descends — `bt` is implicit in the point shape), with
an arrowhead only when the gesture ends rising. `bnvNormalizedPoints` maps
{t,v} to a 0..1 x span. The scalar-arrow path is preserved unchanged as the
fallback; the peak label is unchanged.

3D (plugins/highway_3d/screen.js): `bnvSampleAt` linearly interpolates the
curve (clamped to its endpoints) and `bendSemisAtTime` samples it when
present, else keeps the synthetic rise→hold→release envelope from `bn`. The
chevron count still comes from the peak. Fixed a stale-scratch hazard: the
reused `_scrChordNote` now resets `bnv`/`bt` (omit-when-default) after
Object.assign, mirroring the existing `fhm` reset, so a chord note without a
curve can't inherit the previous note's contour.

Render-only — no wire/schema change. Pure helpers covered by
tests/js/highway_bend_curve.test.js (interp, clamping, round-trip,
degenerate/empty); node --check passes on both files; full tests/js green.

Part of #334

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@byrongamatos
byrongamatos merged commit 351b273 into main Jun 20, 2026
0 of 3 checks passed
@byrongamatos
byrongamatos deleted the feat/bend-shape-highway branch June 20, 2026 21:34
byrongamatos added a commit that referenced this pull request Jun 20, 2026
#535)

Post-merge Codex review of the bend-curve PRs (#531/#532) surfaced edge cases:

- GP8 (#531 P2): bnv timing used rn.sustain, which is zeroed for notes <= 0.2s,
  so short GP8 bends kept the scalar bn but lost bt/bnv. Use the beat duration
  `dur` (matching the GP5 path) so the curve survives.
- 2D highway (#532 P2): bnvNormalizedPoints mapped x over the curve's own t-range
  [first,last] instead of the note span, so curves not starting at 0 / ending at
  sus were time-distorted. Now maps over [0, sus] (clamped), with a curve-span
  fallback when sus<=0 (existing no-sus callers unaffected).
- 3D highway (#532 P3): the sustain ribbon + bend chevron were gated on bn>0, so a
  note carrying an authoritative bnv with bn==0 drew no ribbon/marker. Both now
  also fire on bnv presence; chevron steps derived from max(bn, bnv peak).

Codex-reviewed: clean (no findings). +1 JS test (sus-relative mapping + fallback).
JS 8/8, 250 core GP/song tests pass.

NB: GP8's short-bend path still lacks a dedicated synthetic-GPIF fixture (same gap
as the GP8 offset-prop-names P3) — _gpx_bend_shape units cover the function; the
fix is the one-line caller change.

Part of #334.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
byrongamatos added a commit that referenced this pull request Jun 21, 2026
Render the three per-note teaching marks on both highways, mirroring the
bend-curve render (#532). Display only — no scoring / NoteVerifier coupling.

- 2D static/highway.js: fg renders by default as a small finger numeral hugging
  the gem (T = thumb, 1..4); sd (degree label) and ch (strum bracket connecting
  notes that share a ch key, arrow direction from pkd) are opt-in behind a new
  `showTeachingMarks` toggle (exposed via toggle/get/set + the bundle's
  `teachingMarksVisible` flag). Pure helpers teachingFingerLabel /
  teachingDegreeLabel / strumGroupBuckets drive the glyphs. ch bracket is
  note-stream-only (chord notes already read as one gesture).
- 3D plugins/highway_3d/screen.js: fg (default) + sd (opt-in, mirrors the 2D
  toggle via bundle.teachingMarksVisible) render next to the per-note fret label
  via a new pooled sprite (pTeachMarkLbl); _scrChordNote resets fg/sd so chord
  notes don't inherit stale marks. ch strum brackets are deferred in 3D (no
  cross-note batch pass in the per-note render); 2D covers ch.

Tests: tests/js/highway_teaching_marks.test.js extracts the pure helpers from
both files (extract-and-eval) and asserts label mapping + strum-group bucketing.

Part of #334

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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